Android 開發框架 Glide 原理解析


一、復用內存塊

復用內存塊只能在3.0以后使用。2.3上,bitmap的數據是存儲在native的內存區域,並不是在Dalvik的內存堆上。復用內存塊,不需要在重新給這個bitmap申請一塊新的內存,避免了一次內存的分配和回收,從而改善了運行效率。

在4.4之前,只能重用相同大小的bitmap的內存區域,而4.4之后你可以重用任何bitmap的內存區域,只要這塊內存比將要分配內存的bitmap大就可以。這里最好的方法就是使用LRUCache來緩存bitmap,后面來了新的bitmap,可以從cache中按照api版本找到最適合重用的bitmap,來重用它的內存區域。
 
復用內存塊類似對象池的技術原理,避免內存的頻繁的創建和銷毀帶來性能的損耗。使用inBitmap能高提升bitmap的循環效率。

二、引用&引用隊列

java.lang.ref.Reference 為 軟(soft)引用、弱(weak)引用、虛(phantom)引用的父類。

因為Reference對象和垃圾回收密切配合實現,該類可能不能被直接子類化。可以理解為Reference的直接子類都是由jvm定制化處理的,因此在代碼中直接繼承於Reference類型沒有任何作用。但可以繼承jvm定制的Reference的子類。其內部提供2個構造函數,一個帶queue,一個不帶queue。其中queue的意義在於,我們可以在外部對這個queue進行監控。即如果有對象即將被回收,那么相應的reference對象就會被放到這個queue里。我們拿到reference,就可以再作一些事務。

而如果不帶的話,就只有不斷地輪詢reference對象,通過判斷里面的get是否返回null( phantomReference對象不能這樣作,其get始終返回null,因此它只有帶queue的構造函數 )。這兩種方法均有相應的使用場景,取決於實際的應用。如weakHashMap中就選擇去查詢queue的數據,來判定是否有對象將被回收。而ThreadLocalMap,則采用判斷get()是否為null來作處理。

ReferenceQueue名義上是一個隊列,但實際內部並非有實際的存儲結構,它的存儲是依賴於內部節點之間的關系來表達。可以理解為queue是一個類似於鏈表的結構,這里的節點其實就是reference本身。可以理解為queue為一個鏈表的容器,其自己僅存儲當前的head節點,而后面的節點由每個reference節點自己通過next來保持即可。
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.LinkedList;

public class WeakReferenceDemo {

private static final ReferenceQueue<VeryBig> referenceQueue = new ReferenceQueue<VeryBig>();

public static void main(String args[]) {
int size = 10;
LinkedList<WeakReference<VeryBig>> weakList = new LinkedList<WeakReference<VeryBig>>();
for (int i = 0; i < size; i++) {
weakList.add(new VeryBigWeakReference(new VeryBig("Weak Reference " + i), referenceQueue));
System.out.println("Just created weak: " + weakList.getLast().get().name);
}

System.gc();
try { // 等待上面的垃圾回收線程運行完成
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
checkQueue();
System.out.println("--------------------------------------------------");
checkQueue();
}


public static void checkQueue() {
Reference<? extends VeryBig> ref = null;
while ((ref = referenceQueue.poll()) != null) {
if (ref != null) {
System.out.println("In queue: " + ((VeryBigWeakReference) (ref)).name + "....Class Name = " + ref.getClass().getName());
}
}
}

static class VeryBig {

public String name;

// 占用空間,讓線程進行回收
byte[] b = new byte[2 * 1024];

public VeryBig(String name) {
this.name = name;
}

protected void finalize() {
System.out.println("Finalizing VeryBig -> " + name);
}
}

static class VeryBigWeakReference extends WeakReference<VeryBig> {

public String name;

public VeryBigWeakReference(VeryBig big, ReferenceQueue<VeryBig> referenceQueue) {
super(big, referenceQueue);
this.name = big.name;
}

protected void finalize() {
System.out.println("Finalizing VeryBigWeakReference " + name);
}
}
}

三、Glide 內存緩存機制

一般的圖片加載庫,都是通過內存緩存LruCache、磁盤緩存DiskLruCache中去拿數據,那么Glide也是這樣么?

Glide的緩存可以分為兩種,第一種是內存緩存,第二種是硬盤緩存。

其中內存緩存又包括活動緩存(WeakReference Cache)和 內存緩存(LruCache)。硬盤緩存就是DiskLruCache。

Glide 通過內存緩存獲取數據的流程圖如下:

其中Engine加載活動緩存和弱引用緩存的核心邏輯代碼如下:

public synchronized <R> LoadStatus load(
      
    // 獲取資源的 key ,參數有8個
    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;
    }
    // 通過 LruCache
    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;
    }

    // 網絡加載機制......

其中,Glide從活動緩存(弱引用)里面獲取的代碼如下:

  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
    EngineResource<?> active = activeResources.get(key);
    //如果能拿到資源,則計數器 +1
    if (active != null) {
      active.acquire();
    }

    return active;
  }

#ActiveResources#get()
  @Nullable
  synchronized EngineResource<?> get(Key key) {
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }

    EngineResource<?> active = activeRef.get();
    if (active == null) {
      cleanupActiveReference(activeRef);
    }
    return active;
  }

#EngineResource#acquire()
  synchronized void acquire() {
    if (isRecycled) {
      throw new IllegalStateException("Cannot acquire a recycled resource");
    }
    ++acquired;
  }

 

從上面的代碼可以看出來,activeEngineResources 為實現了弱引用的 hasmap,通過 key 拿到弱引用的對象,如果獲取不到,則可能是因為GC導致對象被回收了,則從 map 中移除;如果拿到對象,則引用計數 acquired +1。

