Glide源碼分析Two

1、Glide基本使用

Glide官方文檔地址
java

1.一、 Example

Glide.with(imageView.context)
    .load(roundRectUrl)
    .apply(RequestOptions().priority(Priority.HIGH))
    .apply(RequestOptions().diskCacheStrategy(DiskCacheStrategy.DATA))
    .apply(RequestOptions.centerCropTransform())
    .apply(
        RequestOptions.bitmapTransform(
            RoundedCornersTransformation(
                radius, 0, RoundedCornersTransformation.CornerType.ALL
            )
        )
    )
    .into(imageView)
複製代碼

GlideDemo GitHub Address
node

1.二、自定義AppGlideModule

Glide4.0以後,GlideModule推薦使用註解(@GlideModule)形式實現android

@GlideModule
class GlideConfigModule : AppGlideModule() {

    override fun applyOptions(context: Context, builder: GlideBuilder) {
        //設置內存大小
        builder.setMemoryCache(LruResourceCache(1024 * 1024 * 100)) // 100M

        //設置圖片緩存大小
        builder.setBitmapPool(LruBitmapPool(1024 * 1024 * 50))

        /**
         * 設置磁盤緩存大小
         */
        // 內部緩存目錄  data/data/packageName/DiskCacheName
        builder.setDiskCache(
            InternalCacheDiskCacheFactory(context, "GlideDemo", 1024 * 1024 * 100)
        )
        // 外部磁盤SD卡
        /*builder.setDiskCache(
            ExternalPreferredCacheDiskCacheFactory(context, "GlideDemo", 1024 * 1024 * 10)
        )*/
    }

    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
        // 替換網絡請求組件,使用OkHttp
        val builder = OkHttpClient.Builder()
        builder.addInterceptor(ProgressInterceptor)
        val okHttpClient = builder.build()

        registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(okHttpClient))
    }

    /**
     * 禁用清單解析
     */
    override fun isManifestParsingEnabled(): Boolean {
        return false
    }
}
複製代碼

2、前言

2.1 一個好的圖片加載框架必備的功能:

當閱讀Glide源碼以前,咱們應該考慮一個問題,假設讓咱們本身去實現一個圖片加載框架,會須要哪些功能?

 一、網絡組件,下載圖片  HttpUrlConnection  OkHttp
 二、請求隊列,線程池   優先級處理,請求的終止
 三、緩存: 內存緩存,本地文件緩存,服務器緩存等等
 四、內存資源回收機制: 先進先出,或者生命週期
 五、更加基礎的組件就是各類圖片資源的轉碼解碼了
git

2.2 Android圖片加載框架對比

  假設你去面試,面試官問你使用什麼圖片框架,你說你用Glide。面試官並非想讓你說Glide,而是你選擇Glide圖片加載框架的緣由和理由。它與其餘圖片加載框架的優缺點。

  這裏引用前輩的文章,就不在單獨再寫一篇了:Android圖片加載框架Fresco,Glide,Picasso對比分析程序員

3、Glide流程分析

3.1 流程圖

這是從網上找的流程圖: github


  你心中會想:這麼簡單??
  我回答你,固然不是這麼簡單,但這確實是最主要最底層的架構,全部的圖片加載框架都同樣,區別在於在這個基礎上延伸的各類小細節,好比多少級緩存、緩存的位置(是緩存在應用的內存中,仍是緩存在系統的共享匿名內存中)、生命週期管理等等。

3.2 Glide使用三步曲源碼分析

這裏就須要去閱讀個人上一篇文章了:Glide源碼分析One面試

3.3 類關係圖

OK,當你看完了3.2後,再看Glide的類關係圖你會至關的清晰,這個圖也是我從網上找的: 算法



接下來就是一些細節問題了

緩存

4、生命週期管理

生命週期管理是在: 第一步Glide.with() 中建立的,具體邏輯在RequestManagerRetriever.get()中實現:

安全

4.1 RequestManagerRetriever.get():

