Glide源碼加載流程分析一

前言

閱讀源碼應(yīng)該認準一個功能點(主線),然后去分析這個功能點是如何實現(xiàn)的。千萬不要試圖去搞懂每一行代碼都是什么意思,那樣很容易會陷入到源碼中庄吼,而且越陷越深。

Glide 使用簡明的流式語法API严就,在大部分情況下一行代碼即可搞定需求:

Glide.with(fragment)
    .load(url)
    .into(imageView);

取消加載同樣很簡單:

Glide.with(fragment).clear(imageView);

盡管及時取消不必要的加載是很好的習(xí)慣总寻,但這并不是必須的操作。實際上梢为,當 Glide.with() 中傳入的 Activity 或 Fragment 實例銷毀時渐行,Glide 會自動取消加載并回收資源。Glide的使用非常簡單铸董,但是簡單的背后祟印,隱藏了復(fù)雜的實現(xiàn),源碼內(nèi)容非常多粟害。

with.png

Glide.with

Glide類是單例模式蕴忆,其中重載了多個with方法,允許我們傳遞上下文(Context)以及能夠獲得上下文的Fragment與View悲幅。

所有的with方法中的實現(xiàn)均為:

//Glide
public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
}
    
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
    return Glide.get(context).getRequestManagerRetriever();
}

主要做了兩件事情:

  1. 通過get方法獲得Glide單例對象并獲取其成員屬性:RequestManagerRetriever套鹅;
  2. 通過RequestManagerRetriever的get方法獲得RequestManager。

RequestManagerRetriever

RequestManagerRetriever的功能就是獲得RequestManager請求管理者汰具。Glide中所有加載圖片的請求都是由請求管理器進行管理卓鹿。為了進行自動生命周期的管理,請求管理器與Glide.with方法傳遞的不同參數(shù)相對應(yīng)留荔。

Glide.with參數(shù):

  • 傳遞Activity吟孙,則此次加載圖片會在退出Activity后自動取消;
  • 傳遞Fragment存谎,則加載圖片會在Fragment銷毀時取消拔疚;
  • 傳遞Application,那么Glide將無法管理圖片請求生命周期既荚。

換句話說稚失,每一個Activity存在一個對應(yīng)的RequestManager,每一個不同的Fragment也有其對應(yīng)的RequestManager恰聘,而整個應(yīng)用運行階段同樣會有一個RequestManager句各。這些不同的RequestManager通過RequestManagerRetriever.get獲取。RequestManagerRetriever同樣重載了多個get方法晴叨,以get(FragmentActivity)為例:

//RequestManagerRetriever
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));
    }
}
  • 若非主線程凿宾,則使用Application上下文調(diào)用get重載方法get(Context),直接返回自己的成員屬性RequestManager類型的applicationManager兼蕊;
  • 若在主線程初厚,則獲得Activity對應(yīng)的FragmentManager來獲得RequestManager。

對于主線程的情況,Glide為了進行生命周期管理产禾,在Activity#onDestory時取消圖片加載排作。那么如何監(jiān)聽到Activity的onDestory回調(diào)?如果向此Activity中添加一個Fragment亚情,當Activity銷毀時妄痪,Activity中附加的Fragment同樣也會銷毀。事實上實現(xiàn)也確實如此楞件,在獲得了Activity對應(yīng)的FragmentManager之后衫生,supportFragmentGet的實現(xiàn)為:

//RequestManagerRetriever
private RequestManager supportFragmentGet(@NonNull Context context,@NonNull FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {
    SupportRequestManagerFragment current =
        getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      // TODO(b/27524013): Factor out this Glide.get() call.
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
}

其中getSupportRequestManagerFragment(fm, parentHint, isParentVisible)就是獲得附加在對應(yīng)Activity的Fragment,而此Fragment中存在成員RequestManager土浸。如果此成員不為null罪针,則直接返回,否則創(chuàng)建并設(shè)置進入Fragment之后再返回栅迄。

小結(jié)

with時序圖.png
  • 構(gòu)建 Glide 實例;
  • 獲取 RequestManagerRetriever 對象;
  • 構(gòu)建 RequestManager 對象站故。
    • 若傳遞Activity/Fragment并為主線程調(diào)用, 則為 Activity/Fragment 添加一個 RequestManagerFragment, 其內(nèi)部含有 ReqeustManager 對象;
    • 否則, 獲取一個相當于單例的 applicationManager 專門用于處理這類請求。

RequestManager.load

Glide.with獲得RequestManager之后毅舆,執(zhí)行l(wèi)oad方法設(shè)置圖片源西篓。圖片源可以是:圖片數(shù)據(jù)字節(jié)數(shù)組、File文件憋活,網(wǎng)絡(luò)圖片地址等岂津。以load(String)為例:

//RequestManager
public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
}
public RequestBuilder<Drawable> asDrawable() {
    return as(Drawable.class);
}
public <ResourceType> RequestBuilder<ResourceType> as(
      @NonNull Class<ResourceType> resourceClass) {
    return new RequestBuilder<>(glide, this, resourceClass, context);
}

