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設定,或者統一的錯誤碼判斷等),如果你的攔截器只有一段程式碼用到,其他地方不想用,可以試試這樣的代理方式,方便動態管理。