public RequestManager get(@NonNull Context context) {
  、、、
  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);
}
複製代碼

  get()方法重載了不少,參數對應與Glide.with()方法的參數,分別有Context、Activity、FragmentActivity、Fragment、android.support.v4.app.Fragment、View.
參數很重要,參數很重要,參數很重要!!!接下來給出解釋:
  一、RequestManagerRetriever.get(Activity): 參數爲Activity的get()方法

public RequestManager get(@NonNull Activity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();  // FragmentManager直接來自Activity
      return fragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }
複製代碼

  二、RequestManagerRetriever.get(Fragment): 參數爲Fragment的get()方法

public RequestManager get(@NonNull Fragment fragment) {
    if (Util.isOnBackgroundThread()) {
      return get(fragment.getActivity().getApplicationContext());
    } else {
      FragmentManager fm = fragment.getChildFragmentManager();  // FragmentManager來自Fragment
      return supportFragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible());
    }
  }
複製代碼

  三、RequestManagerRetriever.get(View): 參數爲View的get()方法

public RequestManager get(@NonNull View view) {
    ...
    Activity activity = findActivity(view.getContext());
    if (activity == null) {
      return get(view.getContext().getApplicationContext());
    }

    // Support Fragments.
    // Although the user might have non-support Fragments attached to FragmentActivity, searching
    // for non-support Fragments is so expensive pre O and that should be rare enough that we
    // prefer to just fall back to the Activity directly.
    if (activity instanceof FragmentActivity) {
      Fragment fragment = findSupportFragment(view, (FragmentActivity) activity);
      return fragment != null ? get(fragment) : get(activity);
    }

    // Standard Fragments.
    android.app.Fragment fragment = findFragment(view, activity);
    if (fragment == null) {
      return get(activity);
    }
    return get(fragment);
  }
複製代碼

經過View去尋找其所在的Fragment,若是該View在Fragment中,直接走參數爲Fragment的get(Fragment)方法;若是在Activity中,則直接走參數爲Activity的get(Activity)方法

重載的全部get()方法,最終都只有三種狀況:
  1)、 getApplicationManager()
  2)、 fragmentGet()
  3)、 supportFragmentGet()

  • 一、 getApplicationManager() 當參數爲ApplicationContext或者運行在非主線程時,會執行到getApplicationManager()方法:
private RequestManager getApplicationManager(@NonNull Context context) {
  // Either an application context or we are on a background thread.
  if (applicationManager == null) {
    synchronized (this) {
      if (applicationManager == null) {
        // Normally pause/resume is taken care of by the fragment we add to the fragment or
        // activity. However, in this case since the manager attached to the application will not
        // receive lifecycle events, we must force the manager to start resumed using
        // ApplicationLifecycle.

        // TODO(b/27524013): Factor out this Glide.get() call.
        Glide glide = Glide.get(context.getApplicationContext());
        applicationManager =
            factory.build(
                glide,
                new ApplicationLifecycle(),
                new EmptyRequestManagerTreeNode(),
                context.getApplicationContext());
      }
    }
  }
  return applicationManager;
}
複製代碼

看該方法的註釋,Request(也就是緩存)的生命週期是跟隨Application

  • 二、fragmentGet(FragmentManager)
private RequestManager fragmentGet(@NonNull Context context,
      @NonNull android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible); 
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      Glide glide = Glide.get(context);
      // build RequestManager時,將RequestManagerFragment的生命週期(current.getGlideLifecycle())傳入
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }

private RequestManagerFragment getRequestManagerFragment(
      @NonNull final android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    // 經過FragmentManager尋找是否已經添加過RequestManagerFragment,避免重複添加
    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); 
    if (current == null) {
      current = pendingRequestManagerFragments.get(fm);
      if (current == null) {
        current = new RequestManagerFragment();  // 新建一個空佈局的Fragment
        current.setParentFragmentHint(parentHint);
        if (isParentVisible) {
          current.getGlideLifecycle().onStart();
        }
        // 在原有的界面上添加一個空佈局的Fragment,用於生命週期管理
        pendingRequestManagerFragments.put(fm, current); 
        fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
        handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
      }
    }
    return current;
  }
