Okhttp3的封装2

在上一篇文章Okhttp3的封装的基础上做了如下修改:

1.对请求参数key/value进行了封装,支持上传文件时传递不同的name, 支持一个key,多个value的形式提交参数.

2.对请求方法的调用进行了封装,使用一个RequestManager的类来对方法参数进行封装,支持链式调用, 废弃了通过方法重载的方式设置多种请求的形式.
为了减少链式调用的书写长度,RequestManager的默认请求策略是NETWORK_FIRST, 请求方法是GET,响应类型为JSON,回调方式为异步回调, 如果符合这些设置的,可以不用手动赋值.

另外,RequestManager支持批量/单个设置请求头/参数, 也支持批量/单个添加文件

3.支持json的提交

4.修复了清理缓存的bug

5.解决了服务器没有设置Cache-Control响应头时,App采用network_first策略时, 断网情况拿不到缓存的问题.

使用方式

在Application中初始化

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
       
        new HttpManager.Builder(this)
                .setConnectTimeout(5000)
                .setReadTimeout(5000)
                .setWriteTimeout(5000)
                .build();
    }
}

get请求调用方式

public void get_2(View view) {
   
    Map<String, String> bodyMap = new HashMap();
    bodyMap.put("username", "胜哥");
    bodyMap.put("age", "20");
    
    Map<String, String> headerMap = new HashMap<>();
    headerMap.put("Cookie", "session=123123");
    new HttpManager.RequestManager()
            .setUrl("http://192.168.30.217/shopping/test/demo1")
            .setBodyMap(bodyMap) //支持批量设置参数
            .addParams("hobby", "吃") //支持单个设置参数
            .addParams("hobby", "喝")
            .addParams("hobby", "玩")
            .setHeaderMap(headerMap) //支持批量设置请求头
            .addHeader("test", "testHeader")//支持单个设置请求头
            .setCallback(new HttpManager.ResultCallback() {
                @Override
                public void onFailure(Exception e) {
                    mInfoTv.setText("onFailure:" + e.getMessage());
                }
                @Override
                public void onSuccess(Object o, HttpManager.Result result) throws Exception {
                    boolean fromCache = result.getResultType() == HttpManager.Result.TYPE_CACHE;
                    mInfoTv.setText(fromCache ? "来自缓存" : "来自网络");
                    mInfoTv.append("\r\n" + result.getJson().toString());
                }
            }).execute();
}

post提交调用方式

public void post_2(View view) {
   
    Map<String, String> headerMap = new HashMap<>();
    headerMap.put("Cookie", "session=123123");
    Map<String, String> bodyMap = new HashMap();
    bodyMap.put("username", "胜哥");
    bodyMap.put("age", "20");
    bodyMap.put("hobby", "吃,喝,玩");
    String url = "http://192.168.30.217/shopping/test/demo1";
    new HttpManager.RequestManager()
            .setMethod(HttpManager.RequestMethod.POST)
            .setUrl(url)
            .setBodyMap(bodyMap)
            .setHeaderMap(headerMap)
            .setCallback(new HttpManager.ResultCallback() {
                @Override
                public void onFailure(Exception e) {
                    mInfoTv.setText("onFailure:" + e.getMessage());
                }
                @Override
                public Object doInBackground(HttpManager.Result result) throws Exception {
                    return result.getJson().optJSONObject("user");
                }
                @Override
                public void onSuccess(Object o, HttpManager.Result result) throws Exception {
                    mInfoTv.setText("o=" + o + " result:" + result.getJson());
                }
            }).execute();
}

下载文件方式

