专栏首页包子的书架Glide的图片下载进度

Glide的图片下载进度

前言

好久没有写简书了,都荒废了自己,今天整理了一下以前的代码和目前现有的项目代码,看了关于gradle图片下载进度的代码,这边整理了Glide3.7.0和Glide4.8.0的图片下载进度的实现

思路分析

Glide下载的进度获取是通过对http请求的Interceptor拦截器进行获取responsebody的获取返回的长度和总长度,进行计算,然后通过接口回调给UI层。

Glide的3.7.0版本的图片下载进度实现

  • gradle的依赖引用
    implementation 'com.github.bumptech.glide:glide:3.7.0'
    implementation 'com.squareup.okhttp3:okhttp:3.9.0'
  • 定义进度回调接口
public interface ProgressListener {
    void onProgress(int progress);
}
  • 实现一个继承responsebody的子类,进行对响应数据长度的计算(Glide使用的是okhttp的网络请求库),在这边其实Source相当于一个输入流InputStream,ProgressSource这个内部类就是对响应数据流进行做计算处理,得出图片下载进度。
package cn.xxxx.demoset.glide;
import android.util.Log;

import java.io.IOException;

import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;

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;
        }
    }
}
  • 定义拦截器ProgressInterceptor, 通过拦截器获取ResponseBody对象,再通过我们上面定义的ProgressResponseBody进行响应数据处理
package cn.xxxx.demoset.glide;

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 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(Interceptor.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;
    }
}
  • 实现一个继承GlideModule类,用来配置 GlideModule 来修改 Glide 的一些初始化配置,这边通过复写registerComponents方法来添加上面定义的拦截器。
public class MyGlideModule implements GlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
    }

    @Override
    public void registerComponents(Context context, Glide 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));
    }
}
  • 项目早期使用Glide,并没有引用com.github.bumptech.glide:okhttp3-integration的引用,所以需要手动添加OkHttpGlideUrlLoaderOkHttpFetcher这两个类,如下: OkHttpGlideUrlLoader类
package cn.xxxx.demoset.glide;

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

OkHttpFetcher类

package cn.xxxx.demoset.glide;

import com.bumptech.glide.Priority;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.util.ContentLengthInputStream;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

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

-在AndroidManifest配置MyGlideModule

 <application
        .........
        <activity android:name=".ui.image.ImageDownloadActivity" />
        <meta-data
            android:name="cn.xxxx.demoset.glide.MyGlideModule"
            android:value="GlideModule" />
    </application>
  • 最终调用实现
 ProgressInterceptor.addListener(url, new ProgressListener() {
      @Override
      public void onProgress(int progress) {
        progressDialog.setProgress(progress);
      }
    });

Glide.with(this)
      .load(url)
      .asGif()
      .toBytes()
      .into(new SimpleTarget<byte[]>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) {

          @Override
          public void onLoadStarted(Drawable placeholder) {
            super.onLoadStarted(placeholder);
            progressDialog.show();
          }

          @Override
          public void onResourceReady(byte[] bytes, GlideAnimation<? super byte[]> glideAnimation) {
            progressDialog.dismiss();
            ProgressInterceptor.removeListener(url);
            // 下载成功回调函数
            // 数据处理方法,保存bytes到文件
            File externalCacheDir = ImageDownloadActivity.this.getExternalCacheDir();
            String folder = externalCacheDir.getAbsolutePath() + File.separator + "Pictures";
            boolean result = FileUtil.writeFileToCache(bytes, folder, "aa.gif");
            if (result) {
              Toast.makeText(ImageDownloadActivity.this, "保存成功", Toast.LENGTH_SHORT).show();
              String path = folder + File.separator + "aa.gif";
              File file = new File(path);
              if (file.exists()) {
                Glide.with(ImageDownloadActivity.this)
                    .load(file).asGif().into(showImage);
              }
            }
          }

          @Override
          public void onLoadFailed(Exception e, Drawable errorDrawable) {
            // 下载失败回调
            progressDialog.dismiss();
            ProgressInterceptor.removeListener(url);
          }
        });

