不一樣視角的Glide剖析


 

 

推薦閱讀:

 

滴滴Booster移動App質量優化框架-學習之旅 一

 

Android 模塊Api化演練

 

不一樣視角的Glide剖析(一)

 

 

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分為四級緩存:

  1. 活動資源 (ActiveResources)

  2. 內存緩存 (MemoryCache)

  3. 資源類型(Resource DiskCache)

  4. 原始數據 (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圖片加載庫(官方)

Glide高級詳解—緩存與解碼復用

[Glide4源碼解析系列] — 3.Glide數據解碼與轉碼

android.support.v4.util.Pools源碼解析

 Glide4.8源碼拆解(四)Bitmap解析之"下采樣"淺析

 

 如果您對博主的更新內容持續感興趣,請關注公眾號!

 


免責聲明!

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



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