複製代碼

Glide的請求的生命週期管理是經過向頁面添加一個空佈局的Fragment來實現的,上面說參數很重要,這裏給你們解惑。
   一、當參數爲Activity時,空佈局的Fragment直接添加在Activity上,此時的Request的生命週期跟隨Activity;
   二、當參數爲Fragment時,空佈局的Fragment直接添加在Fragment上,此時的Request的生命週期跟隨父Fragment;
   三、單參數爲View時,這時會經過View去找尋該View是在Fragment上仍是在Activity中,分別走以上兩種狀況;

4.2 RequestManager

RequestManager(
    Glide glide,
    Lifecycle lifecycle,
    RequestManagerTreeNode treeNode,
    RequestTracker requestTracker,
    ConnectivityMonitorFactory factory,
    Context context) {
  ···
  // If we are the application level request manager, we may be created on a background thread.
  // In that case we cannot risk synchronously pausing or resuming requests, so we hack around the
  // issue by delaying adding ourselves as a lifecycle listener by posting to the main thread.
  // This should be entirely safe.
  if (Util.isOnBackgroundThread()) {
    mainHandler.post(addSelfToLifecycle);
  } else {
    lifecycle.addListener(this);
  }
  ···
}

private final Runnable addSelfToLifecycle = new Runnable() {
  @Override
  public void run() {
    lifecycle.addListener(RequestManager.this);
  }
};
複製代碼

一、mainHandler.post(addSelfToLifecycle);
  當前線程爲後臺進程、或者是application等級的主線程時,經過mainHandler切回主線程,由於得刷新UI,主線程安全
二、lifecycle.addListener(this);
   直接監聽上一步生成的佈局爲空的fragment的生命週期

5、優先級Priority

5.1 Priority 類

public enum Priority {
  IMMEDIATE,
  HIGH,
  NORMAL,
  LOW,
}
複製代碼

5.2 DecodeJob

  前面分析Glide執行流程時發現,DecodeJob是真正幹活的類,其實現了Runnable接口,可是它還實現了Comparable<DecodeJob<?>>接口.
Engine實例化DecodeJob時,會將優先級屬性傳入DecodeJob中,具體使用在其實現的比較方法中:

/**
 * @return  a negative integer, zero, or a positive integer as  
 * this object is less than, equal to, or greater than the specified        
 **/
@Override
public int compareTo(@NonNull DecodeJob<?> other) {
  int result = getPriority() - other.getPriority();
  if (result == 0) {
    result = order - other.order;
  }
  return result;
}
複製代碼

ThreadPoolExecutor:
  execute()
PriorityBlockingQueue:  // 線程池的任務隊列,選擇高優先級的任務優先執行
  offer()
  siftUpComparable()
同時實現Runnable和Comparable<DecodeJob<?>>接口,線程池自動實現優先級的自動排序。

6、緩存

6.1 LRU緩存算法

在Glide初始化時,會指定一個默認的緩存算法,在GlideBuilder.build()方法中

Glide build(@NonNull Context context) {
  ···
  if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
  }
  ···
}
複製代碼

  使用了LRU(Least Recently Used)算法,核心就是最近最少使用。在算法的內部維護了一個LinkHashMap的鏈表(雙向鏈表),經過put數據的時候判斷是否內存已經滿了,若是滿了,則將最近最少使用的數據給剔除掉,從而達到內存不會爆滿的狀態。


  更重要的一點是,當咱們經過 get()方法獲取數據的時候,這個獲取的數據會從隊列中跑到隊列尾來,從而很好的知足咱們LruCache的算法設計思想。
LinkedHashMap.get():

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMapEntry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMapEntry<K,V> p =
            (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}
複製代碼

6.2 Glide緩存邏輯

緩存邏輯關鍵類Engine

6.2.1 緩存Key

  • Enginr.load():
public synchronized <R> LoadStatus load(...) {
  EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);
  ...
}
複製代碼

model: Url、Uri、FilePath,圖片來源地址

