热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

Android基于Glidev4.x的图片加载进度监听

Glide是一款优秀的图片加载框架,简单的配置便可以使用起来,为开发者省下了很多的功夫。不过,它没有提供其加载图片进度的api,对于这样的需

Glide是一款优秀的图片加载框架,简单的配置便可以使用起来,为开发者省下了很多的功夫。不过,它没有提供其加载图片进度的api,对于这样的需求,实现起来还真颇费一番周折。

尝试

遇到这个需求,第一反应是网上肯定有人实现过,不妨借鉴一下别人的经验。

Glide加载图片实现进度条效果

可惜,这个实现是基于3.7版本的,4.0版本以上的glide改动比较大,using函数已经被移除了

using()
The using() API was removed in Glide 4 to encourage users to register their components once with a AppGlideModule to avoid object re-use. Rather than creating a new ModelLoader each time you load an image, you register it once in an AppGlideModule and let Glide inspect your model (the object you pass to load()) to figure out when to use your registered ModelLoader.
To make sure you only use your ModelLoader for certain models, implement handles() as shown above to inspect each model and return true only if your ModelLoader should be used.

思考

又要用最新的版本又希望给其增加功能,鱼与熊掌不可兼得?“贪新厌旧”的我怎会轻易放弃。我对加载图片进度的需求再理了一遍,发现可以用其他思路简化一下:

  • 加载图片分为加载本地图片和网络图片
  • 加载本地图片速度很快,主要耗时在图片解码的工作,如果图片已经在内存中,解码的步骤也省去了
  • 加载网络图片主要时间耗在下载图片数据过程中

所以,能监听到图片的下载进度就大概是图片的加载进度了。那难道要先下载图片再交给glide去加载?不用,glide支持整合其他网络模块,例如OkHttp,并且如果我们愿意的话,也可以利用其接口实现自己的网络加载模块。

glide官方仓库提供了OkHttp的整合模块,于是乎我去这些代码了寻找一下突破口。

突破

OkHttp模块是非常简单的,只有4个文件,并且文件都不长

首先,用过glide的都知道,继承GlideModule的类是用于设置glide的配置信息和加载模块的,在OkHttpGlideModule里,向系统注册了一个用于加载GlideUrl类型的组件,简单的可以理解为加载网络图片的组件。

public class OkHttpGlideModule implements GlideModule {
  public OkHttpGlideModule() {
  }

  public void applyOptions(Context context, GlideBuilder builder) {
  }

  public void registerComponents(Context context, Glide glide, Registry registry) {
    registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
  }
}

OkHttpLoader文件也很简单,实现了ModelLoader接口,这里ModelLoader是glide的抽象的资源loader的表示,例如这里,就是说加载GlideUrl的model,返回InputStream,即图片的输入流。关于Glide的loadData、model、fetch的详细介绍,可以查看 官方文档

OkHttpLoader的最终目的,也就是返回了一个LoadData对象。现在情况明确了,glide框架就是利用这个LoadData对象得到图片的输入流,从而下载图片并经过一系列的解码,裁剪,缓存等操作,最后加载出来的。LoadData的参数有一个OkHttpStreamFetcher,从名字看来,这里一定就是下载图片的地方了,我们继续看下去。

public class OkHttpUrlLoader implements ModelLoader {
  private final okhttp3.Call.Factory client;

  public OkHttpUrlLoader(okhttp3.Call.Factory client) {
    this.client = client;
  }

  public boolean handles(GlideUrl url) {
    return true;
  }

  public LoadData buildLoadData(GlideUrl model, int width, int height, Options options) {
    //返回LoadData对象,泛型为InputStream
    return new LoadData(model, new OkHttpStreamFetcher(this.client, model));
  }

  public static class Factory implements ModelLoaderFactory {
    private static volatile okhttp3.Call.Factory internalClient;
    private okhttp3.Call.Factory client;

    private static okhttp3.Call.Factory getInternalClient() {
      if(internalClient == null) {
        Class var0 = OkHttpUrlLoader.Factory.class;
        synchronized(OkHttpUrlLoader.Factory.class) {
          if(internalClient == null) {
            internalClient = new OkHttpClient();
          }
        }
      }

      return internalClient;
    }

