OkHttp優雅的實現下載監聽

北緯34點8度發表於2018-08-21

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();
   }
})
複製程式碼

行嗎[假裝] ???

當然不行[假裝豁然開朗]~~

OkHttp優雅的實現下載監聽
從這張執行圖很清晰的可以看到,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設定,或者統一的錯誤碼判斷等),如果你的攔截器只有一段程式碼用到,其他地方不想用,可以試試這樣的代理方式,方便動態管理。

相關文章