对于项目开发中,如果遇到加载大图,重网络上下载并在本地显示这个大图,会稍微耗时一点,这时,就会想给出一个下载进度的显示,这样用户体验会更好,但是Glide是没有提供下载进度监听的api的,为了解决这个问题,这篇文章的介绍将基于上篇文章 中,对Glide的自定义模块时,使用Okhttp替换了默认的
HttpUrlConnection作为网络情况的组件。基于这个OkHttp,可以通过添加拦截器的方式来计算下载进度,然后通过设置一个监听来监听这个下载进度,这样就达到了监听下载进度的目的。话不多说,上代码:
首先添加okhttp依赖(记得添加网络权限)
// Okhttp库
compile 'com.squareup.okhttp3:okhttp:3.1.2'
下面是自定义的GlideModule:
import android.content.Context;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.load.engine.cache.ExternalCacheDiskCacheFactory;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.GlideModule;
import java.io.InputStream;
class MyGlideModule implements GlideModule {
private final int DISK_CACHE_SIZE = 1024 * 1024 * 100;//缓存是100Mb
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, DISK_CACHE_SIZE));
}
@Override
public void registerComponents(Context context, Glide glide) {
glide.register(GlideUrl.class, InputStream.class, new MyOkhttpGlideUrlLoader.Factory());
}
}
下面是MyOkhttpGlideUrlLoader类的代码实现:
import android.content.Context;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GenericLoaderFactory;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import java.io.InputStream;
import okhttp3.OkHttpClient;
public class MyOkhttpGlideUrlLoader implements ModelLoader {
private OkHttpClient mOkHttpClient;
/**
* The default factory for {@link com.bumptech.glide.load.model.stream.HttpUrlGlideUrlLoader}s.
*/
public static class Factory implements ModelLoaderFactory {
private OkHttpClient mClient;
public Factory(){
}
@Override
public ModelLoader build(Context context, GenericLoaderFactory factories) {
return new MyOkhttpGlideUrlLoader(getOkHttpClient());
}
@Override
public void teardown() {
// Do nothing.
}
private synchronized OkHttpClient getOkHttpClient(){
if(null == mClient){
OkHttpClient.Builder builder = new OkHttpClient.Builder();
//添加拦截器
builder.addInterceptor(new MyProgressInterceptor());
mClient = builder.build();
}
return mClient;
}
}
public MyOkhttpGlideUrlLoader(OkHttpClient okHttpClient) {
this.mOkHttpClient = okHttpClient;
}
@Override
public DataFetcher getResourceFetcher(GlideUrl model, int width, int height) {
return new MyOkhttpUrlFetcher(model,mOkHttpClient);
}
}
在这个自定义的MyOkhttpGlideUrlLoader类中,创建OkHttpClient对象时,添加了一个MyProgressInterceptor的拦截器,对网络请求进行拦截。下面看看具体的实现:
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class MyProgressInterceptor implements Interceptor {
static final Map listeners = new HashMap();
static void addListener(String url ,MyProgressListener progressListener){
listeners.put(url,progressListener);
}
static void removeListener(String url){
listeners.remove(url);
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
ResponseBody body = response.body();
MyProgressBody myProgressBody = new MyProgressBody(request.url().toString(),body);
Response newResponse = response.newBuilder().body(myProgressBody).build();
return newResponse;
}
}
在拦截器中,定义了两个静态方法,分别是添加监听和移除监听的方法。添加监听的方法是根据不同图片的url来作为key,来存储每个不同的图片的监听的。这样处理的好处是,可以对不同的图片的监听做各自的处理。下面是监听的接口实现:
public interface MyProgressListener {
void onProgress(int progress);
}
很简单,就是一个接口。
intercept方法中,对返回的结果进行了包装,创建了一个MyProgressBody类,这个是继承自ResponseBody的,下面是具体实现:
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.BufferedSource;
import okio.Okio;
public class MyProgressBody extends ResponseBody {
private final String mUrl;
private final ResponseBody mResponseBody;
private BufferedSource bufferedSource;
private MyProgressSource myProgressSource;
public MyProgressBody(String url, ResponseBody responseBody){
this.mUrl = url;
this.mResponseBody = responseBody;
}
@Override
public MediaType contentType() {
return (null != mResponseBody)?mResponseBody.contentType():null;
}
@Override
public long contentLength() {
return (null != mResponseBody)?mResponseBody.contentLength():0;
}
@Override
public BufferedSource source() {
if(null == bufferedSource){
myProgressSource = new MyProgressSource(mResponseBody.source(),mUrl, mResponseBody);
bufferedSource = Okio.buffer(myProgressSource);
}
return bufferedSource;
}
}
这个类中, 要重写父类的三个方式,其中contentType方法中, 直接返回传入的ResponseBody实例的contentType()方法的值作为返回结果即可。contentLength()方法也是一样的处理,也是调用传入的ResponseBody的实例对象的contentLength()的值作为返回结果。最关键的是source()方法,这个方法中,才是处理下载的进度的关键方法。这个方法中,传入了一个继承自ForwardingSource的类MyProgressSource。这个MyProgressSource类中,完成了具体的下载进度的计算。
import java.io.IOException;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.ForwardingSource;
import okio.Source;
import test.cn.example.com.util.LogUtil;
public class MyProgressSource extends ForwardingSource {
private ResponseBody mResponseBody;
private long totalReadLength;
private long fullLength;
private int currentProgress;
private MyProgressListener progressListener;
public MyProgressSource(Source delegate) {
super(delegate);
}
public MyProgressSource(Source delegate,String url,ResponseBody responseBody){
this(delegate);
this.mResponseBody = responseBody;
fullLength = mResponseBody.contentLength();
//根据请求的图片的url地址来获取相应的监听
progressListener = MyProgressInterceptor.listeners.get(url);
}
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long readLength = super.read(sink, byteCount);
if(-1 == readLength){
totalReadLength = fullLength;
}else {
totalReadLength +=readLength;
}
int progress = (int) (100f * totalReadLength/fullLength);
LogUtil.i("当前进度: "+currentProgress);
if(null != progressListener && currentProgress!=progress){
progressListener.onProgress(progress);
}
if(null !=progressListener && currentProgress==fullLength){
progressListener = null;
}
currentProgress = progress;
return totalReadLength;
}
}
在read方法中,计算当前读取的自己的长度,然后将其累加,通过累加的结果除以总的字节长度,算出一个100以内的整数。在MyProgressSource的构造方法中,获取当前这个url的对应的监听,之所以这样处理,是因为项目中会有多个图片需要加载,根据不同的url实现对不同的图片的下载监听,这样就能够更加准确的处理各个不同的图片的下载进度的监听。
定义完后,还需要在manifest文件中,做如下配置:
其中meta-data的name要配置成自定义的GlideModule的类全路径名称,value写成固定的GlideModule即可。
在项目中,还是和平常使用Glide加载图片的方式一样,不用做任何其他额外处理。
import android.app.ProgressDialog;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.GlideDrawableImageViewTarget;
import test.cn.example.com.androidskill.R;
public class GlideDemoActivity3 extends AppCompatActivity implements View.OnClickListener {
private ImageView iv;
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
private LinearLayout ll_root;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_glide_demo3);
ll_root = findViewById(R.id.ll_root);
findViewById(R.id.btn_3).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_3:
reset();
final ProgressDialog progressDialog = new ProgressDialog(this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
final String url3 = "https://up.sc.enterdesk.com/edpic/fb/6a/4c/fb6a4c2e7c044a22d785a18a01b8b6d1.jpg";
MyProgressInterceptor.addListener(url3, new MyProgressListener() {
@Override
public void onProgress(int progress) {
progressDialog.setProgress(progress);
}
});
Glide.with(this).load(url3)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(new GlideDrawableImageViewTarget(iv){
@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();
MyProgressInterceptor.removeListener(url3);
}
});
break;
}
}
private void reset() {
ll_root.removeView(iv);
iv = new ImageView(this);
iv.setLayoutParams(layoutParams);
ll_root.addView(iv);
}
}
布局文件如下: