OkHttp优雅的实现下载监听
(ps:很久很久没有发什么博文了,打字都要锈了,请允许我在这里水一波~~)
我们都知道okhttp的运转原理,通过interceptor拦截器一层一层嵌套执行。要实现下载监听,通过以下代码
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
return response.newBuilder().body(new ProcessableResponse(response.body(), progressListeners)).build();
}
复制代码
okhttp添加拦截器,传入带有监听的ResponseBody实现下载监听。
↓
↓
问题
既然核心是传入带有监听的ResponseBody,那么能不能在enqueue(Callback callback)回调里传入带有监听的responseBody呢(也就是以下代码)
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
response.newBuilder().body(new ProcessableResponse(response.body(), progressListeners)).build();
}
})
复制代码
行吗[假装] ???
当然不行[假装豁然开朗]~~
从这张执行图很清晰的可以看到,HttpLoggingInterceptor已经执行了,而这个拦截器其实是我最先设置进去的,也就是说拦截器递归执行返回的时候已经走完了所有的拦截器,自然我们设置的监听的拦截器也被执行了,最终卡在了Okio的read方法里~~结论~~
也就是说,在执行call的回调之前,Okio已经在读取数据了,如果我们想要对下载进行监听,就必须在读取数据之前,把默认的responseBody包装成我们能监听数据读取的responseBody。
重点来了
既然要用拦截器来实现下载监听,一般是要在okhttp初始化阶段,而我们的下载监听实际上就只有下载数据的时候需要用到,也就是说我想在要下载的时候才设置我们的下载监听,下载完了,把它移除,但是↓
OkHttpClient(Builder builder) {
...
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
...
}
复制代码
上面代码可以看到,okhttp在创建时就把拦截器设置成了不可修改的了,也就是说,后续我们就不能动态的添加拦截器了~~
解决办法
根据okhttp拦截器嵌套执行的逻辑,我们也可以模拟一个自己的拦截器的嵌套执行,然后和默认的拦截器串联在一起,来实现我们想要的逻辑。
/**
* 网络连接代理拦截器
* Created by yan on 8/20/18.
*/
class ProxyNetInterceptor implements Interceptor {
private List<Interceptor> realInterceptors = new ArrayList<>();
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
if (!realInterceptors.isEmpty()) {
TempInterceptorChain tempInterceptorChain = new TempInterceptorChain(chain, realInterceptors, 0);
return tempInterceptorChain.proceed(chain.request());
}
return chain.proceed(chain.request());
}
public void addRealInterceptor(Interceptor realInterceptor) {
if (!realInterceptors.contains(realInterceptor)) {
realInterceptors.add(realInterceptor);
}
}
public void removeNetInterceptor(Interceptor netInterceptor) {
realInterceptors.remove(netInterceptor);
}
private static class TempInterceptorChain implements Interceptor.Chain {
private Chain realInterceptorChain;
private List<Interceptor> realInterceptors;
private int index;
private TempInterceptorChain(Chain realInterceptorChain, List<Interceptor> realInterceptors, int index) {
this.realInterceptorChain = realInterceptorChain;
this.realInterceptors = realInterceptors;
this.index = index;
}
@Override
public Request request() {
return realInterceptorChain.request();
}
@Override
public Response proceed(@NonNull Request request) throws IOException {
final Chain next;
if (index + 1 >= realInterceptors.size()) {// 把代理拦截器与原本的拦截器相连接
next = realInterceptorChain;
} else {
next = new TempInterceptorChain(realInterceptorChain, realInterceptors, index + 1);
}
Interceptor interceptor = realInterceptors.get(index);
return interceptor.intercept(next);// 内部继续执行process 形成递归嵌套
}
@Override
public Connection connection() {
return realInterceptorChain.connection();
}
}
}
复制代码
以上就是我们的代理拦截器,可以我们传入的拦截器集合(realInterceptors),嵌套进默认的拦截器执行集合里,这样也就可以实现对拦截器的动态管理了~~
结束语
既然我们使用了okhttp,大概率也是要用到拦截器(除了下载监听,还有统一header设置,或者统一的错误码判断等),如果你的拦截器只有一段代码用到,其他地方不想用,可以试试这样的代理方式,方便动态管理。