Android开发Android技术知识Android知识

OkHttp封装

2017-08-31  本文已影响111人  Android开发666

前言

上个知识点介绍了OKHttp的基本使用,在Activity中写了大量访问网络的代码,这种代码写起来很无聊,并且对技术没什么提升。在真实的企业开发中,肯定是把这些代码封装起来,做一个库,给Activity调用。

封装之前我们需要考虑以下这些问题:

代码实现

首先需要在线引用以下三个依赖库

compile 'com.squareup.okhttp3:okhttp:3.2.0' //okhttp
compile 'com.google.code.gson:gson:2.7' //解析jsons数据
compile 'io.github.lizhangqu:coreprogress:1.0.2' //上传下载回调监听

新建一个HttpConfig类,用来配置一些请求参数,存储请求服务器的公共参数。设置连接时间等。。。

public class HttpConfig {
    private boolean debug=false;//true:debug模式
    private String userAgent="";//用户代理 它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。
    private boolean agent=true;//有代理的情况能不能访问,true:有代理能访问 false:有代理不能访问
    private String tagName="Http";

    private int connectTimeout=10;//连接超时时间 单位:秒
    private int writeTimeout=10;//写入超时时间 单位:秒
    private int readTimeout=30;//读取超时时间 单位:秒

    //通用字段
    private List<NameValuePair> commonField=new ArrayList<>();

    public boolean isDebug() {
        return debug;
    }

    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    public String getUserAgent() {
        return userAgent;
    }

    public void setUserAgent(String userAgent) {
        this.userAgent = userAgent;
    }

    public boolean isAgent() {
        return agent;
    }

    public void setAgent(boolean agent) {
        this.agent = agent;
    }

    public String getTagName() {
        return tagName;
    }

    public void setTagName(String tagName) {
        this.tagName = tagName;
    }