    public Factory() {
      this(getInternalClient());
    }

    public Factory(okhttp3.Call.Factory client) {
      this.client = client;
    }

    public ModelLoader build(MultiModelLoaderFactory multiFactory) {
      return new OkHttpUrlLoader(this.client);
    }

    public void teardown() {
    }
  }
}

可以看到,所有的重点都在loadData函数里面了,用过OkHttp的同学,有没有觉得好简单?哈哈。

这里直接得到图片的InputStream然后通过回调函数callback.onDataReady()返回了

问题来了,callback的glide框架里传过来的,我们并不能控制,我尝试找了一下调用loadData的地方,结果没有找到。怎么办?看最终的实现吧。

public class OkHttpStreamFetcher implements DataFetcher {
  private static final String TAG = "OkHttpFetcher";
  private final Factory client;
  private final GlideUrl url;
  InputStream stream;
  ResponseBody responseBody;
  private volatile Call call;

  public OkHttpStreamFetcher(Factory client, GlideUrl url) {
    this.client = client;
    this.url = url;
  }

  public void loadData(Priority priority, final DataCallback<&#63; super InputStream> callback) {
    Builder requestBuilder = (new Builder()).url(this.url.toStringUrl());
    Iterator request = this.url.getHeaders().entrySet().iterator();

    while(request.hasNext()) {
      Entry headerEntry = (Entry)request.next();
      String key = (String)headerEntry.getKey();
      requestBuilder.addHeader(key, (String)headerEntry.getValue());
    }

    Request request1 = requestBuilder.build();
    this.call = this.client.newCall(request1);
    this.call.enqueue(new Callback() {
      public void onFailure(Call call, IOException e) {
        if(Log.isLoggable("OkHttpFetcher", 3)) {
          Log.d("OkHttpFetcher", "OkHttp failed to obtain result", e);
        }

        callback.onLoadFailed(e);
      }

      public void onResponse(Call call, Response response) throws IOException {
        OkHttpStreamFetcher.this.respOnseBody= response.body();
        if(response.isSuccessful()) {
          long cOntentLength= OkHttpStreamFetcher.this.responseBody.contentLength();
          OkHttpStreamFetcher.this.stream = ContentLengthInputStream.obtain(OkHttpStreamFetcher.this.responseBody.byteStream(), contentLength);
          callback.onDataReady(OkHttpStreamFetcher.this.stream);
        } else {
          callback.onLoadFailed(new HttpException(response.message(), response.code()));
        }

      }
    });
  }

  public void cleanup() {
    try {
      if(this.stream != null) {
        this.stream.close();
      }
    } catch (IOException var2) {
      ;
    }

    if(this.responseBody != null) {
      this.responseBody.close();
    }

  }

  public void cancel() {
    Call local = this.call;
    if(local != null) {
      local.cancel();
    }

  }

  public Class getDataClass() {
    return InputStream.class;
  }

  public DataSource getDataSource() {
    return DataSource.REMOTE;
  }
}

实现

在loadData函数里,有这样一句代码

代码如下:

OkHttpStreamFetcher.this.stream = ContentLengthInputStream.obtain(OkHttpStreamFetcher.this.responseBody.byteStream(), contentLength);

ContentLengthInputStream是glide的工具类,用来读取输入流的,点进去看看,发现有几个read方法

  public synchronized int read() throws IOException {
    int value = super.read();
    this.checkReadSoFarOrThrow(value >= 0&#63;1:-1);
    return value;
  }

  public int read(byte[] buffer) throws IOException {
    return this.read(buffer, 0, buffer.length);
  }

  public synchronized int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
    return this.checkReadSoFarOrThrow(super.read(buffer, byteOffset, byteCount));
  }

所以,我的解决方法就是在这里计算下载进度和进行进度的报告的,把这个类从源码里拷贝出来,替换掉OkHttp模块里面的ContentLengthInputStream,并在这插入自己的下载进度逻辑就行了。

具体的代码实现就不贴出来了,写个大概的思路:

