一文搞懂Glide,不懂來打我

1、什么是Glide?

1.1、官方描述

Glide是一個快速高效的Android圖片加載庫,注重于平滑的滾動。Glide提供了易用的API,高性能、可擴展的圖片解碼管道(decode pipeline),以及自動的資源池技術。

  • Glide 支持拉取,解碼和展示視頻快照、圖片和GIF動畫。Glide的Api靈活易用,開發者可以插入和替換成自己喜愛的任何網絡棧。默認情況下,Glide使用的是一個定制化的基于HttpUrlConnection的棧,但同時也提供了與Google Volley和Square OkHttp快速集成的工具庫。
  • Glide 的目標是讓任何形式的圖片列表的滾動盡可能地變得更快、更平滑,但實際上,Glide幾乎能滿足你對遠程圖片的拉取/縮放/顯示的一切需求。

1.2、特點

  • 自動、智能地采樣(downsampling)和緩存(caching),最小化存儲的開銷和解碼的次數;
  • 有效的資源重用,例如字節數組和Bitmap,最小化垃圾回收和堆碎片的影響;
  • 深度的生命周期集成,確保優先處理活躍的Fragment和Activity的請求,同時有利于應用在必要時釋放資源(頁面銷毀等)。

2、Glide怎么使用?

2.1、官方 Glide API

Glide 使用簡明的流式語法API,這是一個非常棒的設計,因為它允許你在大部分情況下一行代碼搞定需求:

// build.gradle文件添加 Glide 依賴
dependencies {
    implementation "com.github.bumptech.glide:glide:4.12.0"
}

// API 簡單使用
Glide.with(context)
    .load(url)
    .into(imageView);

2.2、自定義 Glide API

通過Glide提供的注解,來添加自己定義的API

  • GlideModule 注解用于AppGlideModule
  • GlideExtension 注解用于標識一個擴展Glide API的類,任何拓展Glide API的類都必須使用這個注解來標記,被@GlideExtension注解的類應以工具類的方式實現。
  • GlideOption 注解為RequestOptions添加一個選項。
  • GlideType 添加新的資源類型的支持(GIF,SVG等)

項目需要通過GlideModule注解繼承自AppGlideModule類的子類,并通過GlideExtension注解到工具類上,來擴展自定義Glide API,使用GlideOption、GlideType注解時必須為靜態方法,最后經過Rebuild Project之后,最終會被編譯到XXXRequest.java類

// build.gradle文件添加 Glide 注解處理器
dependencies {
    implementation "com.github.bumptech.glide:glide:4.12.0"
    annotationProcessor "com.github.bumptech.glide:compiler:4.12.0"
}

// Application模塊內,GlideModule注解自定義子類繼承AppGlideModule,可以不用重寫任何方法。
@GlideModule(glideName = "GlideApp")
public class MyGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        // 可以添加一些全局性的options
        super.applyOptions(context, builder);
    }
}

// GlideExtension注解,添加自定義的Glide API
@GlideExtension
public class MyGlideExtensions {
    private MyGlideExtensions() {
    }

    // GlideOption注解,添加自定義的Option
    @GlideOption
    public static BaseRequestOptions<?> myMiniThumb(BaseRequestOptions<?> options, int size) {
        return options.fitCenter().override(size);
    }

    // 自定義decode resource Type
    private static final RequestOptions DECODE_TYPE_GIF = RequestOptions.decodeTypeOf(GifDrawable.class);

    // GlideType注解,添加自定義的資源類型
    @GlideType(GifDrawable.class)
    public static RequestBuilder<GifDrawable> asMyGif(RequestBuilder<GifDrawable> requestBuilder) {
        return requestBuilder
                .transition(new DrawableTransitionOptions())    // 設置用于在加載完成時從占位符到正常顯示的過渡效果
                .apply(DECODE_TYPE_GIF);    // 將自定義的ResourceClass設置到resourceClass參數
    }
}

    // 使用自定義API
    GlideApp.with(context)
            .asMyGif()      // 使用自定義的資源
            .load(url)
            .myMiniThumb(100)   // 使用自定義的Option
            .into(view);

