Glide本身功能十分强大,但是有一个缺点就是不支持监听下载进度回调。如果图片比较小的话,会很快加载出来,如果图片比较大的话,就会出现产品上的体验问题。这个时候监听下载进度就十分的有必要了。
更换HttpUrlConnection为okhttp
通过源码分析,我们知道Glide内部是通过HttpUrlConnection来进行底层网络请求的。但是HttpUrlConnection的可扩展性比较有限,我们在它的基础之上无法实现监听下载进度的功能,因此今天的第一个大动作就是要将Glide中的HTTP通讯组件替换成OkHttp。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59public class OkHttpFetcher implements DataFetcher<InputStream> { 
    private final OkHttpClient client; 
    private final GlideUrl url; 
    private InputStream stream; 
    private ResponseBody responseBody; 
    private volatile boolean isCancelled; 
    public OkHttpFetcher(OkHttpClient client, GlideUrl url) { 
        this.client = client; 
        this.url = url; 
    } 
     
    public InputStream loadData(Priority priority) throws Exception { 
        Request.Builder requestBuilder = new Request.Builder() 
                .url(url.toStringUrl()); 
        for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
            String key = headerEntry.getKey(); 
            requestBuilder.addHeader(key, headerEntry.getValue()); 
        } 
        Request request = requestBuilder.build(); 
        if (isCancelled) { 
            return null; 
        } 
        Response response = client.newCall(request).execute(); 
        responseBody = response.body(); 
        if (!response.isSuccessful() || responseBody == null) { 
            throw new IOException("Request failed with code: " + response.code());
        } 
        stream = ContentLengthInputStream.obtain(responseBody.byteStream(), 
                responseBody.contentLength()); 
        return stream; 
    } 
     
    public void cleanup() { 
        try { 
            if (stream != null) { 
                stream.close(); 
            } 
            if (responseBody != null) { 
                responseBody.close(); 
            } 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
     
    public String getId() { 
        return url.getCacheKey(); 
    } 
     
    public void cancel() { 
        isCancelled = true; 
    } 
}
然后新建一个OkHttpGlideUrlLoader类,并且实现ModelLoader1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41public class OkHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> { 
    private OkHttpClient okHttpClient; 
    public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> { 
        private OkHttpClient client; 
        public Factory() { 
        } 
        public Factory(OkHttpClient client) { 
            this.client = client; 
        } 
        private synchronized OkHttpClient getOkHttpClient() { 
            if (client == null) { 
                client = new OkHttpClient(); 
            } 
            return client; 
        } 
         
        public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new OkHttpGlideUrlLoader(getOkHttpClient()); 
        } 
         
        public void teardown() { 
        } 
    } 
    public OkHttpGlideUrlLoader(OkHttpClient client) { 
        this.okHttpClient = client; 
    } 
     
    public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) { 
        return new OkHttpFetcher(okHttpClient, model); 
    } 
}
新建一个MyGlideModule类并实现GlideModule接口,然后在registerComponents()方法中将我们刚刚创建的OkHttpGlideUrlLoader和OkHttpFetcher注册到Glide当中,将原来的HTTP通讯组件给替换掉。1
2
3
4
5
6
7
8
9
10public class MyGlideModule implements GlideModule { 
     
    public void applyOptions(Context context, GlideBuilder builder) { 
    } 
     
    public void registerComponents(Context context, Glide glide) { 
        glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory());
    } 
}
最后,为了让Glide能够识别我们自定义的MyGlideModule,还得在AndroidManifest.xml文件当中加入如下配置才行:1
2
3<meta-data 
    android:name="com.example.glideprogresstest.MyGlideModule" 
    android:value="GlideModule" />
实现下载进度监听
新建一个监听进度的接口1
2
3public interface ProgressListener {
    void onProgress(int progress);
}
实现okhttp网络拦截器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class ProgressInterceptor implements Interceptor { 
    static final Map<String, ProgressListener> LISTENER_MAP = new HashMap<>();
    public static void addListener(String url, ProgressListener listener) {
        LISTENER_MAP.put(url, listener); 
    } 
    public static void removeListener(String url) { 
        LISTENER_MAP.remove(url); 
    } 
     
    public Response intercept(Chain chain) throws IOException { 
        Request request = chain.request(); 
        Response response = chain.proceed(request); 
        return response; 
    } 
}
在MyGlideModule中的registerComponents方法中,添加okhttp拦截器。1
2
3
4
5
6
7 
public void registerComponents(Context context, Glide glide) { 
    OkHttpClient.Builder builder = new OkHttpClient.Builder(); 
    builder.addInterceptor(new ProgressInterceptor()); 
    OkHttpClient okHttpClient = builder.build(); 
    glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory(okHttpClient));
}
计算下载进度
下载进度的详细计算规则如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66public class ProgressResponseBody extends ResponseBody {
    private static final String TAG = "ProgressResponseBody";
    private BufferedSource bufferedSource;
    private ResponseBody responseBody;
    private ProgressListener listener;
    public ProgressResponseBody(String url, ResponseBody responseBody) {
        this.responseBody = responseBody;
        listener = ProgressInterceptor.LISTENER_MAP.get(url);
    }
    
    public MediaType contentType() {
        return responseBody.contentType();
    }
    
    public long contentLength() {
        return responseBody.contentLength();
    }
     
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(new ProgressSource(responseBody.source()));
        }
        return bufferedSource;
    }
    private class ProgressSource extends ForwardingSource {
        long totalBytesRead = 0;
        int currentProgress;
        ProgressSource(Source source) {
            super(source);
        }
         
        public long read(Buffer sink, long byteCount) throws IOException {
            long bytesRead = super.read(sink, byteCount);
            long fullLength = responseBody.contentLength();
            if (bytesRead == -1) {
                totalBytesRead = fullLength;
            } else {
                totalBytesRead += bytesRead;
            }
            int progress = (int) (100f * totalBytesRead / fullLength);
            Log.d(TAG, "download progress is " + progress);
            if (listener != null && progress != currentProgress) {
                listener.onProgress(progress);
            }
            if (listener != null && totalBytesRead == fullLength) {
                listener = null;
            }
            currentProgress = progress;
            return bytesRead;
        }
    }
}
使用计算的下载进度
通过网络拦截器,拦截响应体数据流,来实现对进度的监听。1
2
3
4
5
6
7
8
9
10
11
12
13public class ProgressInterceptor implements Interceptor { 
    ... 
     
    public Response intercept(Chain chain) throws IOException { 
        Request request = chain.request(); 
        Response response = chain.proceed(request); 
        String url = request.url().toString(); 
        ResponseBody body = response.body(); 
        Response newResponse = response.newBuilder().body(new ProgressResponseBody(url, body)).build();
        return newResponse; 
    } 
}
加载图片
界面上显示加载图片进度。并通过ProgressInterceptor.addListener方法添加监听。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public void loadImage(View view) {
    ProgressInterceptor.addListener(url, new ProgressListener() {
        
        public void onProgress(int progress) {
            progressDialog.setProgress(progress);
        }
    });
    Glide.with(this)
            .load(url)
            .diskCacheStrategy(DiskCacheStrategy.NONE)
            .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
            .into(new GlideDrawableImageViewTarget(image) {
                
                public void onLoadStarted(Drawable placeholder) {
                    super.onLoadStarted(placeholder);
                    progressDialog.show();
                }
                 
                public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
                    super.onResourceReady(resource, animation);
                    progressDialog.dismiss();
                    ProgressInterceptor.removeListener(url);
                }
            });
    }