Glide的4.8.0版本的图片下载进度实现

  • gradle的依赖引用
implementation "com.github.bumptech.glide:glide:4.8.0"
annotationProcessor "com.github.bumptech.glide:compiler:4.8.0"
 implementation "com.github.bumptech.glide:okhttp3-integration:4.8.0"
  • 接口ProgressListener、ProgressResponseBody 和ProgressInterceptor这三个代码同上不再贴了
  • 编写GlideModule,实现的代码是一样的,唯一和上面的实现区别是,这边是直接通过注解@GlideModule的形式引用,不需要在到AndroidManifest的清单文件里面注册
@GlideModule
public class OkHttpLibraryGlideModule extends AppGlideModule {
  @Override
  public void registerComponents(Context context, Glide glide, Registry registry) {
    //添加拦截器到Glide
    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.addInterceptor(new ProgressInterceptor());
    OkHttpClient okHttpClient = builder.build();

    registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(okHttpClient));
  }

  @Override
  public boolean isManifestParsingEnabled() {
    //完全禁用清单解析
    return false;
  }
}
  • 实现调用通上面的一样,不在重复

出现过的问题

  • 在开发中出现过,获取数据的总长度位-1的情况
long fullLength = responseBody.contentLength();

即 fullLength 为-1

  • 出现的原因,是因为自家服务端的数据返回采用的是g-zip的格式导致的,或者其他的原因 -解决方法:
LazyHeaders.Builder builder = new LazyHeaders.Builder();
//添加当前head头部是为了处理okhttp的responsebody.contentLength()获取到的值为-1 
builder.addHeader("Accept-Encoding", "identity");
GlideUrl glideUrl =  new GlideUrl(url, builder.build());
File file = Glide.with(context.getApplicationContext()).download(glideUrl)
 .submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).get();

结语

以上就是个人在做glide实现图片下载带有进度的全部内容,欢迎各位同学点评,如果问题的dia

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • memset的含义及作用

    struct sample_struct { char csName[16]; int iSeq; int iType; };

    包子388321
  • Android 通过DecorView计算statusBar、navigationBar的高度

    近期在做项目的时候碰到了底部虚拟按键在各个厂商适配的问题,闷逼了一圈,后面搜索一圈,发现即使各大厂商有变动,还是离不开原生本质

    包子388321
  • 加壳上碰到的问题

    最近由于公司项目原因,开始学习入手C++的加壳技术壳的编写,参考文献oBuYiSeng的博客里面详细的介绍了加壳的原理和开发步骤。 个人在开发的时候碰到了一些...

    包子388321
  • XStream、JAXB 日期(Date)、数字(Number)格式化输出xml

    XStream、Jaxb是java中用于对象xml序列化/反序列化 的经典开源项目,利用它们将对象转换成xml时,经常会遇到日期(Date)、数字按指定格式输出...

    菩提树下的杨过
  • Java程序员笔记—mybatis结合redis实战二级缓存

    我们知道任何mybatis二级缓存都需要实现一个接口,这个接口就是org.apache.ibatis.cache.Cache,代码如下:

    慕容千语
  • PHP反射类export方法详细解析

    CrazyCodes
  • Java原生序列化和Kryo序列化性能比较

    最近几年,各种新的高效序列化方式层出不穷,不断刷新序列化性能的上限,最典型的包括:

    用户1205080
  • IoC在ASP.NET Web API中的应用

    控制反转(Inversion of Control,IoC),简单地说,就是应用本身不负责依赖对象的创建和维护,而交给一个外部容器来负责。这样控制权就由应用转移...

    蒋金楠
  • Rxjava + retrofit + dagger2 + mvp搭建Android框架

    最近出去面试,总会被问到我们项目现在采用的什么开发框架,不过据我的经验网络框架(volley)+图片缓存(uIl)+数据库(orm)+mvp,不过现在这套框架比...

    xiangzhihong
  • 一个 android 的框架

    最近在 github 上看到一个 android 的框架,结合了 Rxjava + retrofit + dagger2 + mvp,结合了当下比较流行的框架,...

    用户1263308

扫码关注云+社区

领取腾讯云代金券