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

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、磁盘缓存没有,再去网络获取本地文件读取

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容