3、Glide加載圖片過程

Glide加載圖片的過程,可以分為三個階段:with、load和into。

Glide 結構圖如下:

3.1、with階段

with方法用于獲取到RequestManager,RequestManager用于管理圖片請求;在創建RequestManager時,根據不同的Context上下文和線程,創建出綁定不同生命周期的組件(Application,Activity,Fragment)的requestManager實例。

RequestManager職責:

  • 用于管理和啟動 Glide 請求的類,通過內部的requestTracker來跟蹤記錄所有的request;
  • 可以使用 Activity、Fragment 等連接生命周期事件來智能地停止、啟動和重啟請求;

通過不同的靜態with方法,獲取擁有不通生命周期的requestManager實例。

    Glide#with(android.app.Activity)
    Glide#with(androidx.fragment.app.FragmentActivity)
    Glide#with(android.app.Fragment)
    Glide#with(androidx.fragment.app.Fragment)
    Glide#with(Context)
    Glide#with(View)

    // 對應到上述with方法,通過不同的get重載方法來創建或檢索 requestManager 對象
    RequestManagerRetriever#get(android.app.Activity)
    RequestManagerRetriever#get(androidx.fragment.app.FragmentActivity)
    RequestManagerRetriever#get(android.app.Fragment)
    RequestManagerRetriever#get(androidx.fragment.app.Fragment)
    RequestManagerRetriever#get(Context)
    RequestManagerRetriever#get(View)

Glide with 流程圖如下:

3.1.1、獲取 Glide 單例

首先從Glide.with(Context)方法開始

# Glide.java
  // 通過retriever 的get方法來獲取requestManager
  public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
  }

  // 獲取 retriever 檢索器,其內部持有RequestManagerFactory,檢索器用于創建或檢索 requestManager 實例
  private static RequestManagerRetriever getRetriever(@Nullable Context context) {
    return Glide.get(context).getRequestManagerRetriever();
  }

  // 獲取Glide,Glide內部持有檢索器
  public static Glide get(@NonNull Context context) {
    // 雙重檢查鎖的方式,獲取 glide 單例
    if (glide == null) {
      // 獲取App模塊內自定義的AppGlideModule類(*GlideModule注解的)
      GeneratedAppGlideModule annotationGeneratedModule =
          getAnnotationGeneratedGlideModules(context.getApplicationContext());
      synchronized (Glide.class) {
        if (glide == null) {
          // 創建 Glide 實例
          checkAndInitializeGlide(context, annotationGeneratedModule);
        }
      }
    }
    return glide;
  }

    // checkAndInitializeGlide方法最終會調用initializeGlide方法
    private static void initializeGlide(
            @NonNull Context context,
            @NonNull GlideBuilder builder,  // builder = new GlideBuilder()
            @Nullable GeneratedAppGlideModule annotationGeneratedModule) {
        Context applicationContext = context.getApplicationContext();

        RequestManagerRetriever.RequestManagerFactory factory =
                annotationGeneratedModule != null
                        // 獲取到創建requestManager的factory,factory會創建自動生成的GlideRequests繼承自RequestManager,其包含GlideType注解的API方法
                        ? annotationGeneratedModule.getRequestManagerFactory()
                        : null;
        builder.setRequestManagerFactory(factory);
        if (annotationGeneratedModule != null) {
            annotationGeneratedModule.applyOptions(applicationContext, builder);
        }
        // 構建 glide 實例,內部會創建默認的 RequestManagerRetriever和RequestManagerFactory等創建requestManager的相關參數。
        Glide glide = builder.build(applicationContext);
        if (annotationGeneratedModule != null) {
            annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
        }
        applicationContext.registerComponentCallbacks(glide);
        // 設置到靜態變量,單例模式
        Glide.glide = glide;
    }

3.1.2、獲取 RequestManager 實例

在Glide創建過程中會 創建檢索器 RequestManagerRetriever,通過檢索器獲取 requestManager,接著分析 RequestManagerRetriever.get(Context)方法