這裏有個前提知識必須得知道:
  DiskCacheStrategy.NONE     什麼都不緩存
  DiskCacheStrategy.DATA      只緩存原來的全分辨率的圖像。
  DiskCacheStrategy.RESOURCE   只緩存最終的圖像,即下降分辨率後的(或者是 轉換後的)
  DiskCacheStrategy.ALL       緩存全部版本的圖像
  DiskCacheStrategy.AUTOMATIC  讓Glide根據圖片資源智能地選擇使用哪種緩存策略 (默認選項)

width,height:從緩存的key中包含寬高能得出,同一張圖片,假設設置的不是緩存原始圖片,不一樣寬高將緩存不一樣的圖片。
優勢: 加載更快,少了圖片寬高處理的過程。
缺點: 更費內存

另外一Android圖片加載框架Picasso,只緩存一張全尺寸的圖片,優勢是佔用內存小,缺點是再次顯時示,須要從新調整大小。

6.2 緩存讀取邏輯

Enginr.load():

public synchronized <R> LoadStatus load() {
 ... 
 EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);

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, callbackExecutor);
 if (VERBOSE_IS_LOGGABLE) {
   logWithTimeAndKey("Added to existing load", startTime, key);
 }
 return new LoadStatus(cb, current);
}

// 生成網絡請求邏輯
...
}

@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
 if (!isMemoryCacheable) {
   return null;
 }
 EngineResource<?> active = activeResources.get(key);
 if (active != null) {
   active.acquire();  // 引用計數器加1
 }
 return active;
}

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
 if (!isMemoryCacheable) {
   return null;
 }

 EngineResource<?> cached = getEngineResourceFromCache(key);  // 將緩存數據從緩存隊列中移出
 if (cached != null) {
   cached.acquire();  // 引用計數器加1
   activeResources.activate(key, cached);  // 將數據放入正在活動的緩存列表中
 }
 return cached;
}
複製代碼

EngineResource.acquire():

synchronized void acquire() {
 if (isRecycled) {
   throw new IllegalStateException("Cannot acquire a recycled resource");
 }
 ++acquired; // 資源被引用的計數器
}
複製代碼

6.3 緩存生成

其實就是在圖片數據加載成功後,放入緩存列表。回調的最終方法在Engine的onEngineJobComplete()方法。

public synchronized void onEngineJobComplete(
   EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
 // A null resource indicates that the load failed, usually due to an exception.
 if (resource != null) {
   resource.setResourceListener(key, this);

   if (resource.isCacheable()) { // 判斷該資源是否須要緩存
     activeResources.activate(key, resource);   // 緩存
   }
 }
 jobs.removeIfCurrent(key, engineJob);
}
複製代碼

6.4 緩存的移除

Engine.onResourceReleased():

public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
 activeResources.deactivate(cacheKey); // 從活動列表移除
 if (resource.isCacheable()) {
   cache.put(cacheKey, resource);  // 放入緩存列表
 } else {
   resourceRecycler.recycle(resource);
 }
}
複製代碼

移除緩存的具體調用流程:
  這裏就得扯到前面的生命週期管理了,RequestManager中實現了LifecycleListener,接收Fragment或Activity的生命週期,這裏主要分析onDestory()方法:
RequestManager.onDestory():

public synchronized 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);
}
複製代碼

RequestTracker.clearRequests() -> SingleRequest.clear() -> Engine.release() -> EngineResource.release()

void release() {
  // To avoid deadlock, always acquire the listener lock before our lock so that the locking
  // scheme is consistent (Engine -> EngineResource). Violating this order leads to deadlock
  // (b/123646037).
  synchronized (listener) {
    synchronized (this) {
      if (acquired <= 0) {
        throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
      }
      if (--acquired == 0) { // 引用數值標準acquire減1
        listener.onResourceReleased(key, this);
      }
    }
  }
}
複製代碼

參考了下面的優秀文章:

  Glide源碼分析
  Glide源碼分析流程思惟導圖 ©愛穿襯衫的程序員

相關文章
相關標籤/搜索