Glide源碼分析以及三級(jí)緩存原理

Glide是Android端開源圖片加載庫(kù),能夠幫助我們下載戏阅、緩存昼弟、展示多種格式圖片。也是現(xiàn)在主流圖片加載框架之一奕筐。源碼內(nèi)部究竟是如何實(shí)現(xiàn)的呢舱痘?講解主流程,簡(jiǎn)略分析救欧。

用法如下:

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

我這里拆分為三步分析:

一衰粹、with(context)

點(diǎn)擊源碼查看到是多個(gè)重載方法activity、fragment笆怠、view等等铝耻,下面用其中一個(gè)方法來展示

  @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

調(diào)用getRetriever方法獲取RequestManagerRetriever對(duì)象。在創(chuàng)建該對(duì)象之前首先通過Glide.java中的get方法獲得了Glide單例對(duì)象以及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方法可知道瓢捉,在子線程不會(huì)添加生命周期;主線程添加一個(gè)空白的fragment來處理生命周期办成。最后返回RequestManager對(duì)象

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

//調(diào)用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)

上面執(zhí)行完成到這里已經(jīng)拿到RequestManager對(duì)象,然后調(diào)用load(url)迂卢∧诚遥看源碼可知是多個(gè)重載方法,傳不同類型的資源而克。最終拿到RequestBuilder對(duì)象

// 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對(duì)象,調(diào)用into可知有2個(gè)重載方法员萍。into的參數(shù)就是最終顯示的控件腾降。

image
image.gif

編輯

into方法內(nèi)部代碼分支很多,代碼龐大碎绎,所以只需走主流程如何顯示ImageView的實(shí)現(xiàn)即可螃壤。當(dāng)into內(nèi)部代碼執(zhí)行完成后回到 buildImageViewTarget方法抗果,這個(gè)方法是顯示使用的,通過Executors.mainThreadExecutor())來切主線程奸晴,最終顯示控件冤馏。

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

點(diǎn)擊到into內(nèi)部源碼如下:

  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

這里處理請(qǐng)求

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

Request previous = target.getRequest();

將請(qǐng)求對(duì)象裝到集合中,并且有加鎖處理寄啼,運(yùn)用于多線程的并發(fā)請(qǐng)求宿接。

url請(qǐng)求走如下:

image
image.gif

編輯

網(wǎng)絡(luò)請(qǐng)求完成callback.onDataReady(result),開始一步一步往回傳數(shù)據(jù)辕录。在這一系列過程中,進(jìn)行了數(shù)據(jù)處理梢卸,比如:圖片壓縮等走诞。 省略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類,顯示控件蛤高。這就是整體簡(jiǎn)略主流程蚣旱。

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

四、緩存原理分析

當(dāng)加載圖片會(huì)走2種方式:

1戴陡、是Http/IO 塞绿;

2、三級(jí)緩存策略

一級(jí)緩存:活動(dòng)緩存 恤批,當(dāng)前Activity退出緩存銷毀异吻。

二級(jí)緩存:LRU內(nèi)存緩存 ,APP應(yīng)用退出緩存銷毀喜庞。

三級(jí)緩存:LRU磁盤緩存 诀浪,一直存在。

一延都、緩存機(jī)制加載流程:

獲取順序是雷猪,先從活動(dòng)緩存取,如果沒有就再去內(nèi)存緩存取晰房,如果還沒是沒有就再去磁盤緩存取求摇,都沒有就再去網(wǎng)絡(luò)下載。

二殊者、緩存介紹:

   (1)  活動(dòng)緩存:Glide自己實(shí)現(xiàn)的一種緩存策略与境,將使用的對(duì)象存放在HashMap,里面使用的弱引用幽污,不需要時(shí)立即移除及時(shí)釋放資源嚷辅。

(2)內(nèi)存緩存:使用的LRU算法進(jìn)行處理,核心是使用 LinkedHashMap 實(shí)現(xiàn)距误,保存到內(nèi)存中簸搞。

 (3)磁盤緩存:使用的LRU算法進(jìn)行處理扁位,核心是使用 LinkedHashMap 實(shí)現(xiàn),保存到磁盤中趁俊。(Glide使用DiskLruCache實(shí)現(xiàn)域仇,將圖片進(jìn)行的加密、壓縮處理寺擂,所以文件讀寫比普通IO處理效率高)

LRU的原理:假設(shè) maxSize =3暇务,當(dāng)?shù)?個(gè)數(shù)據(jù)進(jìn)入時(shí),移除最先未使用的怔软。畫圖理解一哈:

image
image.gif

編輯

LruCache類實(shí)際上是對(duì)LinkedHashMap進(jìn)行的封裝垦细。上代碼證明:

image
image.gif

編輯

值得注意的是,第三個(gè)參數(shù)true代表訪問排序

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

三挡逼、活動(dòng)緩存的意義

示例場(chǎng)景:加入maxSize=3時(shí)括改,有新元素添加,此刻正回收1元素家坎,剛好頁(yè)面又使用1元素嘱能。這時(shí)候如果1元素被回收,就會(huì)找不到1元素從而崩潰虱疏。所以設(shè)計(jì)了活動(dòng)緩存

image
image.gif

編輯

增加的活動(dòng)緩存區(qū)解決上面的問題惹骂,畫圖方便理解:

image
image.gif

編輯

總結(jié):1、當(dāng)元素在使用時(shí)做瞪,將從內(nèi)存緩存(二級(jí)緩存)移動(dòng)到活動(dòng)緩存(一級(jí)緩存)对粪;

         2、當(dāng)元素未使用時(shí)穿扳,將從活動(dòng)緩存釋放資源衩侥,然后把該元素從活動(dòng)緩存移動(dòng)到內(nèi)存緩存;

三級(jí)緩存策略的使用總結(jié):

1矛物、優(yōu)先從活動(dòng)緩存讀取
2茫死、活動(dòng)緩存沒有,再內(nèi)存緩存中讀取
3履羞、內(nèi)存緩存沒有峦萎,再去磁盤緩存讀取
4、磁盤緩存沒有忆首,再去網(wǎng)絡(luò)獲取本地文件讀取

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末爱榔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子糙及,更是在濱河造成了極大的恐慌详幽,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異唇聘,居然都是意外死亡版姑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門迟郎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剥险,“玉大人,你說我怎么就攤上這事宪肖”碇疲” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵控乾,是天一觀的道長(zhǎng)么介。 經(jīng)常有香客問我,道長(zhǎng)蜕衡,這世上最難降的妖魔是什么夭拌? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮衷咽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蒜绽。我一直安慰自己镶骗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布躲雅。 她就那樣靜靜地躺著鼎姊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪相赁。 梳的紋絲不亂的頭發(fā)上相寇,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音钮科,去河邊找鬼唤衫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛绵脯,可吹牛的內(nèi)容都是我干的佳励。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛆挫,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼赃承!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起悴侵,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤瞧剖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抓于,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡做粤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了毡咏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驮宴。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖呕缭,靈堂內(nèi)的尸體忽然破棺而出堵泽,到底是詐尸還是另有隱情,我是刑警寧澤恢总,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布迎罗,位于F島的核電站,受9級(jí)特大地震影響片仿,放射性物質(zhì)發(fā)生泄漏纹安。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧己单,春花似錦吆你、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至卒茬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間咖熟,已是汗流浹背圃酵。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留馍管,地道東北人郭赐。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像确沸,于是被迫代替她去往敵國(guó)和親堪置。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容