不一样视角的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对应的生命周期函数,代码如下:

SupportRequestManagerFragmentRequestManagerFragment{
  //对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解析之"下采样"浅析

 

 如果您对博主的更新内容持续感兴趣,请关注公众号!

 

原文地址:https://www.cnblogs.com/sihaixuan/p/10925585.html