Glide實(shí)現(xiàn)原理記錄

Glide初始化,采用懶注冊(cè)的方式跺讯,設(shè)置參數(shù)

Glide是一個(gè)單例,應(yīng)用第一次使用Glide是會(huì)調(diào)用initializeGlide方法殉农,編譯期根據(jù)GlideModule或清單文件中的模塊命名(已棄用)生成GeneratedAppGlideModuleImpl類刀脏,提供初始化構(gòu)造參數(shù)的方法填充GlideBuilder,最終build出Glide
GlideModule注解類,提供初始化默認(rèn)參數(shù)修改超凳,AppGlideModule提供兩個(gè)方法

  • isManifestParsingEnabled 是否讀取清單文件中的參數(shù)
  • applyOptions 修改GlideBuilder的默認(rèn)初始化參數(shù)愈污,設(shè)置緩存參數(shù)之類
    使用方法是新建類繼承這個(gè)類并且加上GlideModule注解,按照需要重寫上面兩個(gè)方法完成初始化參數(shù)設(shè)置
/**
Defines a set of dependencies and options to use when initializing Glide within an application.
...
*/
public abstract class AppGlideModule extends LibraryGlideModule implements AppliesOptions {
  /**
   * Returns {@code true} if Glide should check the AndroidManifest for {@link GlideModule}s.
   *
   * <p>Implementations should return {@code false} after they and their dependencies have migrated
   * to Glide's annotation processor.
   *
   * <p>Returns {@code true} by default.
   */
  public boolean isManifestParsingEnabled() {
    return true;
  }

  @Override
  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
    // Default empty impl.
  }
}
/**
 * Registers a set of components to use when initializing Glide within an app when
*/
public abstract class LibraryGlideModule implements RegistersComponents {
  @Override
  public void registerComponents(@NonNull Context context, @NonNull Glide glide,
      @NonNull Registry registry) {
    // Default empty impl.
  }
}

registerComponents可增加解析組件
例子:增加webp格式的圖片加載

@GlideModule
public class WebpGlideLibraryModule extends LibraryGlideModule {

    @Override
    public void registerComponents(Context context, Glide glide, Registry registry) {

        // We should put our decoder before the build-in decoders,
        // because the Downsampler will consume arbitrary data and make the inputstream corrupt
        // on some devices
        final Resources resources = context.getResources();
        final BitmapPool bitmapPool = glide.getBitmapPool();
        final ArrayPool arrayPool = glide.getArrayPool();
        /* static webp decoders */
        WebpDownsampler webpDownsampler = new WebpDownsampler(registry.getImageHeaderParsers(),
                resources.getDisplayMetrics(), bitmapPool, arrayPool);
        AnimatedWebpBitmapDecoder bitmapDecoder = new AnimatedWebpBitmapDecoder(arrayPool, bitmapPool);
        ByteBufferBitmapWebpDecoder byteBufferBitmapDecoder = new ByteBufferBitmapWebpDecoder(webpDownsampler);
        StreamBitmapWebpDecoder streamBitmapDecoder = new StreamBitmapWebpDecoder(webpDownsampler, arrayPool);
        /* animate webp decoders */
        ByteBufferWebpDecoder byteBufferWebpDecoder =
                new ByteBufferWebpDecoder(context, arrayPool, bitmapPool);
        registry
                /* Bitmaps for static webp images */
                .prepend(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)
                .prepend(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder)
                /* BitmapDrawables for static webp images */
                .prepend(
                        Registry.BUCKET_BITMAP_DRAWABLE,
                        ByteBuffer.class,
                        BitmapDrawable.class,
                        new BitmapDrawableDecoder<>(resources, byteBufferBitmapDecoder))
                .prepend(
                        Registry.BUCKET_BITMAP_DRAWABLE,
                        InputStream.class,
                        BitmapDrawable.class,
                        new BitmapDrawableDecoder<>(resources, streamBitmapDecoder))
                /* Bitmaps for animated webp images*/
                .prepend(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class,
                        new ByteBufferAnimatedBitmapDecoder(bitmapDecoder))
                .prepend(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class,
                        new StreamAnimatedBitmapDecoder(bitmapDecoder))
                /* Animated webp images */
                .prepend(ByteBuffer.class, WebpDrawable.class, byteBufferWebpDecoder)
                .prepend(InputStream.class, WebpDrawable.class, new StreamWebpDecoder(byteBufferWebpDecoder, arrayPool))
                .prepend(WebpDrawable.class, new WebpDrawableEncoder());
    }
}