# RequestManagerRetriever.java

    public RequestManager get(@NonNull Context context) {
        // 省略了安全檢查

        if (Util.isOnMainThread() && !(context instanceof Application)) {
            if (context instanceof FragmentActivity) {
                // 獲取 FragmentActivity 級 RequestManager
                return get((FragmentActivity) context);
            } else if (context instanceof Activity) {
                // 獲取 Activity 級 RequestManager
                return get((Activity) context);
            } else if (context instanceof ContextWrapper
                    // 使用 ContextWrapper 附加的 Context 繼續獲取
                    && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
                return get(((ContextWrapper) context).getBaseContext());
            }
        }
        // 獲取 Application 級 RequestManager
        return getApplicationManager(context);
    }

    // 跟蹤 FragmentActivity 方式獲取的 RequestManager
    public RequestManager get(@NonNull FragmentActivity activity) {
        if (Util.isOnBackgroundThread()) {
            // 非主線程時,獲取 Application 級 RequestManager
            return get(activity.getApplicationContext());
        } else {
            assertNotDestroyed(activity);   // activity 銷毀檢查
            frameWaiter.registerSelf(activity);
            FragmentManager fm = activity.getSupportFragmentManager();
            return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
        }
    }

    // 獲取 Fragment 級 RequestManager
    private RequestManager supportFragmentGet(
            @NonNull Context context,
            @NonNull FragmentManager fm,
            @Nullable Fragment parentHint,
            boolean isParentVisible) {
        // 獲取 SupportRequestManagerFragment,其內部持有:
        // 1、ActivityFragmentLifecycle,在 Fragment 的模版方法中通過 lifecycle 回調生命周期事件
        // 2、RequestManagerTreeNode,用于跟蹤記錄嵌套的 Fragment 的RequestManager
        SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint);
        RequestManager requestManager = current.getRequestManager();
        if (requestManager == null) {
            // 獲取全局 glide 單例
            Glide glide = Glide.get(context);
            // 通過工廠方式構建 requestManager 對象
            requestManager =
                    factory.build(
                            glide,
                            // 1. requestManager 通過 lifecycle 注冊listener
                            // 2. 回調生命周期事件,在Fragment生命周期變化時,通知RequestManager實現的LifecycleListener接口方法進行響應
                            current.getGlideLifecycle(),
                            current.getRequestManagerTreeNode(), // 跟蹤嵌套Fragment內的RequestManager
                            context);
            if (isParentVisible) {
                // 開始請求,并設置target顯示
                requestManager.onStart();
            }
            // 給 fragment 設置RequestManger
            current.setRequestManager(requestManager);
        }
        return requestManager;
    }

3.1.3、Glide 生命周期分析

RequestMananger的構造方法中,和創建的用于監聽生命周期事件的Fragment進行關聯,RequestManager實現了LifeCycleListener接口,通過LifeCycle.addListener(this)的方式將觀察者注入生命周期監視器。 RequestManager在實現了LifeCycleListener接口的onStart()/onStop()/onDestory()的方法中,通過RequestTracker來管理請求任務,通過TargetTracker來控制View的顯示效果。