load方法默認設(shè)置目標資源為 Drawable,獲得一個RequestBuilder悦即。緊接著調(diào)用RequestBuilder.load方法記錄加載的model(圖片源)吮成。

//RequestBuilder
public RequestBuilder<TranscodeType> load(@Nullable String string) {
    return loadGeneric(string);
}
@NonNull
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
    this.model = model;
    isModelSet = true;
    return this;
}

RequestBuilder是請求構(gòu)建者,用戶可以使用它設(shè)置如:單獨的緩存策略辜梳、加載成功前占位圖粱甫、加載失敗后顯示圖片等等加載圖片的各種配置。當RequestBuilder 構(gòu)建完成之后作瞄,接下來就等待執(zhí)行這個請求茶宵。

RequestBuilder.into

使用Glide最簡單的方式加載圖片最后一個階段就是執(zhí)行into方法。從into方法為入口開始執(zhí)行圖片加載宗挥,邏輯也開始復(fù)雜起來乌庶。

//RequestBuilder
@NonNull
  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    Util.assertMainThread();
    Preconditions.checkNotNull(view);

    BaseRequestOptions<?> requestOptions = this;
    if (!requestOptions.isTransformationSet()
        && requestOptions.isTransformationAllowed()
        && view.getScaleType() != null) {
      // Clone in this method so that if we use this RequestBuilder to load into a View and then
      // into a different target, we don't retain the transformation applied based on the previous
      // View's scale type.
      switch (view.getScaleType()) {
        case CENTER_CROP:
          requestOptions = requestOptions.clone().optionalCenterCrop();
          break;
        case CENTER_INSIDE:
          requestOptions = requestOptions.clone().optionalCenterInside();
          break;
        case FIT_CENTER:
        case FIT_START:
        case FIT_END:
          requestOptions = requestOptions.clone().optionalFitCenter();
          break;
        case FIT_XY:
          requestOptions = requestOptions.clone().optionalCenterInside();
          break;
        case CENTER:
        case MATRIX:
        default:
          // Do nothing.
      }
    }

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

一般來說,我們在執(zhí)行into時傳入一個ImageView用于顯示契耿。在這個into方法中瞒大,先確定本次加載的BaseRequestOptions,然后執(zhí)行重載的另一個into方法搪桂。其中BaseRequestOptions就是上面我們提到的RequestBuilder可以設(shè)置圖片加載的各種配置透敌,這些配置選項就被封裝在BaseRequestOptions中(RequestBuilder extends BaseRequestOptions)。而重載的into方法實現(xiàn)為:

//RequestBuilder

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

    requestManager.clear(target);
    target.setRequest(request);
    requestManager.track(target, request);

    return target;
  }

這個into方法中首先調(diào)用 buildRequest 構(gòu)建了一個請求Request,然后把Request交給RequestManager跟蹤(生命周期)并啟動請求酗电。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載淌山,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末顾瞻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子德绿,更是在濱河造成了極大的恐慌荷荤,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件移稳,死亡現(xiàn)場離奇詭異蕴纳,居然都是意外死亡,警方通過查閱死者的電腦和手機个粱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門古毛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人都许,你說我怎么就攤上這事稻薇。” “怎么了胶征?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵塞椎,是天一觀的道長。 經(jīng)常有香客問我睛低,道長案狠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任钱雷,我火速辦了婚禮骂铁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罩抗。我一直安慰自己拉庵,他們只是感情好,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布澄暮。 她就那樣靜靜地躺著名段,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泣懊。 梳的紋絲不亂的頭發(fā)上伸辟,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音馍刮,去河邊找鬼信夫。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的静稻。 我是一名探鬼主播警没,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼振湾!你這毒婦竟也來了杀迹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤押搪,失蹤者是張志新(化名)和其女友劉穎树酪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體大州,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡续语,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了厦画。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疮茄。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖根暑,靈堂內(nèi)的尸體忽然破棺而出力试,到底是詐尸還是另有隱情,我是刑警寧澤排嫌,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布懂版,位于F島的核電站,受9級特大地震影響躏率,放射性物質(zhì)發(fā)生泄漏躯畴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一薇芝、第九天 我趴在偏房一處隱蔽的房頂上張望蓬抄。 院中可真熱鬧,春花似錦夯到、人聲如沸嚷缭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阅爽。三九已至,卻和暖如春荐开,著一層夾襖步出監(jiān)牢的瞬間付翁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工晃听, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留百侧,地道東北人砰识。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像佣渴,于是被迫代替她去往敵國和親辫狼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

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