public void download_2(View view) {
  
    final String filename = "c.mp4";
    String url = "http://192.168.30.217/shopping/test/download?fileName=" + filename;
    new HttpManager.RequestManager()
            .setUrl(url)
            .setRequestType(HttpManager.RequestType.FORCE_NETWORK)
            .setResponseType(HttpManager.ResponseType.IO)
            .setProgress(new HttpManager.Progress() {
                @Override
                public void onProgress(long currByte, long totalByte, int progress) {
                    System.out.println("currByte:" + currByte + " totalByte:" + totalByte
                    mProgressBar.setMax(100);
                    mProgressBar.setProgress(progress);
                    mProgressTv.setText(progress + "%");
                }
            })
            .setCallback(new HttpManager.ResultCallback() {
                @Override
                public void onFailure(Exception e) {
                    mInfoTv.setText("下载失败:" + e);
                }
                @Override
                public Object doInBackground(HttpManager.Result result) throws Exception 
                    File file = makeFile(filename);
                    //相当于bufferInputStream
                    BufferedSource source = Okio.buffer(Okio.source(result.getInputStream
                    //相当于bufferOutputStream
                    BufferedSink sink = Okio.buffer(Okio.sink(file));
                    //拷贝流
                    source.readAll(sink);
                    source.close();
                    sink.close();
                    return file.getAbsolutePath();
                }
                @Override
                public void onSuccess(Object o, HttpManager.Result result) throws Excepti
                    mInfoTv.setText("下载成功 path=" + o);
                }
            })
            .execute();
}

上传文件方式

public void upload_2(View view) {
   
    Map<String, String> headerMap = new HashMap<>();
    headerMap.put("Cookie", "session=123123");
    Map<String, String> bodyMap = new HashMap();
    bodyMap.put("username", "胜哥");
    bodyMap.put("age", "20");
    bodyMap.put("date", new SimpleDateFormat("HH:mm:ss").format(new Date()));
    File f1 = getAssetFile("c.mp4");
    File f2 = getAssetFile("timg.gif");
    List<File> file = new ArrayList<>();
    file.add(f1);
    file.add(f2);
    String url = "http://192.168.30.217/shopping/test/demo1";
    new HttpManager.RequestManager(url, "mulitupload", file, headerMap, bodyMap)
            .addParams("hobby", "吃")
            .addParams("hobby", "喝")
            .addParams("hobby", "玩")
            //.setFile("mulitupload",file) 也可以不用构造方法的方式批量添加文件
            //.addFile("mulitupload",f1) //单个添加文件也可以
            .setProgress(new HttpManager.Progress() {
                @Override
                public void onProgress(long currByte, long totalByte, int progress) {
                    System.out.println("currByte:" + currByte + " totalByte:" + totalByte +
                            " progress:" + progress);
                    mProgressBar.setMax(100);
                    mProgressBar.setProgress(progress);
                    mProgressTv.setText(progress + "%");
                }
            })
            .setCallback(new HttpManager.ResultCallback() {
                @Override
                public void onFailure(Exception e) {
                    mInfoTv.setText("上传失败:" + e.getMessage());
                }
                @Override
                public void onSuccess(Object o, HttpManager.Result result) throws Exception {
                    Log.e("cys", result.getJson().toString());
                    mInfoTv.setText("上传成功:" + result.getJson());
                }
            })
            .execute();
}

提交json数据

public void uploadJson(View view) {
   
    JSONObject jsonObject = null;
    try {
        jsonObject = new JSONObject();
        jsonObject.put("name", "胜哥");
        jsonObject.put("age", 20);
    } catch (JSONException e) {
        e.printStackTrace();
    }
    String url = "http://192.168.30.217/shopping/test/post";
    new HttpManager.RequestManager(url, jsonObject, null)
            .setCallback(new HttpManager.ResultCallback() {
                @Override
                public void onFailure(Exception e) {
                    mInfoTv.setText("onFailure:" + e.getMessage());
                }
                @Override
                public void onSuccess(Object o, HttpManager.Result result) {
                    Log.e("cys", result.getJson().toString());
                    mInfoTv.setText(result.getJson().toString());
                }
            })
            .execute();
}

同步get请求方式, 其他同步方式可参考get

public void get_3(View view){
    new Thread(new Runnable() {
        @Override
        public void run() {
            String url = "http://192.168.30.217/shopping/test/get?username=胜哥&age=22";
            HttpManager.Result result = new HttpManager.RequestManager()
                    .setUrl(url)
                    .setAsync(false)
                    .execute();
            if(null !=result){
                Log.e("cys", result.getJson().toString());
            }else{
            	Log.e("cys", "onFailure");
            }
        }
    }).start();
}

HttpManager的实现

弃用上一篇文章的提交方式,由于代码量比较多,这里介绍几个核心的类和方法, 文章末尾会给出下载链接

请求参数的封装的内部类

public static class Params {
    private String key;
    private Object value;
    public Params(String key, String value) {
        this.key = key;
        this.value = value;
    }
    public Params(String key, File value) {
        this.key = key;
        this.value = value;
    }
    public String getKey() {
        return key;
    }
    public Object getValue() {
        return value;
    }
}

RequestManager内部类

 public static class RequestManager {
     private String url;// 请求url
     private JSONObject json;//上传的json
     private Map<String, String> headerMap = new HashMap<>();//请求头
     private List<Params> bodyParams = new ArrayList<>();//   请求参数
     private RequestMethod method = RequestMethod.GET;//  请求方式,默认get
     private RequestType requestType = RequestType.NETWORK_FIRST;// 请求类型,默认网络优先
     private ResponseType responseType = ResponseType.JSON;//响应数据类型,默认json
     private Progress progress;//请求/响应进度,用于上传/下载文件时的进度监听
     private ResultCallback callback;// 回调结果
     private boolean async = true;//是否异步,默认同步
     private Request.Builder okRequestBuilder;
     public RequestManager() {
     }
     //普通get/post请求可用这个
     public RequestManager(String url, Map<String, String> headerMap, Map<String, String> bodyMap) {
         this.url = url;
         this.headerMap = headerMap;
         this.setBodyMap(bodyMap);
     }
     //文件上可用这个
     public RequestManager(String url, String name, List<File> file, Map<String, String> headerMap, Map<String, String> bodyMap) {
         this.url = url;
         this.headerMap = headerMap;
         this.method = HttpManager.RequestMethod.POST;
         this.requestType = RequestType.FORCE_NETWORK;
         this.setFile(name, file);
         this.setBodyMap(bodyMap);
     }
     //json提交可用这个
     public RequestManager(String url, JSONObject json, Map<String, String> headerMap) {
         this.url = url;
         this.json = json;
         this.headerMap = headerMap;
         this.method = HttpManager.RequestMethod.POST;
         this.requestType = RequestType.FORCE_NETWORK;
     }
     public RequestManager setUrl(String url) {
         this.url = url;
         return this;
     }
     //添加文件参数
     public RequestManager addFile(String name, File file) {
         this.bodyParams.add(new Params(name, file));
         return this;
     }
     //添加普通参数
     public RequestManager addParams(String key, String value) {
         this.bodyParams.add(new Params(key, value));
         return this;
     }
     //添加请求头
     public RequestManager addHeader(String key, String value) {
         this.headerMap.put(key, value);
         return this;
     }
     //添加json
     public RequestManager setJson(JSONObject json) {
         this.json = json;
         return this;
     }
     //批量添加请求头
     public RequestManager setHeaderMap(Map<String, String> headerMap) {
         this.headerMap = headerMap;
         return this;
     }
     //批量添加请求参数
     public RequestManager setBodyMap(Map<String, String> bodyMap) {
         if (null != bodyMap) {
             for (String key : bodyMap.keySet()) {
                 this.addParams(key, bodyMap.get(key));
             }
         }
         return this;
     }
     //批量添加文件
     public RequestManager setFile(String name, List<File> file) {
         if (null != file) {
             for (File f : file) {
                 this.addFile(name, f); //多个文件使用同一个name属性
             }
         }
         return this;
     }
     //请求方法设置
     public RequestManager setMethod(RequestMethod method) {
         this.method = method;
         return this;
     }
     //请求类型设置
     public RequestManager setRequestType(RequestType requestType) {
         this.requestType = requestType;
         return this;
     }
     //响应结果类型设置
     public RequestManager setResponseType(ResponseType responseType) {
         this.responseType = responseType;
         return this;
     }
     //上传/下载进度监听设置
     public RequestManager setProgress(Progress progress) {
         this.progress = progress;
         return this;
     }
     //结果回调设置
     public RequestManager setCallback(ResultCallback callback) {
         this.callback = callback;
         return this;
     }
     //是否异步请求
     public RequestManager setAsync(boolean async) {
         this.async = async;
         return this;
     }
     
     //私有方法,仅供HttpManager使用
     private RequestManager setOkRequestBuilder(Request.Builder okRequestBuilder) {
         this.okRequestBuilder = okRequestBuilder;
         return this;
     }
     //私有方法,仅供HttpManager使用
     private boolean hasFile() {
         for (Params params : bodyParams) {
             Object value = params.getValue();
             if (value instanceof File) {
                 return true;
             }
         }
         return false;
     }
    
     //快捷执行请求入口
     public Result execute() {
         return HttpManager.getInstance().execute(this);
     }
 }

初始化请求参数的核心代码

private Request.Builder initRequest(RequestManager request) {
    Map<String, String> headerMap = request.headerMap;
    List<Params> bodyParams = request.bodyParams;
    JSONObject json = request.json;
    String url = request.url;
    RequestMethod method = request.method;
    Progress uploadProgress = request.progress;
    //构建请求
    Request.Builder requestBuilder = new Request.Builder();
    if (method == RequestMethod.GET) {
        url = attachGetParams2(url, bodyParams);//get请求参数拼接
        requestBuilder.get();
    } else {
        //添加post请求参数封装,分为文件表单,普通表单,json提交3种
        if (request.hasFile()) {
            //1.支持文件上传的表单的Content-Type形如  multipart/form-data; boundary=f7345bb5-ef5e-4da1-9cef-7f259d54c229
            MultipartBody.Builder form = new MultipartBody.Builder();
            form.setType(MultipartBody.FORM);//强制为multipart/form-data
            for (Params params : bodyParams) {
                String key = params.getKey();
                Object value = params.getValue();
                if (value instanceof File) {
                    //添加表单的文件
                    File f = (File) value;
                    MediaType mediaType = MediaType.parse("text/x-markdown;charset=utf-8");
                    RequestBody fileBody = RequestBody.create(mediaType, f);
                    form.addFormDataPart(key, f.getName(), fileBody);//上传文件
                } else {
                    //添加表单的普通参数
                    form.addFormDataPart(key, (String) value);//okhttp3对普通参数没有设置Content-Type
                }
            }
            //总文件上传进度
            requestBuilder.post(new ProgressRequestBody(form.build(), uploadProgress));//请求体中包含了文件和普通参数
        } else {
            //2.仅支持普通参数提交的表的Content-Type为 application/x-www-form-urlencoded
            if (!bodyParams.isEmpty()) {
                FormBody.Builder form = new FormBody.Builder();
                for (Params params : bodyParams) {
                    form.add(params.getKey(), (String) params.getValue());
                }
                requestBuilder.post(form.build());//FormBody提交参数是以键值对key1=value1&key2=value2的形式写在请求体中的
            }
            //3.json数据提交封装的Content-Type为application/json; charset=utf-8
            else if (null != json) {
                RequestBody jsonBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json.toString())
                requestBuilder.post(jsonBody); //json数据提交是直接将json字符串写在请求体中
            }
        }
    }
    //请求url和tag设置
    requestBuilder.url(url).tag(url);
    //请求头设置
    if (null != headerMap) {
        for (String key : headerMap.keySet()) {
            requestBuilder.header(key, headerMap.get(key));
        }
    }
    return requestBuilder;
}