# RequestManager.java

  RequestManager(
      Glide glide,
      Lifecycle lifecycle,
      RequestManagerTreeNode treeNode,
      RequestTracker requestTracker,
      ConnectivityMonitorFactory factory,
      Context context) {
    this.glide = glide;
    this.lifecycle = lifecycle;
    this.treeNode = treeNode;
    this.requestTracker = requestTracker;
    this.context = context;

    connectivityMonitor =
        factory.build(
            context.getApplicationContext(),
            new RequestManagerConnectivityListener(requestTracker));
    // 非主線程,切換到主線程綁定生命周期事件
    if (Util.isOnBackgroundThread()) {
      Util.postOnUiThread(addSelfToLifecycle);
    } else {
      // 監聽生命周期事件
      lifecycle.addListener(this);
    }
    // 監聽網絡變化事件
    lifecycle.addListener(connectivityMonitor);
    ...
  }

 @Override
  public synchronized void onStart() {
    // 恢復請求
    requestTracker.resumeRequests();
    targetTracker.onStart();
  }
  
  @Override
  public synchronized void onStop() {
    // 暫停請求
    requestTracker.pauseRequests();
    targetTracker.onStop();
  }

  @Override
  public synchronized void onDestroy() {
    targetTracker.onDestroy();
    // 清理target
    for (Target<?> target : targetTracker.getAll()) {
      clear(target);
    }
    targetTracker.clear();
    // 清除請求
    requestTracker.clearRequests();
    lifecycle.removeListener(this);
    lifecycle.removeListener(connectivityMonitor);
    Util.removeCallbacksOnUiThread(addSelfToLifecycle);
    glide.unregisterRequestManager(this);
  }
 
 # RequestTracker.java
 
   public void resumeRequests() {
    isPaused = false;
    // 處理所有的請求
    for (Request request : Util.getSnapshot(requests)) {
      if (!request.isComplete() && !request.isRunning()) {
        // 重新發起請求
        request.begin();
      }
    }
    // pending隊列保存未完成并排隊等待再次運行的請求。 列表來維護對這些請求的硬引用,確保它們在開始運行之前或暫停時不會被垃圾收集,在重啟開啟請求時清理。
    pendingRequests.clear();
  }

3.1.4、Glide 網絡變化分析
  1. 設置自定義的網絡監聽方式;
  2. 未設置自定義網絡監聽方式,采用默認方式;
  • 有網絡權限時,通過 ConnectivityManager.android.net.conn.CONNECTIVITY_CHANGE 廣播的方式監聽網絡事件。
  • 無網絡權限時,不監聽。
# DefaultConnectivityMonitorFactory.java
  // 默認監聽網絡變化廣播
  public ConnectivityMonitor build(
      @NonNull Context context, @NonNull ConnectivityMonitor.ConnectivityListener listener) {
    int permissionResult = ContextCompat.checkSelfPermission(context, NETWORK_PERMISSION);
    // 檢查網絡權限
    boolean hasPermission = permissionResult == PackageManager.PERMISSION_GRANTED;
    return hasPermission
        ? new DefaultConnectivityMonitor(context, listener)
        : new NullConnectivityMonitor();
  }

  # ReuquestManager.java
  // 網絡連接變化事件處理
  private class RequestManagerConnectivityListener
      implements ConnectivityMonitor.ConnectivityListener {
    @GuardedBy("RequestManager.this")
    private final RequestTracker requestTracker;

    RequestManagerConnectivityListener(@NonNull RequestTracker requestTracker) {
      this.requestTracker = requestTracker;
    }

    @Override
    public void onConnectivityChanged(boolean isConnected) {
      if (isConnected) {
        synchronized (RequestManager.this) {
          // 通過 requestTracker 重新發起請求
          requestTracker.restartRequests();
        }
      }
    }
  }

3.2、load階段

load階段創建出 RequestBuilder 對象,為每個請求封裝 glide,requestManager,glideContext,model,requestOptions 等參數。

RequestBuilder extends BaseRequestOptions {}

3.3、into階段

into階段可以分為四個過程:

  1. target 綁定 request 并發起 request 請求;
  2. 數據加載;
  3. 資源解碼;
  4. 資源緩存和顯示;

Glide into 流程圖如下:

# RequestBuilder.java

  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    // 省略了安全檢查
    // 省略了 options 變換

    return into(
    // 獲取 ImageView 載體
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        // 主線程
        Executors.mainThreadExecutor());
  }

  private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      BaseRequestOptions<?> options,
      Executor callbackExecutor) {

    // 構建 Request 加載請求
    Request request = buildRequest(target, targetListener, options, callbackExecutor);

    Request previous = target.getRequest();
    // 載體原有請求與新請求比對,請求等效時采用原有請求進行加載
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      if (!Preconditions.checkNotNull(previous).isRunning()) {
        // 當請求未開始時,開始啟動請求,加載數據
        previous.begin();
      }
      return target;
    }

    // 清理原來的請求
    requestManager.clear(target);
    // 將請求與 Target 進行綁定
    target.setRequest(request);
    // 記錄請求,并啟動請求
    requestManager.track(target, request);

    return target;
  }