這里說明一下為什么要使用弱引用來緩存當前活躍的資源,而不是像我們之前理解的圖片加載框架一般從LruCache獲取。這樣是為了通過弱引用緩存正在使用的強引用資源,又不阻礙系統需要回收的無引用資源。當前資源正在使用的時候,不會被LruCache算法回收。

 

Glide從LRU內存緩存里面獲取的代碼如下:

// 從 lrucache 獲取對象
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);

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

    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }

 

可以看到其邏輯為:從LRUCache中獲取對象,如果對象不為空,則通過activeResources.activate(key, cached); 把它加入弱引用中,且從 LruCache 刪除。且 調用 acquire() 讓計數器 +1.

綜合上述的邏輯,可以看出:Glide 的內存緩存的流程是這樣的,先從弱引用中取對象,如果存在,引用計數+1,如果不存在,從 LruCache 取,如果存在,則引用計數+1,並把它存到弱引用中,且自身從 LruCache 移除。

 

四、Glide 硬盤緩存機制

Glide 的硬盤策略可以分為如下幾種:

  • DiskCacheStrategy.RESOURCE :只緩存解碼過的圖片
  • DiskCacheStrategy.DATA :只緩存原始圖片
  • DiskCacheStrategy.ALL : 即緩存原始圖片,也緩存解碼過的圖片啊, 對於遠程圖片,緩存 DATA 和 RESOURCE;對本地使用 只緩存 RESOURCE。
  • DiskCacheStrategy.NONE :不使用硬盤緩存
  • DiskCacheStrategy.AUTOMATIC :默認策略,會對本地和和遠程圖片使用最佳的策略;對下載網絡圖片,使用 DATA,對於本地圖片,使用 RESOURCE

 

 硬盤緩存時通過在 EngineJob 中的 DecodeJob 中完成的,先通過ResourcesCacheGenerator、DataCacheGenerator 看是否能從 DiskLruCache 拿到數據,如果不能,從SourceGenerator去解析數據,並把數據存儲到 DiskLruCache 中,后面通過 DataCacheGenerator 的 startNext() 去分發 fetcher 。
最后會回調 EngineJob 的 onResourceReady() 方法了,該方法會加載圖片,並把數據存到弱引用中。

五、Glide 生命周期管理機制

生命周期管理主要涉及兩個類:Glide.class和RequestManagerRetriever.class,主要用來獲得RequestManager。

//with返回一個RequestManager
public static RequestManager with(Activity activity) {
    return getRetriever(activity).get(activity);
}
//無論調用的是哪個with重載方法,最后都會到這里
public RequestManager get(Activity activity) {
    if (Util.isOnBackgroundThread()) {
        return get(activity.getApplicationContext());
    } else {
        assertNotDestroyed(activity);
        android.app.FragmentManager fm = activity.getFragmentManager();
        return fragmentGet(activity, fm, null);
    }
}

//這里新建了一個沒有視圖的RequestManagerFragment 
private RequestManager fragmentGet(Context context,
                                   android.app.FragmentManager fm,
                                   android.app.Fragment parentHint) {
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
        Glide glide = Glide.get(context);
     //綁定requestManager和Fragment的Lifecycle
        requestManager =
                factory.build(
                        glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
        current.setRequestManager(requestManager);
    }
    return requestManager;
}

RequestManagerFragment.class中持有一個lifecycle,在Fragment進入關鍵生命周期時會主動通知lifecycle執行相關方法

public class RequestManagerFragment extends Fragment {
  ...
  private final ActivityFragmentLifecycle lifecycle;
  ...
 @Override
  public void onStart() {
    super.onStart();
    lifecycle.onStart();
  }

  @Override
  public void onStop() {
    super.onStop();
    lifecycle.onStop();
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    lifecycle.onDestroy();
  } 
}

ActivityFragmentLifecycle.class中持有一個lifecycleListeners,在Fragment進入關鍵生命周期時Lifecycle會通知他的所有Listener

class ActivityFragmentLifecycle implements Lifecycle {
 ...
  private final Set<LifecycleListener> lifecycleListeners;void onStart() {
    isStarted = true;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onStart();
    }
  }

  void onStop() {
    isStarted = false;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onStop();
    }
  }

  void onDestroy() {
    isDestroyed = true;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onDestroy();
    }
  }
  ...
}

RequestManger.class關鍵生命周期中處理加載任務

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

@Override
public void onStop() {
    pauseRequests();
    targetTracker.onStop();
}

@Override
public void onDestroy() {
    targetTracker.onDestroy();
    for (Target<?> target : targetTracker.getAll()) {
        clear(target);
    }
    targetTracker.clear();
    requestTracker.clearRequests();
}

Glide在加載綁定了Activity的生命周期。

在Activity內新建一個無UI的Fragment,這個特殊的Fragment持有一個Lifecycle。通過Lifecycle在Fragment關鍵生命周期通知RequestManger進行相關的操作。

在生命周期onStart時繼續加載,onStop時暫停加載,onDestory是停止加載任務和清除操作。

六、如何自己寫一個圖片加載框架

異步加載數據,需要考慮使用 線程池 加載。同時需要在展示的時候進行線程切換,那么 Handler 的使用也是必要的。

為了優化數據加載,需要考慮使用 緩存機制, 如使用 LruCache、DiskLruCache。

為了防止加載圖片出現OOM,可以考慮使用 弱引用圖片壓縮內存復用 等方式。

為了防止內存泄露,需要注意ImageView的正確使用和生命周期管理。

為了保證App的正常進行,避免因為圖片加載導致OOM,需要通過實現 ComponentCallbacks2 進行各個步驟的內存管理。

 

參考文章:

Glide 緩存機制解析(為啥使用弱引用)

Android面試題:講一講Glide的原理

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM