Glide源码解析(五):添加进度

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
59
public 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;
}

@Override
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;
}

@Override
public void cleanup() {
try {
if (stream != null) {
stream.close();
}
if (responseBody != null) {
responseBody.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public String getId() {
return url.getCacheKey();
}

@Override
public void cancel() {
isCancelled = true;
}
}

然后新建一个OkHttpGlideUrlLoader类,并且实现ModelLoader

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
public 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;
}

@Override
public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
return new OkHttpGlideUrlLoader(getOkHttpClient());
}

@Override
public void teardown() {
}
}

public OkHttpGlideUrlLoader(OkHttpClient client) {
this.okHttpClient = client;
}

@Override
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
10
public class MyGlideModule implements GlideModule { 
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}

@Override
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
3
public 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
20
public 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);
}

@Override
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
@Override 
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
66
public 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);
}

@Override
public MediaType contentType() {
return responseBody.contentType();
}

@Override
public long contentLength() {
return responseBody.contentLength();
}

@Override
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);
}

@Override
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
13
public class ProgressInterceptor implements Interceptor { 
...
@Override
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
26
public void loadImage(View view) {
ProgressInterceptor.addListener(url, new ProgressListener() {
@Override
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) {
@Override
public void onLoadStarted(Drawable placeholder) {
super.onLoadStarted(placeholder);
progressDialog.show();
}

@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
super.onResourceReady(resource, animation);
progressDialog.dismiss();
ProgressInterceptor.removeListener(url);
}
});
}

参考文章

您的支持是我原创的动力