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 图片加载库(官方) https://muyangmin.github.io/glide-docs-cn/
Glide 高级详解 - 缓存与解码复用
[Glide4 源码解析系列] - 3.Glide 数据解码与转码 https://www.jianshu.com/p/e4b17cef6370
Android.support.v4.util.Pools 源码解析
Glide4.8 源码拆解(四)Bitmap 解析之 "下采样" 浅析 https://juejin.im/post/5c3b00f16fb9a049ab0dd8c6
来源: https://www.cnblogs.com/sihaixuan/p/10925585.html