    public List<NameValuePair> getCommonField() {
        return commonField;
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public int getWriteTimeout() {
        return writeTimeout;
    }

    public void setWriteTimeout(int writeTimeout) {
        this.writeTimeout = writeTimeout;
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    /**
     * 更新参数
     * @param key
     * @param value
     */
    public void updateCommonField(String key,String value){
        boolean result = true;
        for(int i=0;i<commonField.size();i++){
            NameValuePair nameValuePair = commonField.get(i);
            if(nameValuePair.getName().equals(key)){
                commonField.set(i,new NameValuePair(key,value));
                result = false;
                break;
            }
        }
        if(result){
            commonField.add(new NameValuePair(key,value));
        }
    }

    /**
     * 删除公共参数
     * @param key
     */
    public void removeCommonField(String key){
        for(int i=commonField.size()-1;i>=0;i--){
            if(commonField.get(i).equals("key")){
                commonField.remove(i);
            }
        }
    }

    /**
     * 添加请求参数
     * @param key
     * @param value
     */
    public void addCommonField(String key,String value){
        commonField.add(new NameValuePair(key,value));
    }
}

我们给服务器提交表单数据都是通过name跟vulue的形式提交数据,封装了NameValuePair类。如果是上传文件,isFile设置true就行。

public class NameValuePair {
    private String name;//请求名称
    private String value;//请求值
    private boolean isFile=false;//是否是文件

    public NameValuePair(String name, String value){
        this.name=name;
        this.value = value;
    }

    public NameValuePair(String name, String value,boolean isFile){
        this.name=name;
        this.value = value;
        this.isFile=isFile;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public boolean isFile() {
        return isFile;
    }

    public void setFile(boolean file) {
        isFile = file;
    }
}

请求服务器成功或者失败都会回调Callback接口,我们封装HttpResponseHandler抽象类来实现这个接口,对服务器返回的数据以及状态码进行了简单的过滤,最终最调用自己的onFailure跟onSuccess方法。

public abstract class HttpResponseHandler implements Callback {
    public HttpResponseHandler(){
    }

    public void onFailure(Call call, IOException e){
        onFailure(-1,e.getMessage().getBytes());
    }

    public void onResponse(Call call, Response response) throws IOException {
        int code =response.code();
        byte[] body = response.body().bytes();
        if(code>299){
            onFailure(response.code(),body);
        }else{
            Headers headers = response.headers();
            Header[] hs = new Header[headers.size()];

            for (int i=0;i<headers.size();i++){
                hs[i] = new Header(headers.name(i),headers.value(i));
            }
            onSuccess(code,hs,body);
        }
    }

    public void onFailure(int status,byte[] data){
    }

//  public void onProgress(int bytesWritten, int totalSize) {
//  }

    public abstract void onSuccess(int statusCode, Header[] headers, byte[] responseBody);
}

Get请求封装

封装后的HTTPCaller代码如下,现在里面只有get请求,如果一下子把post请求跟postFile方法贴进来,代码有点多。

public class HTTPCaller {
    private static HTTPCaller _instance = null;
    private OkHttpClient client;//okhttp对象
    private Map<String,Call> requestHandleMap = null;//以URL为KEY存储的请求
    private CacheControl cacheControl = null;//缓存控制器

    private Gson gson = null;

    private HttpConfig httpConfig=new HttpConfig();//配置信息

    private HTTPCaller() {}

    public static HTTPCaller getInstance(){
        if (_instance == null) {
            _instance = new HTTPCaller();
        }
        return _instance;
    }

    /**
     * 设置配置信息 这个方法必需要调用一次
     * @param httpConfig
     */
    public void setHttpConfig(HttpConfig httpConfig) {
        this.httpConfig = httpConfig;

        client = new OkHttpClient.Builder()
                .connectTimeout(httpConfig.getConnectTimeout(), TimeUnit.SECONDS)
                .writeTimeout(httpConfig.getWriteTimeout(), TimeUnit.SECONDS)
                .readTimeout(httpConfig.getReadTimeout(), TimeUnit.SECONDS)
                .build();

        gson = new Gson();
        requestHandleMap = Collections.synchronizedMap(new WeakHashMap<String, Call>());
        cacheControl =new CacheControl.Builder().noStore().noCache().build();//不使用缓存
    }

    public <T> void get(Class<T> clazz,final String url,Header[] header,final RequestDataCallback<T> callback) {
        this.get(clazz,url,header,callback,true);
    }

    /**
     * get请求
     * @param clazz json对应类的类型
     * @param url 请求url
     * @param header 请求头
     * @param callback 回调接口
     * @param autoCancel 是否自动取消 true:同一时间请求一个接口多次  只保留最后一个
     * @param <T>
     */
    public <T> void get(final Class<T> clazz,final String url,Header[] header,final RequestDataCallback<T> callback, boolean autoCancel){
        if (checkAgent()) {
            return;
        }
        add(url,getBuilder(url, header, new MyHttpResponseHandler(clazz,url,callback)),autoCancel);
    }

    private Call getBuilder(String url, Header[] header, HttpResponseHandler responseCallback) {
        url=Util.getMosaicParameter(url,httpConfig.getCommonField());//拼接公共参数
        Request.Builder builder = new Request.Builder();
        builder.url(url);
        builder.get();
        return execute(builder, header, responseCallback);
    }

    private Call execute(Request.Builder builder, Header[] header, Callback responseCallback) {
        boolean hasUa = false;
        if (header == null) {
            builder.header("Connection","close");
            builder.header("Accept", "*/*");
        } else {
            for (Header h : header) {
                builder.header(h.getName(), h.getValue());
                if (!hasUa && h.getName().equals("User-Agent")) {
                    hasUa = true;
                }
            }
        }
        if (!hasUa&&!TextUtils.isEmpty(httpConfig.getUserAgent())){
            builder.header("User-Agent",httpConfig.getUserAgent());
        }
        Request request = builder.cacheControl(cacheControl).build();
        Call call = client.newCall(request);
        call.enqueue(responseCallback);
        return call;
    }

    public class MyHttpResponseHandler<T> extends HttpResponseHandler {
        private Class<T> clazz;
        private String url;
        private RequestDataCallback<T> callback;

        public MyHttpResponseHandler(Class<T> clazz,String url,RequestDataCallback<T> callback){
            this.clazz=clazz;
            this.url=url;
            this.callback=callback;
        }

        @Override
        public void onFailure(int status, byte[] data) {
            clear(url);
            try {
                printLog(url + " " + status + " " + new String(data, "utf-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            sendCallback(callback);
        }

        @Override
        public void onSuccess(int status,final Header[] headers, byte[] responseBody) {
            try {
                clear(url);
                String str = new String(responseBody,"utf-8");
                printLog(url + " " + status + " " + str);
                T t = gson.fromJson(str, clazz);
                sendCallback(status,t,responseBody,callback);
            } catch (Exception e){
                if (httpConfig.isDebug()) {
                    e.printStackTrace();
                    printLog("自动解析错误:" + e.toString());
                }
                sendCallback(callback);
            }
        }
    }

    private void autoCancel(String function){
        Call call = requestHandleMap.remove(function);
        if (call != null) {
            call.cancel();
        }
    }

    private void add(String url,Call call) {
        add(url,call,true);
    }

    /**
     * 保存请求信息
     * @param url 请求url
     * @param call http请求call
     * @param autoCancel 自动取消
     */
    private void add(String url,Call call,boolean autoCancel) {
        if (!TextUtils.isEmpty(url)){
            if (url.contains("?")) {//get请求需要去掉后面的参数
                url=url.substring(0,url.indexOf("?"));
            }
            if(autoCancel){
                autoCancel(url);//如果同一时间对api进行多次请求,自动取消之前的
            }
            requestHandleMap.put(url,call);
        }
    }

    private void clear(String url){
        if (url.contains("?")) {//get请求需要去掉后面的参数
            url=url.substring(0,url.indexOf("?"));
        }
        requestHandleMap.remove(url);
    }

    private void printLog(String content){
        if(httpConfig.isDebug()){
            Log.i(httpConfig.getTagName(),content);
        }
    }

    /**
     * 检查代理
     * @return
     */
    private boolean checkAgent() {
        if (httpConfig.isAgent()){
            return false;
        } else {
            String proHost = android.net.Proxy.getDefaultHost();
            int proPort = android.net.Proxy.getDefaultPort();
            if (proHost==null || proPort<0){
                return false;
            }else {
                Log.i(httpConfig.getTagName(),"有代理,不能访问");
                return true;
            }
        }
    }

    //更新字段值
    public void updateCommonField(String key,String value){
        httpConfig.updateCommonField(key,value);
    }

    public void removeCommonField(String key){
        httpConfig.removeCommonField(key);
    }

    public void addCommonField(String key,String value){
        httpConfig.addCommonField(key,value);
    }

    private <T> void sendCallback(RequestDataCallback<T> callback){
        sendCallback(-1,null,null,callback);
    }

    private <T> void sendCallback(int status,T data,byte[] body,RequestDataCallback<T> callback){
        CallbackMessage<T> msgData = new CallbackMessage<T>();
        msgData.body = body;
        msgData.status = status;
        msgData.data = data;
        msgData.callback = callback;

        Message msg = handler.obtainMessage();
        msg.obj = msgData;
        handler.sendMessage(msg);
    }

    private Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            CallbackMessage data = (CallbackMessage)msg.obj;
            data.callback();
        }
    };

    private class CallbackMessage<T>{
        public RequestDataCallback<T> callback;
        public T data;
        public byte[] body;
        public int status;

        public void callback(){
            if(callback!=null){
                if(data==null){
                    callback.dataCallback(null);
                }else{
                    callback.dataCallback(status,data,body);
                }
            }
        }
    }
}

MyHttpResponseHandler类继承我们自己写的HttpResponseHandler抽象类,重写两个方法onFailure跟onSuccess。

onFailure 先从map中删除这个请求,打印错误log,发送回调。
onSuccess 先从map中删除这个请求,把json字符串转成对象。发送回调。

Post请求封装

通过以上对HTTPCaller类各个方法的解释,相信你知道了get请求服务器的整个调用流程。现在我们对HTTPCaller类增加post请求的方法以及上传文件的方法。postFile方法增加了ProgressUIListener参数,通过这个接口监听上传文件进度的回调。

public <T> void post(final Class<T> clazz, final String url, Header[] header, List<NameValuePair> params, final RequestDataCallback<T> callback) {
        this.post(clazz,url, header, params, callback,true);
}

/**
 *
 * @param clazz json对应类的类型
 * @param url  请求url
 * @param header 请求头
 * @param params 参数
 * @param callback 回调
 * @param autoCancel 是否自动取消 true:同一时间请求一个接口多次  只保留最后一个
 * @param <T>
 */
public <T> void post(final Class<T> clazz,final String url, Header[] header, final List<NameValuePair> params, final RequestDataCallback<T> callback, boolean autoCancel) {
    if (checkAgent()) {
        return;
    }
    add(url,postBuilder(url, header, params, new HTTPCaller.MyHttpResponseHandler(clazz,url,callback)),autoCancel);
}

private Call postBuilder(String url, Header[] header, List<NameValuePair> form, HttpResponseHandler responseCallback) {
    try {
        if (form == null) {
            form = new ArrayList<>(2);
        }
        form.addAll(httpConfig.getCommonField());//添加公共字段
        FormBody.Builder formBuilder = new FormBody.Builder();
        for (NameValuePair item : form) {
            formBuilder.add(item.getName(), item.getValue());
        }
        RequestBody requestBody = formBuilder.build();
        Request.Builder builder = new Request.Builder();
        builder.url(url);
        builder.post(requestBody);
        return execute(builder, header, responseCallback);
    } catch (Exception e) {
        if (responseCallback != null)
            responseCallback.onFailure(-1, e.getMessage().getBytes());
    }
    return null;
}

/**
 * 上传文件
 * @param clazz json对应类的类型
 * @param url 请求url
 * @param header 请求头
 * @param form 请求参数
 * @param callback 回调
 * @param <T>
 */
public <T> void postFile(final Class<T> clazz, final String url, Header[] header,List<NameValuePair> form,final RequestDataCallback<T> callback) {
    postFile(url, header, form, new HTTPCaller.MyHttpResponseHandler(clazz,url,callback),null);
}

/**
 * 上传文件
 * @param clazz json对应类的类型
 * @param url 请求url
 * @param header 请求头
 * @param form 请求参数
 * @param callback 回调
 * @param progressUIListener  上传文件进度
 * @param <T>
 */
public <T> void postFile(final Class<T> clazz, final String url, Header[] header,List<NameValuePair> form,final RequestDataCallback<T> callback,ProgressUIListener progressUIListener) {
    add(url, postFile(url, header, form, new HTTPCaller.MyHttpResponseHandler(clazz,url,callback),progressUIListener));
}

/**
 * 上传文件
 * @param clazz json对应类的类型
 * @param url 请求url
 * @param header 请求头
 * @param name 名字
 * @param fileName 文件名
 * @param fileContent 文件内容
 * @param callback 回调
 * @param <T>
 */
public <T> void postFile(final Class<T> clazz,final String url,Header[] header,String name,String fileName,byte[] fileContent,final RequestDataCallback<T> callback){
    postFile(clazz,url,header,name,fileName,fileContent,callback,null);
}


/**
 * 上传文件
 * @param clazz json对应类的类型
 * @param url 请求url
 * @param header 请求头
 * @param name 名字
 * @param fileName 文件名
 * @param fileContent 文件内容
 * @param callback 回调
 * @param progressUIListener 回调上传进度
 * @param <T>
 */
public <T> void postFile(Class<T> clazz,final String url,Header[] header,String name,String fileName,byte[] fileContent,final RequestDataCallback<T> callback,ProgressUIListener progressUIListener) {
    add(url,postFile(url, header,name,fileName,fileContent,new HTTPCaller.MyHttpResponseHandler(clazz,url,callback),progressUIListener));
}

private Call postFile(String url, Header[] header,List<NameValuePair> form,HttpResponseHandler responseCallback,ProgressUIListener progressUIListener){
    try {
        MultipartBody.Builder builder = new MultipartBody.Builder();
        builder.setType(MultipartBody.FORM);
        MediaType mediaType = MediaType.parse("application/octet-stream");

        form.addAll(httpConfig.getCommonField());//添加公共字段

        for(int i=form.size()-1;i>=0;i--){
            NameValuePair item = form.get(i);
            if(item.isFile()){//上传文件
                File myFile = new File(item.getValue());
                if (myFile.exists()){
                    String fileName = Util.getFileName(item.getValue());
                    builder.addFormDataPart(item.getName(), fileName,RequestBody.create(mediaType, myFile));
                }
            }else{
                builder.addFormDataPart(item.getName(), item.getValue());
            }
        }

        RequestBody requestBody;
        if(progressUIListener==null){//不需要回调进度
            requestBody=builder.build();
        }else{//需要回调进度
            requestBody = ProgressHelper.withProgress(builder.build(),progressUIListener);
        }
        Request.Builder requestBuider = new Request.Builder();
        requestBuider.url(url);
        requestBuider.post(requestBody);
        return execute(requestBuider, header, responseCallback);
    } catch (Exception e) {
        e.printStackTrace();
        Log.e(httpConfig.getTagName(),e.toString());
        if (responseCallback != null)
            responseCallback.onFailure(-1, e.getMessage().getBytes());
    }
    return null;
}

private Call postFile(String url, Header[] header,String name,String filename,byte[] fileContent, HttpResponseHandler responseCallback,ProgressUIListener progressUIListener) {
    try {
        MultipartBody.Builder builder = new MultipartBody.Builder();
        builder.setType(MultipartBody.FORM);
        MediaType mediaType = MediaType.parse("application/octet-stream");
        builder.addFormDataPart(name,filename,RequestBody.create(mediaType, fileContent));

        List<NameValuePair> form = new ArrayList<>(2);
        form.addAll(httpConfig.getCommonField());//添加公共字段
        for (NameValuePair item : form) {
            builder.addFormDataPart(item.getName(),item.getValue());
        }

        RequestBody requestBody;
        if(progressUIListener==null){//不需要回调进度
            requestBody=builder.build();
        }else{//需要回调进度
            requestBody = ProgressHelper.withProgress(builder.build(),progressUIListener);
        }
        Request.Builder requestBuider = new Request.Builder();
        requestBuider.url(url);
        requestBuider.post(requestBody);
        return execute(requestBuider, header,responseCallback);
    } catch (Exception e) {
        if (httpConfig.isDebug()) {
            e.printStackTrace();
            Log.e(httpConfig.getTagName(), e.toString());
        }
        if (responseCallback != null)
            responseCallback.onFailure(-1, e.getMessage().getBytes());
    }
    return null;
}

增加下载文件的方法

get请求跟post请求都有了,上传文件就是post请求,继续增加downloadFile方法用来下载文件,通用的MyHttpResponseHandler不能处理回调,又增加了DownloadFileResponseHandler类处理下载回调。

public void downloadFile(String url, String saveFilePath, Header[] header, ProgressUIListener progressUIListener) {
        downloadFile(url,saveFilePath, header, progressUIListener,true);
    }

public void downloadFile(String url,String saveFilePath, Header[] header,ProgressUIListener progressUIListener,boolean autoCancel) {
    if (checkAgent()) {
        return;
    }
    add(url,downloadFileSendRequest(url,saveFilePath, header, progressUIListener),autoCancel);
}

private Call downloadFileSendRequest(String url, final String saveFilePath, Header[] header, final ProgressUIListener progressUIListener){
    Request.Builder builder = new Request.Builder();
    builder.url(url);
    builder.get();
    return execute(builder, header, new HTTPCaller.DownloadFileResponseHandler(url,saveFilePath,progressUIListener));
}

public class DownloadFileResponseHandler implements Callback {
    private String saveFilePath;
    private ProgressUIListener progressUIListener;
    private String url;

    public DownloadFileResponseHandler(String url,String saveFilePath,ProgressUIListener progressUIListener){
        this.url=url;
        this.saveFilePath=saveFilePath;
        this.progressUIListener=progressUIListener;
    }

    @Override
    public void onFailure(Call call, IOException e) {
        clear(url);
        try {
            printLog(url + " " + -1 + " " + new String(e.getMessage().getBytes(), "utf-8"));
        } catch (UnsupportedEncodingException encodingException) {
            encodingException.printStackTrace();
        }
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        printLog(url + " code:" + response.code());
        clear(url);

        ResponseBody responseBody = ProgressHelper.withProgress(response.body(),progressUIListener);
        BufferedSource source = responseBody.source();

        File outFile = new File(saveFilePath);
        outFile.delete();
        outFile.createNewFile();

        BufferedSink sink = Okio.buffer(Okio.sink(outFile));
        source.readAll(sink);
        sink.flush();
        source.close();
    }
}

RequestDataCallback接口,封装了三个方法,自己想要什么数据就去重写对应的方法。

public abstract class RequestDataCallback<T> {
    //返回json对象
    public void dataCallback(T obj) {
    }

    //返回http状态和json对象
    public void dataCallback(int status, T obj) {
        dataCallback(obj);
    }

    //返回http状态、json对象和http原始数据
    public void dataCallback(int status, T obj, byte[] body) {
        dataCallback(status, obj);
    }
}

还有Util公共类,有三个静态方法。

public class Util {
    /**
     * 获取文件名称
     * @param filename
     * @return
     */
    public static String getFileName(String filename){
        int start=filename.lastIndexOf("/");
//        int end=filename.lastIndexOf(".");
        if(start!=-1){
            return filename.substring(start+1,filename.length());
        }else{
            return null;
        }
    }

    /**
     * 拼接公共参数
     * @param url
     * @param commonField
     * @return
     */
    public static String getMosaicParameter(String url, List<NameValuePair> commonField){
        if (TextUtils.isEmpty(url))
            return "";
        if (url.contains("?")) {
            url = url + "&";
        } else {
            url = url + "?";
        }
        url += getCommonFieldString(commonField);
        return url;
    }

    private static String getCommonFieldString(List<NameValuePair> commonField){
        StringBuffer sb = new StringBuffer();
        try{
            int i=0;
            for (NameValuePair item:commonField) {
                if(i>0){
                    sb.append("&");
                }
                sb.append(item.getName());
                sb.append('=');
                sb.append(URLEncoder.encode(item.getValue(),"utf-8"));
                i++;
            }
        }catch (Exception e){

        }
        return  sb.toString();
    }
}

源码下载

OkHttp封装之后如何使用

      Android开发666 - 安卓开发技术分享
             扫描二维码加关注
Android开发666
上一篇下一篇

猜你喜欢

热点阅读