3級(jí)緩存結(jié)構(gòu)

緩存優(yōu)先級(jí) ActiveResources轮傍,MemoryCache暂雹,DiskCache
從最終的load方法可以看出,加載會(huì)先取ActiveResources创夜,再取MemoryCache如果有的話會(huì)從MemoryCache中刪除并且添加到ActiveResources中杭跪,最后創(chuàng)建EngineJob異步取本地緩存或網(wǎng)絡(luò)獲取,獲取成功后會(huì)添加到ActiveResources中驰吓,ActiveResources利用ReferenceQueue在弱應(yīng)用被回收時(shí)從ActiveResources中移除并添加到MemoryCache中涧尿。

  public <R> LoadStatus load(...) {
    ...
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }

    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }

    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    EngineJob<R> engineJob =
        engineJobFactory.build(...);

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(...);

    jobs.put(key, engineJob);

    engineJob.addCallback(cb);
    engineJob.start(decodeJob);

    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }
  • ActiveResources可以理解為是當(dāng)前正在被引用的資源弱引用(WeakReference)
  • MemoryCache是內(nèi)存緩存,可設(shè)置大小限制檬贰,不會(huì)包含ActiveResources中的資源
  • DiskCache 本地文件緩存姑廉,可設(shè)置緩存路徑及緩存文件總大小
  • 這里有個(gè)細(xì)節(jié),為什么要有兩個(gè)內(nèi)存緩存翁涤,原因是MemoryCache是有大小限制的桥言,為防止正在使用的緩存被釋放,所以增加了ActiveResources
    DiskCache本地緩存迷雪,首次獲取本地緩存時(shí)會(huì)觸發(fā)本地文件緩存掃描限书,緩存目錄會(huì)有一個(gè)journal文件
    里面記錄了本地緩存文件列表及信息,如下

libcore.io.DiskLruCache
1
1
1

CLEAN b31dfe4cd710e8868de840b27c2feba6443539b378343717c28174c5ff4c3a51 8318
CLEAN 3802507bffb423d3e9455801d15a5d82937e505f82134e0675e3096ecadbea5b 10414
CLEAN 28e5a4b66341ef88252f41602144354db0aa878c1863262e9950559df4488a1f 15396

  private void readJournal() throws IOException {
    StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
    try {
      String magic = reader.readLine();
      String version = reader.readLine();
      String appVersionString = reader.readLine();
      String valueCountString = reader.readLine();
      String blank = reader.readLine();
      if (!MAGIC.equals(magic)
          || !VERSION_1.equals(version)
          || !Integer.toString(appVersion).equals(appVersionString)
          || !Integer.toString(valueCount).equals(valueCountString)
          || !"".equals(blank)) {
        throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
            + valueCountString + ", " + blank + "]");
      }
      ...
  }

journal文件前5行目前來(lái)看只是起到了判斷文件是否是glide緩存目錄文件的一個(gè)規(guī)則,后面才是緩存文件信息章咧,以空格分開,分別是文件狀態(tài)/文件名/文件大小倦西,目前來(lái)看只有CLEAN狀態(tài)的文件才有文件大小信息
文件狀態(tài)有

  private static final String CLEAN = "CLEAN"; //干凈的數(shù)據(jù)
  private static final String DIRTY = "DIRTY";  //臟數(shù)據(jù),可能正在被修改
  private static final String REMOVE = "REMOVE"; //已被刪除
  private static final String READ = "READ";  //正在被讀取