执行请求的核心代码

private Result doRequest(RequestManager req) {
    final RequestType requestType = req.requestType;
    final Request.Builder requestBuilder = req.okRequestBuilder;
    final Progress downloadProgress = req.hasFile() ? null : req.progress; //文件上传不需要监听响应进度
    final ResultCallback callback = req.callback;
    final ResponseType responseType = req.responseType;
    boolean async = req.async;
    //设置缓存类型
    switch (requestType) {
        case FORCE_CACHE:
            requestBuilder.cacheControl(CacheControl.FORCE_CACHE);
            break;
        case FORCE_NETWORK:
        case NETWORK_FIRST:
            requestBuilder.cacheControl(CacheControl.FORCE_NETWORK);
            break;
        case DEFAULT: //默认不设置
        default:
            break;
    }
    //如果缓存被删除后,需要重新设置缓存
    if (mClient.cache().isClosed()) {
        mClient = mClient.newBuilder().cache(cache).build();
    }
    //下载任务,单独一个client去处理
    OkHttpClient downloadClient = null;
    if (null != downloadProgress) {
        downloadClient = mClient.newBuilder().addNetworkInterceptor(new DownloadInterceptor(downloadProgress)).build();
    }
    final OkHttpClient client = null != downloadProgress ? downloadClient : mClient;
    //异步请求回调
    Callback asynCallback = new Callback() {
        int count;//请求失败的回调次数
        @Override
        public void onFailure(Call call, IOException e) {
            if (count == 0) {
                //仅针对NETWORK_FIRST策略,网络请求失败,则请求缓存
                if (requestType == RequestType.NETWORK_FIRST) {
                    count++;
                    Request request = requestBuilder.cacheControl(CacheControl.FORCE_CACHE).build();
                    client.newCall(request).enqueue(this);
                } else {
                    if (null != callback)
                        callback.onError(e);
                }
            } else {
                //第二次请求缓存也失败,这直接回调
                if (null != callback)
                    callback.onError(e);
            }
        }
        @Override
        public void onResponse(Call call, Response response) {
            if (null != response && response.isSuccessful() && response.body() != null) {
                if (null != callback)
                    callback.onResponse(responseType, response);
            } else {
                if (null != callback)
                    callback.onError(new Exception("Response is null"));
            }
        }
    };
    if (async) { //异步请求
        client.newCall(requestBuilder.build()).enqueue(asynCallback);
    } else {
        //同步请求
        boolean hasTry = false;
        try {
            Response response = client.newCall(requestBuilder.build()).execute();
            if (null == response || !response.isSuccessful() || response.body() == null) {
                if (requestType == RequestType.NETWORK_FIRST) {
                    //网络优先失败,则请求缓存
                    hasTry = true;
                    Request request = requestBuilder.cacheControl(CacheControl.FORCE_CACHE).build();
                    response = client.newCall(request).execute();
                }
            }
            return new Result(responseType, response);
        } catch (Exception e) {
            if (!hasTry && requestType == RequestType.NETWORK_FIRST) { //首次请求网络异常, 再次尝试请求缓存
                try {
                    Request request = requestBuilder.cacheControl(CacheControl.FORCE_CACHE).build();
                    Response response = client.newCall(request).execute();
                    return new Result(responseType, response);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
    return null;
}

处理离线缓存的Interceptor

有些人可能会好奇,Okhttp不是只带缓存功能吗, 为什么还需要自己处理呢?

这是因为OKhttp的缓存策略是会受接口的Cache-Control控制的,
如果接口没有设置Cache-Control响应头,或者响应头为Cache-Control: no-cache 这种情况, 那么App断网后,Okhttp是不会读缓存的.具体策略可以看我的第一篇文章的介绍.

那么如何解决App的缓存问题呢, 即保留Okhttp的原始策略, 又能满足App自身的业务需求,实现有网络则请求网络, 失败再读取缓存的功能.

这里就要用到网络拦截器来实现,通过OkhttpClient.Builder 的addNetworkInterceptor方法添加, 该拦截器是在网络请求后执行的,具体可以看RealCall的源码.

Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  interceptors.add(retryAndFollowUpInterceptor);
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));
  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  return chain.proceed(originalRequest);
}

可以发现networkInterceptor是在CallServerInterceptor和ConnectInterceptor之间的. 从上到下是请求发起的拦截链, 从下往上是响应的拦截链. 所以networkInterceptor是可以拿到响应的response来处理的.

既然可以拿到response, 那么我们就可以给它添加响应头,如果判断到接口的响应头中没有设置Cache-Control,那么我们就给它添加一个缓存为7天的Cache-Control. 具体代码如下:

//当接口没有提供cache-control时,需要App对get请求的缓存做处理
private class CacheSaveInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
    	//原始request
        Request request = chain.request();
        //获取原始Response
        Response originalResponse = chain.proceed(request);
        //获取接口返回的cache-control字段
        String cacheControl = originalResponse.header("Cache-Control");
        Response.Builder responseBuilder = originalResponse.newBuilder();
        if (request.method().equalsIgnoreCase("get")
                && request.cacheControl().toString().equals(CacheControl.FORCE_NETWORK.toString())
                && (cacheControl == null || cacheControl.equals("no-cache"))) {
            //如果接口无启用Cache-Control,但是App本地需要缓存支持(即网络请求失败的时候需要返回缓存),
            //所以当接口请求成功在时需要保存一下缓存,通过下面的方式来让okhttp保存缓存.
            //即手动修改Response的header,设置一个Cache-Control,这里设置为7周,这样okhttp本地就会缓存该接口数据为7天时间.
            long time = 7 * 24 * 3600L;
            responseBuilder.header("Cache-Control", "public, max-age=" + time)
                    .removeHeader("Pragma");//pragma是http1.0的且优先级高于Cache-Control,避免影响,这里将其删除
        }
        //返回新的Response
        return responseBuilder.build();
    }
}

然后在HttpManager的构造方法中给OkHttpClient.Builder设置networkInterceptor 即可.

缓存查询/删除/获取的静态方法如下:

/**
 * 清空缓存
 *
 * @param context
 */
public static void deleteCache(Context context) {
    if (cache != null) {
        try {
            cache.delete();
            cache = new Cache(createCacheDir(context), CACHE_SIZE);
            cache.initialize();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 * 根据url获取缓存文件
 *
 * @param url
 * @return
 */
public static File getCache(String url) {
    if (TextUtils.isEmpty(url)) throw new NullPointerException("url == null");
    return new File(cache.directory(), Cache.key(HttpUrl.parse(url)) + ".1");
}
/**
 * 判断是否有缓存
 *
 * @param url
 * @return
 */
public static boolean hasCache(String url) {
    if (TextUtils.isEmpty(url)) throw new NullPointerException("url == null");
    boolean hasCache = false;
    if (cache != null) {
        File result = new File(cache.directory(), Cache.key(HttpUrl.parse(url)) + ".1");
        hasCache = result.exists();
    }
    return hasCache;
}

Demo下载

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值