# RequestManager.java

  synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
    targetTracker.track(target);
    // 記錄請求,并啟動請求
    requestTracker.runRequest(request);
  }

# RequestTracker.java

  public void runRequest(@NonNull Request request) {
    // 記錄請求
    requests.add(request);
    if (!isPaused) {
    // 啟動請求
      request.begin();
    } else {
      request.clear();
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Paused, delaying request");
      }
      // 如果請求處于暫停狀態,則記錄加載請求,等狀態恢復時,進行重新啟動
      pendingRequests.add(request);
    }
  }

# SingleRequest.java

  public void begin() {
    synchronized (requestLock) {
      // 省略了安全檢查

      if (status == Status.COMPLETE) {
        // 直接從請求中緩存的 Resource 返回,回調給 ViewTarget 顯示資源
        onResourceReady(
            resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
        return;
      }


      status = Status.WAITING_FOR_SIZE;
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
    // 從內存,本地或者遠端加載數據
        onSizeReady(overrideWidth, overrideHeight);
      } else {
        target.getSize(this);
      }

      if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
          && canNotifyStatusChanged()) {
        // 顯示默認占位圖
        target.onLoadStarted(getPlaceholderDrawable());
      }
    }
  }

  public void onSizeReady(int width, int height) {
    stateVerifier.throwIfRecycled();
    synchronized (requestLock) {
      if (status != Status.WAITING_FOR_SIZE) {
        return;
      }
      status = Status.RUNNING;
      // 開啟加載,engine 是 Glide創建時構造的
      loadStatus =
          engine.load(
              glideContext,
              model,
              requestOptions.getSignature(),
              this.width,
              this.height,
              requestOptions.getResourceClass(),
              transcodeClass,
              priority,
              requestOptions.getDiskCacheStrategy(),
              requestOptions.getTransformations(),
              requestOptions.isTransformationRequired(),
              requestOptions.isScaleOnlyOrNoTransform(),
              requestOptions.getOptions(),
              requestOptions.isMemoryCacheable(),
              requestOptions.getUseUnlimitedSourceGeneratorsPool(),
              requestOptions.getUseAnimationPool(),
              requestOptions.getOnlyRetrieveFromCache(),
              this,
              callbackExecutor);
    }
  }

3.3.1、數據加載流程

數據加載分為兩個部分,一部分是內存(活躍資源 HashMap 和內存 LruCache)中加載;另一部分是從本地或遠端加載。

  • 緩存策略,決定緩存的數據類型:
緩存策略 是否支持轉換資源緩存 是否支持原始數據緩存
NONE 不支持 不支持
ALL 數據源不是磁盤與內存緩存時,支持 數據源是遠程,支持
RESOURCE 支持 不支持
DATA 不支持 數據源不是磁盤與內存緩存時,支持
AUTOMIC 數據源是本地,支持 數據源是遠程,支持
  • 緩存數據類型對應的加載器:
緩存類型 Generator 描述
RESOURCE ResourceCacheGenerator 從包含采樣/轉換資源數據的緩存文件生成DataFetcher
DATA DataCacheGenerator 從包含原始未修改源數據的緩存文件生成DataFetcher
SOURCE SourceGenerator 使用注冊的ModelLoaders和為加載提供的模型從原始源數據生成DataFetcher
FINISHED NULL NULL
3.3.1.1、內存數據加載流程

內存中緩存的數據分為兩種,一種是活躍資源的Map緩存,一種是LRU緩存,數據首先會從這兩個緩存中加載,如果有則直接返回使用,如果資源為null,則從本地或遠端數據加載數據。

內存數據加載流程圖如下:

