在上一篇文章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;
}