下载图片的时候传入图片进度监听,用集合容器,例如map保存监听的实例,key为url,下载过程中计算下载进度后通过url找到对应的回调返回进度,图片加载完毕后将回调从集合了移除(glide提供了图片加载完毕的回调)。

ok,就这样,有不合理的地方欢迎指出,又更好更优雅的实现方式请不吝指教。也希望大家多多支持。


推荐阅读
  • JESD204C 入门:第2部分新特性及其内容
    本文内容来自ADI的技术文章,作者:DelJones原网址为:https:www.analog.comcnanalog-dialoguea ... [详细]
  • 本文详细介绍了如何正确安装Java EE SDK,并解决在安装过程中可能遇到的问题,特别是关于servlet代码在Apache Tomcat 10中无法运行的情况。 ... [详细]
  • 本文探讨如何利用Java反射技术来模拟Webwork框架中的URL解析过程。通过这一实践,读者可以更好地理解Webwork及其后续版本Struts2的工作原理,尤其是它们在MVC架构下的角色。 ... [详细]
  • 本文探讨了Web开发与游戏开发之间的主要区别,旨在帮助开发者更好地理解两种开发领域的特性和需求。文章基于作者的实际经验和网络资料整理而成。 ... [详细]
  • 本文将指导您如何在Docker环境中高效地搜索、下载Redis镜像,并通过指定或不指定配置文件的方式启动Redis容器。同时,还将介绍如何使用redis-cli工具连接到您的Redis实例。 ... [详细]
  • 全能终端工具推荐:高效、免费、易用
    介绍一款备受好评的全能型终端工具——MobaXterm,它不仅功能强大,而且完全免费,适合各类用户使用。 ... [详细]
  • 容器与微服务基础:快速入门指南
    探索容器和微服务的基础知识,了解如何通过先进的应用性能管理(APM)工具提升监控效能。加入AppDynamics APM的导览,掌握容器与微服务实施及监控的最佳实践。 ... [详细]
  • 本文档详细介绍了在 Kubernetes 集群中部署 ETCD 数据库的过程,包括实验环境的准备、ETCD 证书的生成及配置、以及集群的启动与健康检查等关键步骤。 ... [详细]
  • 本文探讨了使用Filter作为控制器的优势,以及Servlet与Filter之间的主要差异。同时,详细解析了Servlet的工作流程及其生命周期,以及ServletConfig与ServletContext的区别与应用场景。 ... [详细]
  • 1、字符型常量字符型常量指单个字符,是用一对单引号及其所括起来的字符表示。例如:‘A’、‘a’、‘0’、’$‘等都是字符型常量。C语言的字符使用的就是 ... [详细]
  • Lua编程进阶:数组与迭代器详解
    本文深入探讨了Lua语言中的数组和迭代器,通过实例讲解了一维数组、多维数组的使用方法及迭代器的工作原理。 ... [详细]
  • Docker 自定义网络配置详解
    本文详细介绍如何在 Docker 中自定义网络设置,包括网关和子网地址的配置。通过具体示例展示如何创建和管理自定义网络,以及容器间的通信方式。 ... [详细]
  • 深入理解Docker网络管理
    本文介绍了Docker网络管理的基本概念,包括为什么需要Docker网络管理以及Docker提供的多种网络驱动模式。同时,文章还详细解释了Docker网络相关的命令操作,帮助读者更好地理解和使用Docker网络功能。 ... [详细]
  • Matlab 实现工程与科学问题 - 第三章个人解析
    作为一名在读大学生,本文分享了我对《工程与科学中的Matlab应用》第三章习题的个人解决方案。欢迎通过私信或评论进行交流和讨论,但不接受任何形式的权威指导。文中提供了详细的代码实现,旨在促进学习和共同进步。 ... [详细]
  • 热璞数据库与云宏达成兼容性互认证,共筑数据安全屏障
    热璞数据库与云宏信息技术有限公司近期宣布完成产品兼容性互认证,旨在提升数据安全性与稳定性,支持企业数字化转型。 ... [详细]
author-avatar
Novice
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有