# Engine.java

  public <R> LoadStatus load() {

    EngineKey key =
        keyFactory.buildKey(
            model,
            signature,
            width,
            height,
            transformations,
            resourceClass,
            transcodeClass,
            options);

    EngineResource<?> memoryResource;
    synchronized (this) {
      // 從內存中加載數據
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

      if (memoryResource == null) {
        // 從本地或者遠端加載數據
        return waitForExistingOrStartNewJob(
            glideContext,
            model,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            options,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache,
            cb,
            callbackExecutor,
            key,
            startTime);
      }
    }

    // 將內存中加載的資源回調給 ViewTarget 顯示
    cb.onResourceReady(
        memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
    return null;
  }

  private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) {
    if (!isMemoryCacheable) {
      return null;
    }
    // 從獲取資源緩存中加載
    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      return active;
    }
    // 從內存緩存中加載
    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
      return cached;
    }

    return null;
  }

  // 加載活躍資源
  private EngineResource<?> loadFromActiveResources(Key key) {
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }
    return active;
  }

  // 加載內存資源
  private EngineResource<?> loadFromCache(Key key) {
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      // 緩存活躍資源,弱引用方式保存到 Map 中
      activeResources.activate(key, cached);
    }
    return cached;
  }

  private EngineResource<?> getEngineResourceFromCache(Key key) {
    // 從 LruCache 中加載
    Resource<?> cached = cache.remove(key);

    final EngineResource<?> result;
    if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      result = (EngineResource<?>) cached;
    } else {
      // 使用 EngineResource包裝緩存資源
      result =
          new EngineResource<>(
              cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
    }
    return result;
  }

3.3.1.2、本地或遠端數據加載流程

當從內存中沒有找到資源時,會開啟本地或遠端數據加載的操作,此過程是異步行為,通過線程池方式提交加載任務啟動加載請求。

本地或者遠程數據加載流程圖如下:

# Engine.java

  private <R> LoadStatus waitForExistingOrStartNewJob() {

    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb, callbackExecutor);
      return new LoadStatus(cb, current);
    }

    // 創建 EngineJob
    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);
    // 創建 DecodeJob,解碼流程
    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);

    jobs.put(key, engineJob);

    engineJob.addCallback(cb, callbackExecutor);
    engineJob.start(decodeJob); // 開啟異步加載流程,decodeJob 實現了 Runnable 接口

    return new LoadStatus(cb, engineJob);
  }

# DecodeJob.java

  public void run() {
      // 匹配 DataFetcherGenerator 進行數據加載
      runWrapped();
  }

  // 從 runWrapped 開始,會調用到 runGenerators 方法
  private void runGenerators() {
    boolean isStarted = false;
    while (!isCancelled
        && currentGenerator != null
        // 開始加載數據,ResourceCacheGenerator,DataCacheGenerator,SourceGenerator
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();

      if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }
    }
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      notifyFailed();
    }
  }