文件名有后綴赁严,journal文件中文件名信息并不完整扰柠,可能考慮以后緩存多分文件所以是以name.0開始命名
緩存文件。

    private Entry(String key) {
      this.key = key;
      this.lengths = new long[valueCount];
      cleanFiles = new File[valueCount];
      dirtyFiles = new File[valueCount];

      // The names are repetitive so re-use the same builder to avoid allocations.
      StringBuilder fileBuilder = new StringBuilder(key).append('.');
      int truncateTo = fileBuilder.length();
      for (int i = 0; i < valueCount; i++) {
          fileBuilder.append(i);
          cleanFiles[i] = new File(directory, fileBuilder.toString());
          fileBuilder.append(".tmp");
          dirtyFiles[i] = new File(directory, fileBuilder.toString());
          fileBuilder.setLength(truncateTo);
      }
    }

Glide的生命周期

Glide通過(guò)with(context)的方法創(chuàng)建一個(gè)RequestManager疼约,這里要說(shuō)的就是這個(gè)manager的生命周期卤档,

  @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) {
        return get(((ContextWrapper) context).getBaseContext());
      }
    }

    return getApplicationManager(context);
  }

如上代碼如果context是Application,則生命跟隨進(jìn)程程剥,若為Activity或FragmentActivity劝枣,則會(huì)創(chuàng)建一個(gè)空SupportRequestManagerFragment加到Activity中,監(jiān)聽activity的生命周期

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

由此即綁定了RequestManager與context的生命周期,發(fā)生變化后會(huì)通知給RequestManager中的Request和Target

  @Override
  public void onStart() {
    resumeRequests();
    targetTracker.onStart();
  }

  /**
   * Lifecycle callback that unregisters for connectivity events (if the
   * android.permission.ACCESS_NETWORK_STATE permission is present) and pauses in progress loads.
   */
  @Override
  public void onStop() {
    pauseRequests();
    targetTracker.onStop();
  }

  /**
   * Lifecycle callback that cancels all in progress requests and clears and recycles resources for
   * all completed requests.
   */
  @Override
  public void onDestroy() {
    targetTracker.onDestroy();
    for (Target<?> target : targetTracker.getAll()) {
      clear(target);
    }
    targetTracker.clear();
    requestTracker.clearRequests();
    lifecycle.removeListener(this);
    lifecycle.removeListener(connectivityMonitor);
    mainHandler.removeCallbacks(addSelfToLifecycle);
    glide.unregisterRequestManager(this);
  }

以此實(shí)現(xiàn)請(qǐng)求的暫停/繼續(xù)與結(jié)束舔腾,該功能為我們實(shí)現(xiàn)滑動(dòng)時(shí)暫停圖片加載提供基礎(chǔ)幫助溪胶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市稳诚,隨后出現(xiàn)的幾起案子哗脖,更是在濱河造成了極大的恐慌,老刑警劉巖扳还,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件才避,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡氨距,警方通過(guò)查閱死者的電腦和手機(jī)桑逝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)衔蹲,“玉大人肢娘,你說(shuō)我怎么就攤上這事呈础∮呤唬” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵而钞,是天一觀的道長(zhǎng)沙廉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)臼节,這世上最難降的妖魔是什么撬陵? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮网缝,結(jié)果婚禮上巨税,老公的妹妹穿的比我還像新娘。我一直安慰自己粉臊,他們只是感情好草添,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扼仲,像睡著了一般远寸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上屠凶,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天驰后,我揣著相機(jī)與錄音,去河邊找鬼矗愧。 笑死灶芝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播夜涕,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颤专,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了钠乏?” 一聲冷哼從身側(cè)響起栖秕,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晓避,沒(méi)想到半個(gè)月后簇捍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俏拱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年暑塑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锅必。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡事格,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搞隐,到底是詐尸還是另有隱情驹愚,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布劣纲,位于F島的核電站逢捺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏癞季。R本人自食惡果不足惜劫瞳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绷柒。 院中可真熱鬧志于,春花似錦、人聲如沸废睦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)郊楣。三九已至憔恳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間净蚤,已是汗流浹背钥组。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留今瀑,地道東北人程梦。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓点把,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親屿附。 傳聞我的和親對(duì)象是個(gè)殘疾皇子郎逃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354