Glide源碼分析以及三級緩存原理

Glide是Android端開源圖片加載庫,能夠幫助我們下載、緩存、展示多種格式圖片。也是現在主流圖片加載框架之一。源碼內部究竟是如何實現的呢?講解主流程,簡略分析。

用法如下:

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

我這裏拆分爲三步分析:

一、with(context)

點擊源碼查看到是多個重載方法activity、fragment、view等等,下面用其中一個方法來展示

  @NonNull
  public static RequestManager with(@NonNull Activity activity) {
    return getRetriever(activity).get(activity);
  }
  @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();
  }

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

下面的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));
    }
  }

二、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);
  }

三、into(imageView)

上一步拿到了RequestBuilder對象,調用into可知有2個重載方法。into的參數就是最終顯示的控件。

編輯

into方法內部代碼分支很多,代碼龐大,所以只需走主流程如何顯示ImageView的實現即可。當into內部代碼執行完成後回到 buildImageViewTarget方法,這個方法是顯示使用的,通過Executors.mainThreadExecutor())來切主線程,最終顯示控件。

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

點擊到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;
    }

這裏處理請求

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

Request previous = target.getRequest();

將請求對象裝到集合中,並且有加鎖處理,運用於多線程的併發請求。

url請求走如下:

編輯

網絡請求完成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));
      }
    }
  }

最後回到了ImageViewTarget類,顯示控件。這就是整體簡略主流程。

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

四、緩存原理分析

當加載圖片會走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個數據進入時,移除最先未使用的。畫圖理解一哈:

編輯

LruCache類實際上是對LinkedHashMap進行的封裝。上代碼證明:

編輯

值得注意的是,第三個參數true代表訪問排序

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

三、活動緩存的意義

示例場景:加入maxSize=3時,有新元素添加,此刻正回收1元素,剛好頁面又使用1元素。這時候如果1元素被回收,就會找不到1元素從而崩潰。所以設計了活動緩存

編輯

增加的活動緩存區解決上面的問題,畫圖方便理解:

編輯

總結:1、當元素在使用時,將從內存緩存(二級緩存)移動到活動緩存(一級緩存);

         2、當元素未使用時,將從活動緩存釋放資源,然後把該元素從活動緩存移動到內存緩存;

三級緩存策略的使用總結:

1、優先從活動緩存讀取
2、活動緩存沒有,再內存緩存中讀取
3、內存緩存沒有,再去磁盤緩存讀取
4、磁盤緩存沒有,再去網絡獲取本地文件讀取

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章