Glide 源码分析

Glide 作为一个出色的图片加载框架,对其剖析的文章数不胜数。而若像大多数文章一样常规的去分析源码就没什么新意了,本文旨在发掘一些新的或相对陌生的知识点,以完善对 Glide 的认知,涉及源码基于 v4.8.0。

主要内容:

  • 1.磁盘缓存
  • 2.内存缓存
  • 3.网络请求
  • 4.图片转换
  • 5.感知生命周期
  • 6.下载及预加载
  • 7.加载图片到通知栏和应用小部件中
  • 8.图片格式及内存优化
  • 9.请求优先级及原理
  • 10.缩略图使用及原理
  • 11.展示 gif 原理
  • 12.自定义模块及延伸
  • 13.兼容3.x写法

1.磁盘缓存

Glide 提供了五种磁盘缓存策略:

  • DiskCacheStrategy.AUTOMATIC
  • DiskCacheStrategy.ALL
  • DiskCacheStrategy.RESOURCE
  • DiskCacheStrategy.DATA
  • DiskCacheStrategy.NONE

默认为 AUTOMATIC 策略,即自动采用最佳策略,它会针对本地和远程图片使用不同的策略。当你加载远程数据时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如变换、裁剪)的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。即:

本地数据 -> DiskCacheStrategy.RESOURCE
远程数据 -> DiskCacheStrategy.DATA

源码中是在何处实现这一逻辑的呢?

2.内存缓存

3.网络请求

4.图片转换

5.感知生命周期

发起一个图片加载请求后,我们期望当该请求所处的界面 onStop 时请求也随之停止,再次 onStart 时请求能够随之继续, onDestroy 时请求能够随之销毁。这就需要能够感知当前 Activity 的生命周期变化,由于 Fragment 在 onAttach 之后与 Activity 有相同的生命周期,glide 利用这一点,通过给 Activity 添加一个无界面的 Fragment 实现感知。

发起请求时通过 with 方法传入上下文,此方法会返回一个 RequestManager,RequestManager 用于管理和启动图片加载请求,可以感知外部 Activity 的生命周期,从而管理请求随之启动、停止和重启。

先来分析一个较为简单的流程:with 方法传入 Activity,会调用到 RequestManagerRetriever 的 get 方法:

@NonNull
  public RequestManager get(@NonNull Activity activity) {
   
    if (Util.isOnBackgroundThread()) {
   
      return get(activity.getApplicationContext());
    } else {
   
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();
      return fragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }

其中调用 fragmentGet 方法去新建 RequestManager :

@NonNull
  private RequestManager fragmentGet(@NonNull Context context,
      @NonNull android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
   
    //这里新建了一个无界面的 Fragment,并添加到该界面
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
   
      Glide glide = Glide.get(context);
      requestManager =
    //这里新建了一个 requestManager,并将无界面 Fragment 的生命周期暴露给 requestManager
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }

当无界面 Fragment 生命周期变化时,通过接口回调出去给 requestManager,这样 requestManager 就实现了随外部生命周期变化自动启动、停止和重启请求。with 方法若传入其他参数,流程上也是大同小异,都是找到当前 Activity 或 Fragment ,给其添加一个无界面 Fragment 罢了。

而不管传入何参数,都有这样一个逻辑:

  if (Util.isOnBackgroundThread()) {
   
       return get(view.getContext().getApplicationContext());
  }

若当前处于非主线程,则一律基于应用生命周期请求,不再关心所在 Fragment 或 Activity 的生命周期,这是因为子线程中执行的任务本身就是跟所在界面生命周期无关的。

在分析这一块时涉及 ContextWrapper 相关的逻辑,若不太熟悉,可参考:ContextWrapper

Glide 推出时谷歌还未发布 Architecture Components,而现在若要实现一个可感知生命周期的逻辑,大可不必像 Glide 一样添加一个 Fragment ,直接使用 Architecture Components 中的 Lifecycle 组件就可以很方便的实现了。

6.下载及预加载

下载的标准写法如下,也是官方示例写法:

    @WorkerThread
    private void downloadFile() {
   
        FutureTarget<File> target = null;
        try {
   
            target = Glide.with(context)
                    .downloadOnly()
                    .load(imgUrl)
                    .submit();
            final File cacheFile = target.get();
            /*
             *  默认会下载到磁盘缓存中,理论上不应对缓存文件进行编辑、删除
             */
        } catch (InterruptedException | ExecutionException e) {
   
            Log.e(TAG, "download: ", e);
        } finally {
   
            // 这里要调用cancel方法取消等待操作并释放资源
            if (target != null) {
   
                target.cancel(true); // 若传true则允许中断操作
            }
        }
    }

此方式要自行开子线程,你可能会觉得稍显麻烦,直接调用 listener 方法监听 onResourceReady 回调岂不是更简单?其实不是的,由于要拿到 FutureTarget 调用其 cancel 方法,若监听 onResourceReady 代码逻辑会更复杂。

对于 FutureTarget.get() 方法,并不是调用时才会去加载数据,调用 submit 方法后就已经开始去加载数据了,get 方法最终会调用到 RequestFutureTarget 的 doGet 方法如下:

  private synchronized R doGet(Long timeoutMillis)
      throws ExecutionException, InterruptedException, TimeoutException {
   
    if (assertBackgroundThread && !isDone()) {
   
      Util.assertBackgroundThread();
    }

    if (isCancelled) {
   
      throw new CancellationException();
    } else if (loadFailed) {
   
      throw new ExecutionException(exception);
    } else if (resultReceived) {
   
      return resource;
    }

    if (timeoutMillis == null) {
   
      waiter.waitForTimeout(this, 0);
    } else if (timeoutMillis > 0) {
   
      long now = System.currentTimeMillis();
      long deadline = now + timeoutMillis;
      while (!isDone() && now < deadline) {
   
        waiter.waitForTimeout(this, deadline - now);
        now = System.currentTimeMillis();
      }
    }

    if (Thread.interrupted()) {
   
      throw new InterruptedException();
    } else if (loadFailed) {
   
      throw new ExecutionException(exception);
    } else if (isCancelled) {
   
      throw new CancellationException();
    } else if (!resultReceived) {
   
      throw new TimeoutException();
    }
    return resource;
  }

可以看到 get 方法内部并没有加载数据的逻辑, RequestFutureTarget 内部通过锁实现了 get 方法的阻塞调用,当资源加载完毕后 onResourceReady 中会解除阻塞:

  @Override
  public synchronized boolean onResourceReady(
      R resource, Object model, Target<R> target, DataSource dataSource, boolean isFirstResource) {
   
    // We might get a null result.
    resultReceived = true;
    this.resource = resource;
    waiter.notifyAll(this);
    return false;
  }

除了下载 File 类型以外,还可以指定下载类型,比如下载 Bitmap:

    @WorkerThread
    private void downloadBitmap() {
   
        RequestOptions DOWNLOAD_ONLY_OPTIONS = RequestOptions
                .diskCacheStrategyOf(DiskCacheStrategy.DATA) //这边其实可以根据业务场景配置,如果是网络图片一般需要缓存
                .priority(Priority.LOW) // 设置优先级
                .skipMemoryCache(true);
        FutureTarget<Bitmap> target = null
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值