美文网首页Android技术知识Android开发
Glide源码分析以及三级缓存原理

Glide源码分析以及三级缓存原理

作者: 落雨敏 | 来源:发表于2023-02-10 11:23 被阅读0次

    Glide是Android端开源图片加载库,能够帮助我们下载、缓存、展示多种格式图片。也是现在主流图片加载框架之一。源码内部究竟是如何实现的呢?讲解主流程,简略分析。

    用法如下:

     Glide.with(context).load(url).into(imageView);
    
    image.gif

    我这里拆分为三步分析:

    一、with(context)

    点击源码查看到是多个重载方法activity、fragment、view等等,下面用其中一个方法来展示

      @NonNull
      public static RequestManager with(@NonNull Activity activity) {
        return getRetriever(activity).get(activity);
      }
    
    image.gif
      @NonNull
      private static RequestManagerRetriever getRetriever(@Nullable Context context) {
        // Context could be null for other reasons (ie the user passes in null), but in practice it will
        // only occur due to errors with the Fragment lifecycle.
        Preconditions.checkNotNull(
            context,
            "You cannot start a load on a not yet attached View or a Fragment where getActivity() "
                + "returns null (which usually occurs when getActivity() is called before the Fragment "
                + "is attached or after the Fragment is destroyed).");
        return Glide.get(context).getRequestManagerRetriever();
      }
    
    image.gif

    调用getRetriever方法获取RequestManagerRetriever对象。在创建该对象之前首先通过Glide.java中的get方法获得了Glide单例对象以及AppClideModule等配置。

    @NonNull
      public static Glide get(@NonNull Context context) {
        if (glide == null) {
          GeneratedAppGlideModule annotationGeneratedModule =
              getAnnotationGeneratedGlideModules(context.getApplicationContext());
          synchronized (Glide.class) {
            if (glide == null) {
              checkAndInitializeGlide(context, annotationGeneratedModule);
            }
          }
        }
    
        return glide;
      }
    
    image.gif

    下面的get方法可知道,在子线程不会添加生命周期;主线程添加一个空白的fragment来处理生命周期。最后返回RequestManager对象

      @NonNull
      public RequestManager get(@NonNull Context context) {
        if (context == null) {
          throw new IllegalArgumentException("You cannot start a load on a null Context");
        } else if (Util.isOnMainThread() && !(context instanceof Application)) {
          if (context instanceof FragmentActivity) {
            return get((FragmentActivity) context);
          } else if (context instanceof Activity) {
            return get((Activity) context);
          } else if (context instanceof ContextWrapper
              // Only unwrap a ContextWrapper if the baseContext has a non-null application context.
              // Context#createPackageContext may return a Context without an Application instance,
              // in which case a ContextWrapper may be used to attach one.
              && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
            return get(((ContextWrapper) context).getBaseContext());
          }
        }
    
        return getApplicationManager(context);
      }
    
    //调用get判断线程
    
    @NonNull
      public RequestManager get(@NonNull FragmentActivity activity) {
        if (Util.isOnBackgroundThread()) {
          //子线程
          return get(activity.getApplicationContext());
        } else {
           //主线程添加生命周期
          assertNotDestroyed(activity);
          FragmentManager fm = activity.getSupportFragmentManager();
          return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
        }
      }
    
    image.gif

    二、load(url)

    上面执行完成到这里已经拿到RequestManager对象,然后调用load(url)。看源码可知是多个重载方法,传不同类型的资源。最终拿到RequestBuilder对象

    // RequestManager.java 的代码如下
    
    public RequestBuilder<Drawable> load(@Nullable Bitmap bitmap) {
        return asDrawable().load(bitmap);
      }
    
      public RequestBuilder<Drawable> load(@Nullable Drawable drawable) {
        return asDrawable().load(drawable);
      }
    
      public RequestBuilder<Drawable> load(@Nullable String string) {
        return asDrawable().load(string);
      }
    
      public RequestBuilder<Drawable> load(@Nullable Uri uri) {
        return asDrawable().load(uri);
      }
    
      public RequestBuilder<Drawable> load(@Nullable File file) {
        return asDrawable().load(file);
      }
    
      public RequestBuilder<Drawable> load(@RawRes @DrawableRes @Nullable Integer resourceId) {
        return asDrawable().load(resourceId);
      }
    
      public RequestBuilder<Drawable> load(@Nullable URL url) {
        return asDrawable().load(url);
      }
    
      public RequestBuilder<Drawable> load(@Nullable byte[] model) {
        return asDrawable().load(model);
      }
    
      public RequestBuilder<Drawable> load(@Nullable Object model) {
        return asDrawable().load(model);
      }
    
    
    image.gif

    三、into(imageView)

    上一步拿到了RequestBuilder对象,调用into可知有2个重载方法。into的参数就是最终显示的控件。

    image image.gif

    编辑

    into方法内部代码分支很多,代码庞大,所以只需走主流程如何显示ImageView的实现即可。当into内部代码执行完成后回到 buildImageViewTarget方法,这个方法是显示使用的,通过Executors.mainThreadExecutor())来切主线程,最终显示控件。

        return into(
            glideContext.buildImageViewTarget(view, transcodeClass),
            /*targetListener=*/ null,
            requestOptions,
            Executors.mainThreadExecutor());
    
    image.gif

    点击到into内部源码如下:

      private <Y extends Target<TranscodeType>> Y into(
          @NonNull Y target,
          @Nullable RequestListener<TranscodeType> targetListener,
          BaseRequestOptions<?> options,
          Executor callbackExecutor) {
        Preconditions.checkNotNull(target);
        if (!isModelSet) {
          throw new IllegalArgumentException("You must call #load() before calling #into()");
        }
    
        Request request = buildRequest(target, targetListener, options, callbackExecutor);
    
        Request previous = target.getRequest();
        if (request.isEquivalentTo(previous)
            && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
          // If the request is completed, beginning again will ensure the result is re-delivered,
          // triggering RequestListeners and Targets. If the request is failed, beginning again will
          // restart the request, giving it another chance to complete. If the request is already
          // running, we can let it continue running without interruption.
          if (!Preconditions.checkNotNull(previous).isRunning()) {
            // Use the previous request rather than the new one to allow for optimizations like skipping
            // setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
            // that are done in the individual Request.
            previous.begin();
          }
          return target;
        }
    
    image.gif

    这里处理请求

    Request request = buildRequest(target, targetListener, options, callbackExecutor);

    Request previous = target.getRequest();

    将请求对象装到集合中,并且有加锁处理,运用于多线程的并发请求。

    url请求走如下:

    image image.gif

    编辑

    网络请求完成callback.onDataReady(result),开始一步一步往回传数据。在这一系列过程中,进行了数据处理,比如:图片压缩等。 省略N步骤

    //  HttpUrlFetcher.java 代码如下
    
    @Override
      public void loadData(
          @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
        long startTime = LogTime.getLogTime();
        try {
          InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, 
        glideUrl.getHeaders());
          callback.onDataReady(result);
        } catch (IOException e) {
          if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Failed to load data for url", e);
          }
          callback.onLoadFailed(e);
        } finally {
          if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
          }
        }
      }
    
    image.gif

    最后回到了ImageViewTarget类,显示控件。这就是整体简略主流程。

     @Override
      public void setDrawable(Drawable drawable) {
        view.setImageDrawable(drawable);
      }
    
    image.gif

    四、缓存原理分析

    当加载图片会走2种方式:

    1、是Http/IO ;

    2、三级缓存策略

    一级缓存:活动缓存 ,当前Activity退出缓存销毁。

    二级缓存:LRU内存缓存 ,APP应用退出缓存销毁。

    三级缓存:LRU磁盘缓存 ,一直存在。

    一、缓存机制加载流程:

    获取顺序是,先从活动缓存取,如果没有就再去内存缓存取,如果还没是没有就再去磁盘缓存取,都没有就再去网络下载。
    

    二、缓存介绍:

       (1)  活动缓存:Glide自己实现的一种缓存策略,将使用的对象存放在HashMap,里面使用的弱引用,不需要时立即移除及时释放资源。
    
    (2)内存缓存:使用的LRU算法进行处理,核心是使用 LinkedHashMap 实现,保存到内存中。
    
     (3)磁盘缓存:使用的LRU算法进行处理,核心是使用 LinkedHashMap 实现,保存到磁盘中。(Glide使用DiskLruCache实现,将图片进行的加密、压缩处理,所以文件读写比普通IO处理效率高)
    

    LRU的原理:假设 maxSize =3,当第4个数据进入时,移除最先未使用的。画图理解一哈:

    image image.gif

    编辑

    LruCache类实际上是对LinkedHashMap进行的封装。上代码证明:

    image image.gif

    编辑

    值得注意的是,第三个参数true代表访问排序

    <pre>this.map = new LinkedHashMap<K, V>(0, 0.75f, true);</pre>

    三、活动缓存的意义

    示例场景:加入maxSize=3时,有新元素添加,此刻正回收1元素,刚好页面又使用1元素。这时候如果1元素被回收,就会找不到1元素从而崩溃。所以设计了活动缓存

    image image.gif

    编辑

    增加的活动缓存区解决上面的问题,画图方便理解:

    image image.gif

    编辑

    总结:1、当元素在使用时,将从内存缓存(二级缓存)移动到活动缓存(一级缓存);

             2、当元素未使用时,将从活动缓存释放资源,然后把该元素从活动缓存移动到内存缓存;
    

    三级缓存策略的使用总结:

    1、优先从活动缓存读取
    2、活动缓存没有,再内存缓存中读取
    3、内存缓存没有,再去磁盘缓存读取
    4、磁盘缓存没有,再去网络获取本地文件读取

    相关文章

      网友评论

        本文标题:Glide源码分析以及三级缓存原理

        本文链接:https://www.haomeiwen.com/subject/pexvuhtx.html