# ResourceCacheGenerator.java,源碼分析該DataFetcherGenerator

  public boolean startNext() {
    List<Key> sourceIds = helper.getCacheKeys();
    while (modelLoaders == null || !hasNextModelLoader()) {
      // Resource 是轉碼后的資源類型,對應的 Key 為 ResourceCacheKey
      currentKey =
          new ResourceCacheKey( // NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      cacheFile = helper.getDiskCache().get(currentKey);
      if (cacheFile != null) {
        sourceKey = sourceId;
        // 根據 CacheFile 匹配出所有符合的 ModelLoaders
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      // 通過 ModelLoader 構造出 LoadData
      loadData =
          modelLoader.buildLoadData(
              cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        // 通過 DataFetcher 開始加載數據
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }

    return started;
  }

# FileLoader.java 內對應的 FileFetcher 嵌套類

  public void loadData(@NonNull Priority priority, @NonNull DataFetcher.DataCallback<? super Data> callback) {
    try {
         // file 為 modelLoader.buildLoadData 時傳入的 model,即 cacheFile
         // opener 是 FileInputStream
        data = opener.open(file);
        // 將打開的文件流數據,成功回調
        callback.onDataReady(data);
    } catch (FileNotFoundException e) {
    // 失敗回調
        callback.onLoadFailed(e);
    }
  }

3.3.2、資源解碼流程

在數據被加載成功之后,會進行資源的解碼操作,轉成Android可以支持顯示的資源數據。

Glide 解碼資源流程圖如下:

# DecodeJob.java

  public void onDataFetcherReady(
        Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {
    // 省略了變量的賦值操作
    if (Thread.currentThread() != currentThread) {
        // 切換到指定線程進行資源解碼操作
        runReason = DecodeJob.RunReason.DECODE_DATA;
        callback.reschedule(this);
    } else {
        GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
        try {
            // 資源解碼
            decodeFromRetrievedData();
        } finally {
            GlideTrace.endSection();
        }
    }
  }

  private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
        throws GlideException {
    LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
    // 通過 LoadPath 進行解碼,loadPath 內有 decodePaths
    return runLoadPath(data, dataSource, path);
  }

# DecodePath.java

  public Resource<Transcode> decode(
      DataRewinder<DataType> rewinder,
      int width,
      int height,
      @NonNull Options options,
      DecodeCallback<ResourceType> callback)
      throws GlideException {
    // 解碼資源,DecodePath內部會匹配注冊的Decoder進行decode操作,解碼出原始的Resource
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
    // 資源轉碼,DecodeJob是實現方,內部通過匹配注冊的Transformation進行transform操作
    // 最后根據緩存策略,決定緩存轉碼資源或者原始資源
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
    // 轉換為新資源類型,eg: Bitmap -> BitmapDrawable
    return transcoder.transcode(transformed, options);
  }

3.3.3、資源緩存和顯示流程

資源在經過加載和解碼之后,進行轉碼階段時,根據DataSource判斷資源是緩存原始資源還是轉碼資源,策略如下:

EncodeStrategy 緩存到磁盤策略描述 緩存Key
SOURCE 將原始資源數據緩存到磁盤 DataCacheKey
TRANSFORMED 將轉碼后的資源數據緩存到磁盤 ResourceCacheKey
NONE 數據不緩存到磁盤

資源緩存方案主要是表中三種:

緩存方案 方案介紹
ActiveResource 內存緩存,采用Map<Key, WeakReference> 弱引用的方案保存正在使用的資源,防止出現LRU導致正在使用的資源被回收
LruCache 內存緩存,采用最近最少使用的策略,保證資源的使用效率,且盡量避免出現OOM問題
DiskLruCache 磁盤緩存,最近最少使用的策略,減少對網絡耗時的請求操作

在經過DataFetcher.loadData數據提取之后,進行數據的一個緩存,緩存分兩種,一種是緩存到磁盤(默認是應用data目錄下的image_manager_disk_cache文件,默認大小為250M),一種是緩存到內存。

Glide 資源緩存和顯示流程圖如下:

// 1、緩存活躍資源

# Engine.java

  public synchronized void onEngineJobComplete(
      EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    if (resource != null && resource.isMemoryCacheable()) {
      // 活躍資源緩存
      activeResources.activate(key, resource);
    }
  }

# ActiveResources.java,內部通過 Map<Key, ResourceWeakReference> 緩存

  synchronized void activate(Key key, EngineResource<?> resource) {
    // 通過弱引用的方式持有資源
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);

    // 將弱引用資源放入Map
    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }

// 2、內存緩存資源,通過 LRU 最近最少使用方案

# Engine.java

  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isMemoryCacheable()) {
      // Cache 是 MemoryCache,內部持有 LruCache
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
    }
  }

# LruResourceCache.java,繼承自LruCache

public synchronized Y put(@NonNull T key, @Nullable Y item) {
    final int itemSize = getSize(item);
    // 超出數量上限時,直接返回,并清理當前需要緩存的資源
    if (itemSize >= maxSize) {
      onItemEvicted(key, item);
      return null;
    }

    if (item != null) {
      currentSize += itemSize;
    }
    // 將資源放入 LinkedHashMap,會通過 afterNodeAccess()方法將最近訪問數據放在雙鏈表的尾部
    @Nullable Entry<Y> old = cache.put(key, item == null ? null : new Entry<>(item, itemSize));
    if (old != null) {
      currentSize -= old.size;

      // 對廢棄資源進行清理
      if (!old.value.equals(item)) {
        onItemEvicted(key, old.value);
      }
    }
    // 重新計算緩存空間,大小超出時,則移除最久未使用的資源
    evict();

    return old != null ? old.value : null;
  }

// 3、磁盤緩存資源,采用 DiskLruCache 方案

# DecodeJob.java 的嵌套類 DeferredEncodeManager

  void encode(DiskCacheProvider diskCacheProvider, Options options) {
    GlideTrace.beginSection("DecodeJob.encode");
    try {
    // 寫入資源時,根據緩存策略,已經確定 toEncode 資源是轉碼資源還是原始資源
      diskCacheProvider
          .getDiskCache()
          .put(key, new DataCacheWriter<>(encoder, toEncode, options));
    } finally {
      // 資源回收
      toEncode.unlock();
      GlideTrace.endSection();
    }
  }

// DiskLruCacheWrapper.java 內部持有 DiskLruCache

  private synchronized DiskLruCache getDiskCache() throws IOException {
    if (diskLruCache == null) {
      // 打開磁盤緩存器
      diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);
    }
    return diskLruCache;
  }

  public void put(Key key, Writer writer) {
    // 將 Key 安全的轉成 sha256 字符串編碼,key會被保存在LruCache內,獲取時會加鎖
    String safeKey = safeKeyGenerator.getSafeKey(key);

    // 加鎖,進行安全的寫入操作
    writeLocker.acquire(safeKey);
    try {
      try {
        // 獲取 DiskLruCache
        DiskLruCache diskCache = getDiskCache();
        // 如果資源已緩存,則退出
        Value current = diskCache.get(safeKey);
        if (current != null) {
          return;
        }

    // 獲取資源寫入的編輯器
        DiskLruCache.Editor editor = diskCache.edit(safeKey);
        if (editor == null) {
          throw new IllegalStateException("Had two simultaneous puts for: " + safeKey);
        }
        try {
          // 寫入到文件內
          File file = editor.getFile(0);
          if (writer.write(file)) {
            editor.commit();
          }
        } finally {
          editor.abortUnlessCommitted();
        }
      } catch (IOException e) {
        if (Log.isLoggable(TAG, Log.WARN)) {
          Log.w(TAG, "Unable to put to disk cache", e);
        }
      }
    } finally {
      // 釋放鎖
      writeLocker.release(safeKey);
    }
  }

# Extectors.java
  // 主線程池,用于顯示圖片
  private static final Executor MAIN_THREAD_EXECUTOR =
      new Executor() {
        @Override
        public void execute(@NonNull Runnable command) {
          Util.postOnUiThread(command);
        }
      }

4、總結

使用建議:

1. 結合自身需求,決定是否考慮在AppGlideModule內應用全局性的Options。
2. 在使用Glide時,盡可能的保證context上下文符合預期,防止產生內存泄漏問題。
3. 在滑動事件時,可以考慮結合RequestManager內的 resume 和 pause 來處理快速滑動產生的卡頓問題。

總結,Glide 框架主要分為三個部分:

  • 第一個部分: with 階段,注冊編解碼器,初始化變量(Glide,RequestManager,Engine等)和綁定頁面生命周期等操作,用于管理請求和監聽生命周期事件。
  • 第二個部分:load 階段,為每個請求配置單獨的 Option,比如:設置 width,height,DiskCacheStrategy,Transaction等。
  • 第三個部分:into 階段,最復雜的階段,啟動請求,開始加載數據,對數據進行解碼和轉碼操作,緩存解碼數據或者原始數據,顯示視圖。

作者:jaymzyang
轉載于:https://juejin.cn/post/7044079102839488543
如有侵權,請聯系刪除!

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容