推薦閱讀:
Glide是一個快速高效的Android圖片加載庫,注重於平滑的滾動。Glide提供了易用的API,高性能、可擴展的圖片解碼管道,以及自動的資源池技術。為了讓用戶擁有良好的App使用體驗,圖片不僅要快速加載,而且還不能因為過多的主線程I/O或頻繁的垃圾回收導致頁面的閃爍和抖動現象。
Glide使用了多個步驟來確保在Android上加載圖片盡可能的快速和平滑:
1.自動、智能地下采樣(downsampling)和緩存(caching),以最小化存儲開銷和解碼次數;
2.積極的資源重用,例如字節數組和Bitmap,以最小化昂貴的垃圾回收和堆碎片影響;
3.深度的生命周期集成,以確保僅優先處理活躍的Fragment和Activity的請求,並有利於應用在必要時釋放資源以避免在后台時被殺掉。
本文將依次分析Glide如下問題:
1.Glide圖片加載的大致加載流程
2.Glide圖片加載的生命周期的集成
3.Glide的圖片緩存機制
4.對象池優化,減少內存抖動
5.Bitmap的解碼
6.網絡棧的切換
1.Glide圖片加載的大致加載流程
Glide使用簡明的流式語法Api,大部分情況下一行代碼搞定圖片顯示,比如:
Glide.with(activity).load(url).into(imageView)
就以上述調用簡易分析圖片加載流程,如下圖:
Glide圖片加載流程圖
ResourceDiskCache包含了降低采樣、轉換的圖片資源,DataDiskCache為原始圖片資源。RemoteSurce即從遠端服務器拉取資源。從ResourceCache、RemoteSource 加載圖片都涉及到ModelLoader、 解碼與轉碼,以及多層回調才到顯示圖片,流程比較復雜,這里就不詳述了。
2.Glide圖片加載的生命周期的集成
從Glide.with(host)調用出發,跟蹤創建RequestManager的過程,可以推斷了解到RequestManager的實例化,最終由RequestManagerRetriever一系列重載函數get()完成,最終根據host不同類型(Application和非ui線程除外),由supportFragmentGet或fragmentGet方法構建,其實現如下:
@NonNull private RequestManager supportFragmentGet(@NonNull Context context, @NonNull androidx.fragment.app.FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) { SupportRequestManagerFragment current = this.getSupportRequestManagerFragment(fm, parentHint, isParentVisible); RequestManager requestManager = current.getRequestManager(); if (requestManager == null) { Glide glide = Glide.get(context); requestManager = this.factory.build(glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); current.setRequestManager(requestManager); } return requestManager; } private RequestManager fragmentGet(@NonNull Context context, @NonNull FragmentManager fm, @Nullable android.app.Fragment parentHint, boolean isParentVisible) { RequestManagerFragment current = this.getRequestManagerFragment(fm, parentHint, isParentVisible); RequestManager requestManager = current.getRequestManager(); if (requestManager == null) { Glide glide = Glide.get(context); requestManager = this.factory.build(glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); current.setRequestManager(requestManager); } return requestManager; }
都會創建並添加一個可不見的SupportRequestManagerFragment或者RequestManagerFragment,而這兩個Fragment都有添加LifecycleListener的功能,在其生命周期函數中都會調用listener對應的生命周期函數,代碼如下:
SupportRequestManagerFragment\RequestManagerFragment{ //對LifecycleListener 進行了封裝 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(); unregisterFragmentWithRoot(); } } class ActivityFragmentLifecycle implements Lifecycle { 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(); } } }
再查看RequestManager的相關代碼實現,如下:
public class RequestManager implements LifecycleListener, ...{ RequestManager(...,Lifecycle lifecycle,...) { ... this.lifecycle = lifecycle; ... // If we're 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); } lifecycle.addListener(connectivityMonitor); ... } @Override public synchronized 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 synchronized void onStop() { pauseRequests(); targetTracker.onStop(); } /** * Lifecycle callback that cancels all in progress requests and clears and recycles resources for * all completed requests. */ @Override public synchronized void onDestroy() { ... requestTracker.clearRequests(); lifecycle.removeListener(this); lifecycle.removeListener(connectivityMonitor); .. } }
RequestManager實現了LifecycleListener接口,並在構造器中給lifecycle添加listener,而這里lifecycle正好對應了RequestManagerFragment中的lifecycle,就這樣RequestManager可以只能感知RequestManagerFragment的生命周期,也就感知其中host Activity或者Fragment的生命周期。
RequestManager管理所有的其對應host中所有的請求,requestTracker對跟蹤Request的封裝,具有暫停、重啟、清空請求的功能。
至此就可以知道Glide圖片加載可以智能感知Activity、Fragment的生命周期函數進行重啟,暫停,清除。
3.Glide的圖片緩存機制
在Glide圖片加載流程圖中,可以知道真正開始加載圖片的地方從Engine.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); } return null; } //內存緩存 EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); if (cached != null) { cb.onResourceReady(cached, DataSource.MEMORY_CACHE); return null; } EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache); if (current != null) { current.addCallback(cb, callbackExecutor); return new LoadStatus(cb, current); } EngineJob<R> engineJob = engineJobFactory.build(...); //解碼工作任務 由線程池調度啟動,ResourceDiskcache DataDiskCached都這里加載編碼 DecodeJob<R> decodeJob = decodeJobFactory.build(...); jobs.put(key, engineJob); engineJob.addCallback(cb, callbackExecutor); //開始DecodeJob,由線程池調度啟動 engineJob.start(decodeJob); return new LoadStatus(cb, engineJob); }
DecodeJob decode state有如下幾種:
/** * Where we're trying to decode data from. */ //DecodeJob內部枚舉類 private enum Stage { /** The initial stage. */ INITIALIZE, /** Decode from a cached resource. */ RESOURCE_CACHE, /** Decode from cached source data. */ DATA_CACHE, /** Decode from retrieved source. */ SOURCE, /** Encoding transformed resources after a successful load. */ ENCODE, /** No more viable stages. */ FINISHED, }
State.ENCODE代表成功從Source中加載數據后,把transformed的資源保存到DiskCache。
由此可以看出Glide分為四級緩存:
-
活動資源 (ActiveResources)
-
內存緩存 (MemoryCache)
-
資源類型(Resource DiskCache)
-
原始數據 (Data DiskCache)
活動資源:如果當前對應的圖片資源正在使用,則這個圖片會被Glide放入活動緩存。
內存緩存:如果圖片最近被加載過,並且當前沒有使用這個圖片,則會被放入內存中 。
資源類型: 被解碼后的圖片寫入磁盤文件中,解碼的過程可能修改了圖片的參數(如: inSampleSize、inPreferredConfig)。
原始數據: 圖片原始數據在磁盤中的緩存(從網絡、文件中直接獲得的原始數據)。
Glide加載圖片依次從四級緩存中獲取圖片資源的時序圖如下:
活動資源ActiveResources
ActiveResources維護着弱引用EngineResource map集合,當有垃圾回收時,弱引用關聯的EngineResource 會被存放到ReferenceQueue中,ActiveResources在實例化時開啟線程監控清理被回收的EngineResource 該EngineResource 又會轉移到MemoryCache中去,具體代碼如下:
final class ActiveResources { ... final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>(); private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>(); private ResourceListener listener; ... ActiveResources( boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) { this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed; this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor; monitorClearedResourcesExecutor.execute( new Runnable() { @Override public void run() { cleanReferenceQueue(); } }); } } void cleanupActiveReference(@NonNull ResourceWeakReference ref) { // Fixes a deadlock where we normally acquire the Engine lock and then the ActiveResources lock // but reverse that order in this one particular test. This is definitely a bit of a hack... synchronized (listener) { synchronized (this) { activeEngineResources.remove(ref.key); if (!ref.isCacheable || ref.resource == null) { return; } EngineResource<?> newResource = new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false); newResource.setResourceListener(ref.key, listener); //Engine實現了ResourceListener接口,最終會調用Resource.recycle()方法 listener.onResourceReleased(ref.key, newResource); } } } //Engine類 public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) { activeResources.deactivate(cacheKey); if (resource.isCacheable()) { cache.put(cacheKey, resource); } else { resourceRecycler.recycle(resource); } }
EngineResource是對Resource一種包裝,新增了引用計數功能,每當一個地方獲取該資源時,引用計數acquired就會加1,當EngineResource被release時引用計數acquired減1,當acquired==0也會回調EngineResource從ActiveResources回收到MemeryCache中去。
那引用計數在哪些情況下加1
情況一、 資源在ActiveResources中命中,acquired++,代碼如下:
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } EngineResource<?> active = activeResources.get(key); if (active != null) { active.acquire();//acquire++ } return active; }
情況二、資源在MemoryCache中命中,資源從MemoryCach轉移到ActiveResources,acquired++,代碼如下:
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } EngineResource<?> cached = getEngineResourceFromCache(key); if (cached != null) { cached.acquire(); //cached 轉移到activeResources activeResources.activate(key, cached); } return cached; }
情況三、 資源從DiskCache、RemoteSource加載也會acquired++,拉取的資源也會加入到ActiveResources。通過DecodeJob加載的資源,最終都會回調DecodeJob的decodeFromRetrievedData()方法,最終輾轉到EngineJob的notifyCallbacksOfResult()方法,其代碼如下:
void notifyCallbacksOfResult() { ... //listener 為Engine, //EngineonEngineJobComplete方法中調用了activeResources.activate() listener.onEngineJobComplete(this, localKey, localResource); //CallResourceReady.run方法調用 for (final ResourceCallbackAndExecutor entry : copy) { entry.executor.execute(new CallResourceReady(entry.cb)); } decrementPendingCallbacks(); } //Engine public synchronized void onEngineJobComplete( EngineJob<?> engineJob, Key key, EngineResource<?> resource) { if (resource != null) { resource.setResourceListener(key, this); if (resource.isCacheable()) { //把加載的資源加入到activeResources中去 activeResources.activate(key, resource); } } jobs.removeIfCurrent(key, engineJob); } private class CallResourceReady implements Runnable { ... @Override public void run() { synchronized (EngineJob.this) { if (cbs.contains(cb)) { // Acquire for this particular callback. engineResource.acquire(); //acquire++ callCallbackOnResourceReady(cb); removeCallback(cb); } decrementPendingCallbacks(); } } }
引用計數減一情況
在Glide圖片加載的生命周期的集成部分,已分析RequestManeger能感知Activity,Fragment生命周期函數,由RequetTracker跟蹤Request,具有暫停、重啟,清除Request的功能。
RequestManeger生命回調函數onStop、onDestory代碼如下:
public synchronized void onStop() { pauseRequests();//暫停所有請求 targetTracker.onStop(); } /** * Lifecycle callback that cancels all in progress requests and clears and recycles resources for * all completed requests. */ @Override 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()和pauseRequests()方法都調用了request.clear()方法,而真正的請求實例為SingleRequest,其clear方法代碼如下:
public synchronized void clear() { ... cancel(); // Resource must be released before canNotifyStatusChanged is called. if (resource != null) { releaseResource(resource); } ... } private void releaseResource(Resource<?> resource) { engine.release(resource);//EngineResoure.release this.resource = null; }
調用了EngineResoure.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) {//減一操作 //Engine.onResourceReleased listener.onResourceReleased(key, this); } } } }
當acquired == 0時會回調Engine. onResourceReleased方法,把資源從activeResources中移除,加入帶MemoryCache中去,其代碼如下:
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) { //從activeResources 移除該資源 activeResources.deactivate(cacheKey); if (resource.isCacheable()) { //放入MemoryCache中 cache.put(cacheKey, resource); } else { resourceRecycler.recycle(resource); } }
內存緩存MemoryCache
Glide中MemoryCache默認情況下,為LruResourceCache,繼承了LruCache,使用了最近最少算法管理內存資源,同時對外提供了trimMemory ,clearMemory接口,代碼如下:
/** * An LRU in memory cache for {@link com.bumptech.glide.load.engine.Resource}s. */ public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache { private ResourceRemovedListener listener; ... @Override public void setResourceRemovedListener(@NonNull ResourceRemovedListener listener) { this.listener = listener; } @Override protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) { if (listener != null && item != null) { listener.onResourceRemoved(item); } } @Override protected int getSize(@Nullable Resource<?> item) { if (item == null) { return super.getSize(null); } else { return item.getSize(); } } @SuppressLint("InlinedApi") @Override public void trimMemory(int level) { if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { // Entering list of cached background apps // Evict our entire bitmap cache clearMemory(); } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { // The app's UI is no longer visible, or app is in the foreground but system is running // critically low on memory // Evict oldest half of our bitmap cache trimToSize(getMaxSize() / 2); } } }
MemoryCache對外提供了資源刪除監聽接口,通過搜索可以知道Engine實現了ResourceRemovedListener接口,並設置給MemoryCache,Engine資源刪除回調函數Engine.onResourceRemoved相關代碼如下:
@Override public void onResourceRemoved(@NonNull final Resource<?> resource) { resourceRecycler.recycle(resource); } //ResourceRecycler synchronized void recycle(Resource<?> resource) { if (isRecycling) { // If a resource has sub-resources, releasing a sub resource can cause it's parent to be // synchronously evicted which leads to a recycle loop when the parent releases it's children. // Posting breaks this loop. handler.obtainMessage(ResourceRecyclerCallback.RECYCLE_RESOURCE, resource).sendToTarget(); } else { isRecycling = true; resource.recycle(); isRecycling = false; } }
onResourceRemoved回調函數對資源進行Recycle,MemoryCache的Resource實際上為EngineResource,最終對被包裹的資源進行Recycle,而Resource的實現類有如下圖這些:
對跟Bitmap有關的BitmapResource,BitmapDrawableResource進行分析,其recycle方法實現如下:
//BitmapResource public void recycle() { bitmapPool.put(bitmap); } //BitmapDrawableResource public void recycle() { bitmapPool.put(drawable.getBitmap()); }
把從MemoryCache刪除資源關聯的bitmap回收到BitmapPool中,注意這里刪除是指資源被MemoryCache被逐出,觸發onItemEvicted回調了。
那么有些地方會觸發了onItemEvicted動作了?
情況一、MemoryCache進行put操作時,old的資源被新的資源覆蓋時,oldResource被逐出,和size超過了maxSize,會逐出最近最少使用的資源,都會觸發onItemEvicted,最終資源關聯的Bitmap回收到BitmapPool中。
情況二、Glide對面提供了trimMemory,clearMemory接口(通常會在Activity.trimMemory方法中調用),對最終MemoryCache資源進行清理,觸發onItemEvicted回調,資源關聯的Bitmap回收到BitmapPool中。
這里講到MemoryCache資源被Evicted的情況,其他情況還沒講到,其實前文已經提到了,資源在MemoryCache中命中了,被remove,且轉移到ActiveResources中;在資源請求被暫停、取消、刪除以及ActiveResources自身資源清除監控線程進行清除時,也會是相關的資源從ActiveResources轉移到MemoryCache。
4.對象池優化,減少內存抖動
Glide大量使用對象池Pools來對頻繁需要創建和銷毀的代碼進行優化。
以下就是使用對象池的情況:
1.每次圖片加載都會涉及到Request對象,可能涉及到EncodeJob,DecodeJob對象,在加載大量圖片的加載情況下,這會頻繁創建和銷毀對象,造成內存抖動,至此使用FactoryPools(android support包 Pool)。
2.對MemoryCache的資源回收,使用BitmapPool池對資源關聯的Bitmap回收;解碼圖片生成Bitmap(大對象)時,復用了BitmapPool池中的Bitmap。
3.解碼圖片生成Bitmap時,配置了inTempStorage,使用了ArrayPool技術復用了byte[](64kb)。
FactoryPools
FactoryPools是基於android support包中的對象池存取的輔助類Pools,先看Pools源碼:
public final class Pools { /** * Interface for managing a pool of objects. * * @param <T> The pooled type. */ public static interface Pool<T> { /** * @return An instance from the pool if such, null otherwise. */ public T acquire(); /** * Release an instance to the pool. * * @param instance The instance to release. * @return Whether the instance was put in the pool. * * @throws IllegalStateException If the instance is already in the pool. */ public boolean release(T instance); } private Pools() { /* do nothing - hiding constructor */ } /** * Simple (non-synchronized) pool of objects. * * @param <T> The pooled type. */ public static class SimplePool<T> implements Pool<T> { private final Object[] mPool; private int mPoolSize; /** * Creates a new instance. * * @param maxPoolSize The max pool size. * * @throws IllegalArgumentException If the max pool size is less than zero. */ public SimplePool(int maxPoolSize) { if (maxPoolSize <= 0) { throw new IllegalArgumentException("The max pool size must be > 0"); } mPool = new Object[maxPoolSize]; } @Override @SuppressWarnings("unchecked") public T acquire() { if (mPoolSize > 0) { final int lastPooledIndex = mPoolSize - 1; T instance = (T) mPool[lastPooledIndex]; mPool[lastPooledIndex] = null; mPoolSize--; return instance; } return null; } @Override public boolean release(T instance) { if (isInPool(instance)) { throw new IllegalStateException("Already in the pool!"); } if (mPoolSize < mPool.length) { mPool[mPoolSize] = instance; mPoolSize++; return true; } return false; } private boolean isInPool(T instance) { for (int i = 0; i < mPoolSize; i++) { if (mPool[i] == instance) { return true; } } return false; } } /** * Synchronized) pool of objects. * * @param <T> The pooled type. */ public static class SynchronizedPool<T> extends SimplePool<T> { private final Object mLock = new Object(); /** * Creates a new instance. * * @param maxPoolSize The max pool size. * * @throws IllegalArgumentException If the max pool size is less than zero. */ public SynchronizedPool(int maxPoolSize) { super(maxPoolSize); } @Override public T acquire() { synchronized (mLock) { return super.acquire(); } } @Override public boolean release(T element) { synchronized (mLock) { return super.release(element); } } } }
定義了Pool池接口類,包含兩個方法acquire(從池中取出對象),release(回收對象,存入對象池),提供了兩個實現簡單實現類SimplePool,SynchronizedPool,SimplePool用數組維護這個對象池,有個缺點就是不能動態擴容,SynchronizedPool對SimplePool進行了同步。
FactoryPools類中定義了FactoryPool類,FactoryPool使用裝飾者模式,對Pool擴展了對象工廠創建、回收對象重置功能;提供了一些創建Pool的靜態方法。源碼搜索FactoryPools,就看到哪些地方用FactoryPools了,如圖:
這里就不詳細陳述EncodeJob、DecodeJob、SingRequest,是在什么時機,被回收到FactoryPool中了,基本上都在其release方法中進行回收操作。
BitmapPool
Bitmap的創建是申請內存昂貴的,大則占用十幾M,使用進行BitmapPool回收復用,可以顯著減少內存消耗和抖動。BitmapPool定義了如下接口:
public interface BitmapPool { long getMaxSize(); void setSizeMultiplier(float sizeMultiplier); void put(Bitmap bitmap); Bitmap get(int width, int height, Bitmap.Config config); Bitmap getDirty(int width, int height, Bitmap.Config config); void clearMemory(); void trimMemory(int level); }
Glide的默認BitmapPool實現為LruBitmapPool,從GlideBuild.build方法可以看出,LruBitmapPool把Bitmap緩存的維護委托給LruPoolStrategy,大致代碼如下:
public class LruBitmapPool implements BitmapPool { ... @Override public synchronized void put(Bitmap bitmap) { ... final int size = strategy.getSize(bitmap); strategy.put(bitmap); tracker.add(bitmap); puts++; currentSize += size; ... evict(); } private void evict() { trimToSize(maxSize); } public Bitmap get(int width, int height, Bitmap.Config config) { Bitmap result = getDirtyOrNull(width, height, config); if (result != null) { result.eraseColor(Color.TRANSPARENT); } else { result = createBitmap(width, height, config); } return result; } public Bitmap getDirty(int width, int height, Bitmap.Config config) { Bitmap result = getDirtyOrNull(width, height, config); if (result == null) { result = createBitmap(width, height, config); } return result; } private synchronized Bitmap getDirtyOrNull( int width, int height, @Nullable Bitmap.Config config) { assertNotHardwareConfig(config); final Bitmap result = strategy.get(width, height, config != null config : DEFAULT_CONFIG); if (result == null) { misses++; } else { hits++; currentSize -= strategy.getSize(result); tracker.remove(result); normalize(result); } dump(); return result; } public void clearMemory() { ... trimToSize(0); } public void trimMemory(int level) { if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { clearMemory(); } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { trimToSize(getMaxSize() / 2); } } private synchronized void trimToSize(long size) { while (currentSize > size) { final Bitmap removed = strategy.removeLast(); ... currentSize -= strategy.getSize(removed); evictions++; ... removed.recycle(); } }
Bitmap緩存策略接口默認實現有AttributeStrategy和SizeConfigStrategy,前者在Api<19情況下使用,兩者實現功能相同,只不過前者是嚴格要個圖片的width、height、Bitmap.Config完全匹配,才算命中緩存,后者不嚴格要求圖片的大小size和Config完全匹配,獲取到的緩存Bitmap的size,可能會大於要求圖片的size,再通過Bitmap.reconfigure()重新配置成符合要求的圖片,其具體代碼如下:
public Bitmap get(int width, int height, Bitmap.Config config) { int size = Util.getBitmapByteSize(width, height, config); Key bestKey = findBestKey(size, config); Bitmap result = groupedMap.get(bestKey); if (result != null) { // Decrement must be called before reconfigure. decrementBitmapOfSize(bestKey.size, result); //重新配置成符合要求的圖片 result.reconfigure(width, height, config); } return result; } private Key findBestKey(int size, Bitmap.Config config) { Key result = keyPool.get(size, config); for (Bitmap.Config possibleConfig : getInConfigs(config)) { NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig); //獲取>= size的最小值 Integer possibleSize = sizesForPossibleConfig.ceilingKey(size); if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) { if (possibleSize != size || (possibleConfig == null ? config != null : !possibleConfig.equals(config))) { keyPool.offer(result); result = keyPool.get(possibleSize, possibleConfig); } break; } } return result; }
這兩個緩存策略類都是使用GroupedLinkedMap來維護Bitmap緩存,GroupedLinkedMap內部使用了一個名為head的雙向鏈表,鏈表的key是由bitmap size和config構成的Key,value是一個由bitmap構成的list。這樣GroupedLinkedMap中的每個元素就相當於是一個組,這個組中的bitmap具有相同的size和config,同時,為了加快查找速度,添加了keyToEntry的Hashmap,將key和鏈表中的LinkedEntry對應起來。
GroupedLinkedMap進行get操作時,會把該組移動鏈頭,返回並移除該組的最后一個元素;put操作會把該組移動鏈尾,添加到該組尾部;進行trimToSize操作,優先刪除鏈尾的對應組的最后一個元素,當該組沒有元素時,刪除該組。這里與訪問排序的LinkedHashMap有區別了,get和put操作都是把節點移至到鏈尾,LruCache trimToSize操作時優先刪除鏈頭。
ArrayPool
ArrayPool用於存儲不同類型數組的數組池的接口,默認實現LruArrayPool只支持int[],byte[]池化,內部也是使用GroupedLinkedMap維護着,由size和class構成key,獲取數組資源時,跟SizeConfigStrategy類似,獲取到Array的size,可能會大於要求的size。在圖片Bimtap解碼的時候有使用到ArrayPool。
五、Bitmap的解碼
先介紹下加載本地資源和遠程資源的流程(從DecodeJob中算起)大致如下:
通常情況下,遠程圖片通過ModelLoaders拉取圖片,返回inoutStream/ByteBuffer等,供后續對應的ResourceDecoder解碼器、transformations、ResourceTranscoders轉碼器、ResourceEncoder編碼器處理。
5.Bitmap的解碼
圖片加載不管源自網絡、本地文件都會通過ResourceDecoder編碼器對inputStream、ByteBuffer等進行下采樣、解碼工作,由Downsampler輔助ResourceDecoder完成,Downsampler相關的decode方法如下:
public Resource<Bitmap> decode(..) throws IOException { //從ArrayPool獲取byte[]資源,設置給inTempStorage byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class); BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions(); bitmapFactoryOptions.inTempStorage = bytesForOptions; ... try { Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions, downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth, requestedHeight, fixBitmapToRequestedDimensions, callbacks); return BitmapResource.obtain(result, bitmapPool); } finally { releaseOptions(bitmapFactoryOptions); //byte[]資源回收到ArrayPool byteArrayPool.put(bytesForOptions); } } private Bitmap decodeFromWrappedStreams(...) throws IOException { ... //獲取原圖片的寬高 int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool); //計算BitmapFactory.Options 縮放相關參數 //比如 inSampleSize、inScaled、inDensity、inTargetDensity calculateScaling(...); //設置inPreferredConfig、inDither calculateConfig(...); boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) { if (expectedWidth > 0 && expectedHeight > 0) { //從bitmapPool獲取bitmap資源,設置options.inBitmap //options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig) setInBitmap(options, bitmapPool, expectedWidth, expectedHeight); } } Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool); callbacks.onDecodeComplete(bitmapPool, downsampled); ... Bitmap rotated = null; if (downsampled != null) { // If we scaled, the Bitmap density will be our inTargetDensity. Here we correct it back to // the expected density dpi. 對bitmap 設置Density downsampled.setDensity(displayMetrics.densityDpi); //對圖片進行旋轉 rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation); if (!downsampled.equals(rotated)) { //rotated后的Bitmap不是原Bitmap,回收原Bitmap bitmapPool.put(downsampled); } } return rotated; } private static Bitmap decodeStream(...) throws IOException { ... final Bitmap result; TransformationUtils.getBitmapDrawableLock().lock(); try { result = BitmapFactory.decodeStream(is, null, options); } catch (IllegalArgumentException e) { IOException bitmapAssertionException = newIoExceptionForInBitmapAssertion(e, sourceWidth, sourceHeight, outMimeType, options); //重用已經存在的Bitmap,失敗,在嘗試不重用已經存在的bitmap, //且把該bitmap回收到bitmapPool if (options.inBitmap != null) { try { is.reset(); bitmapPool.put(options.inBitmap); options.inBitmap = null; return decodeStream(is, options, callbacks, bitmapPool); } catch (IOException resetException) { throw bitmapAssertionException; } } throw bitmapAssertionException; } finally { TransformationUtils.getBitmapDrawableLock().unlock(); } if (options.inJustDecodeBounds) { is.reset(); } return result; }
對inputStream(ByteBuffer等也雷同)的decode過程分析如下:
1.給BitmapFactory.Options選項設置了inTempStorage
inTempStorage為Bitmap解碼過程中需要緩存空間,就算我們沒有配置這個,系統也會給我們配置,相關代碼如下:
private static Bitmap decodeStreamInternal(@NonNull InputStream is, @Nullable Rect outPadding, @Nullable Options opts) { // ASSERT(is != null); byte [] tempStorage = null; if (opts != null) tempStorage = opts.inTempStorage; if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE]; return nativeDecodeStream(is, tempStorage, outPadding, opts);
}
不過,這里每次decode過程,就會申請和釋放DECODE_BUFFER_SIZE的內存空間,多次docode可能會造成頻繁gc和內存抖動;而Glide卻從ArrayPool獲取,設置給inTempStorage,decode完成后,又會回收到ArrayPool中,可以減少內存抖動。
2.獲取圖片原始寬高
獲取圖片資源的原始寬高,設置參數inJustDecodeBounds為True即可,沒什么特別的,然后對inputStream進行reset,以便后續的真正的decode動作。
3.計算縮放因子,配置Options的inSampleSize、inScaled、inDensity、inTargetDensity
4.配置Options的inPreferredConfig、inDither,首先判斷是否允許設置硬件位圖,允許則inPreferredConfig設置為HARDWARE,inMutable為false,否則再解析流中的ImageHeader數據,假如有透明通道,inPreferredConfig設置為ARGB_8888,沒有則為RGB_565,同時inDither置為True。
注意HARDWARE的bitmap不能被回收到BitmapPool,具體查看LruBitmapPool的put方法;其相應的像素數據只存在於顯存中,並對圖片僅在屏幕上繪制的場景做了優化,具體詳述查看Glide官方文檔-硬件位圖。
5.設置inBitmap,如果為硬件位圖配置,則不設置inBitmap。其他情況,從BitmapPool獲取Bitmap,設置給inBitmap。
6.配置完Options后,就真正調用BitmapFactory的decode方法,解碼失敗再嘗試一次取消inBitmap進行解碼,並對inBitmap回收BitmapPool。然后setDensity(繪制時縮放,decodedBtimap本身占用內存沒有變化),decodedBtimap最后根據exifOrientation,旋轉位圖。
6、網絡棧的切換
Glide最終使用的網絡加載ModelLoader為HttpGlideUrlLoader,其對應的DataFetcher為HttpUrlFetcher,使用HttpURLConnection進行網絡請求。
Glide可以自由定制加載器ModelLoader,資源解碼器ResourceDecoder,資源編碼器ResourceEncoder,這里想進行底層網絡庫切換,定制ModelLoader即可,教材可以參考Glide文檔,官方提供了OkHttp和Volley 集成庫。
定制的加載器,解碼器,編碼器自動注入到Glide的原理如下:
1.定制LibraryGlideModule類,通過其 registerComponents()方法的形參Registry登記所有定制的加載器ModelLoader,資源解碼器Decoder,資源編碼器Encoder,給定制的LibraryGlideModule類添加@GlideModule注解,編譯期間自動在AndroidManifest.xml文件中添加該LibraryGlideModule相關的元數據。
2.在Glide初始化時,會從功能配置文件AndroidManifest.xml中獲取相關GlideModule元數據,並通過反射實例化所有的GlideModule,再迭代所有定制的GlideModule調用registerComponents方法,這樣那些定制的加載器ModelLoader,解碼器Decoder,編碼器Encoder就自動注入到Glide了。關鍵源碼如下:
private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) { Context applicationContext = context.getApplicationContext(); GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules(); List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList(); if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) { manifestModules = new ManifestParser(applicationContext).parse(); } ... for (com.bumptech.glide.module.GlideModule module : manifestModules) { module.applyOptions(applicationContext, builder); } if (annotationGeneratedModule != null) { annotationGeneratedModule.applyOptions(applicationContext, builder); } Glide glide = builder.build(applicationContext); for (com.bumptech.glide.module.GlideModule module : manifestModules) { module.registerComponents(applicationContext, glide, glide.registry); } ... }
至此,6個問題都解析完畢,相信能對Glide有更深刻的整體認識。
參考資料:
Glide v4 快速高效的Android圖片加載庫(官方)
[Glide4源碼解析系列] — 3.Glide數據解碼與轉碼
android.support.v4.util.Pools源碼解析
Glide4.8源碼拆解(四)Bitmap解析之"下采樣"淺析
如果您對博主的更新內容持續感興趣,請關注公眾號!