一张图掌握Glide内存管理

前言

Glide源码异常庞大, 其中的代码是非常复杂的, 所以笔者不会逐行的分析源码,避免读者陷入代码黑洞中无法自拔,而是通过分析关键的代码和流程来让读者快速的理解和掌握Glide内存管理策略的核心思想 。

Glide缓存可以分为三块
  1. ActiveResources
  2. MemoryCache
  3. BitmapPool
结论
  • 缓存类型和说明
类型名称说明
ActiveResources活跃内存缓存正在被页面引用展示的缓存资源
MemoryCache普通内存缓存无任何页面引用缓存资源,但是Bitmap是有效的, 通过key获取后可以直接展示
BitmapPoolBitmap回收池缓存不可以用来直接展示,只是具有一块内存空间,可以理解为是一个dirty的Bitamp,需要再次绑定图片数据后展示
  • 缓存获取优先级顺序 : ActiveResources -> MemoryCache -> BitmapPool 复用Bitmap 从磁盘或者网络中decoded数据到 `Bitamp中。

process=image/format,png)

功能

ActiveResources 也被称为活跃缓存,它的主要作用就是缓存正在被页面引用的资源。

特性

缓存中的某个资源是否被移除唯一条件是没有页面在引用该资源,否则该缓存中的资源是不会被移除的,即使应用可用内存很低或OOM也不会被回收。

资源是否应该被回收算法: 引用计数法

Glide是通过引用计数法来判断ActiveResources中的缓存资源是否应该被移除,具体的逻辑并不在ActiveResources中,而是在ActiveResources持有的缓存资源EngineResource类中,当EngineResource被引用时引用数+1,取消引用时-1 , 当引用数为0的时候,则从ActiveResources移除。

class EngineResource<Z> implements Resource<Z> {
 
 
    synchronized void acquire() {
        if (isRecycled) {
            throw new IllegalStateException("Cannot acquire a recycled resource");
        }
        ++acquired;
    }

  
    void release() {
        boolean release = false;
        synchronized (this) {
            if (acquired <= 0) {
                throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
            }
            // 引用数为0 ,标记为可释放
            if (--acquired == 0) {
                release = true;
            }
        }
        if (release) {
             // 回调该资源可释放 listener == Engine
             listener.onResourceReleased(key, this);
        }
    }
}

listener 是一个接口,该接口的唯一实现是 Engine 类,带活跃缓存的资源被标记可回收时,则会通过调用 listener.onResourceReleased函数通知 EngineonResourceReleased函数表示资源可以被回收。源代码实现如下:

 public class Engine
    implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {   
    
    @Override
    public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
        activeResources.deactivate(cacheKey);
        // 如果设置了内存缓存,就将资源放入内存缓存
        if (resource.isMemoryCacheable()) {
            cache.put(cacheKey, resource);
        } else {
        // 否则放入到 BitmapPool 回收池
            resourceRecycler.recycle(resource, /* forceNextFrame= */ false);
        }
    }
  }

可以发现当活跃缓存的资源被回收时,并不是被直接销毁,而是尝试放入普通缓存中,如果资源被设置了内存不可缓存 isMemoryCacheable() = false,则会被放入到BitmapPool池中。

MemoryCache
功能

MemoryCache 也被称为普通缓存,它的主要作用有两个:

  1. 从上面的分析代码中可以看到,从活跃缓存中被释放的资源,即无任何页面引用的资源,会被放入到 MemoryCache内。
  2. 当从缓存中加载一个图片时如果ActiveResources活动缓存中不存在, 则尝试从 MemoryCache 中获取,如果获取成功,则资源会被放入到 ActiveResources活动。
 // Engine
 @Nullable
  private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) {
    // 设置了禁用缓存参数,直接返回 null
    if (!isMemoryCacheable) {
      return null;
    }
    // 尝试从一级活跃缓存中获取资源
    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return active;
    }
    // 尝试从二级普通内存缓存中获取资源
    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return cached;
    }

    return null;
  }
  
  
  private EngineResource<?> loadFromCache(Key key) {
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      // 引用计数 + 1
      cached.acquire();
      // 放入活动缓存中
      activeResources.activate(key, cached);
    }
    return cached;
  }

源码实现
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
  private ResourceRemovedListener listener;

  public LruResourceCache(long size) {
    super(size);
  }

  ...
  
    @Override
  protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) {
    if (listener != null && item != null) {
      // Engine    
      listener.onResourceRemoved(item);
    }
  }

  @SuppressLint("InlinedApi")
  @Override
  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);
    }
  }
}

MemoryCache是一个接口,具体的实现类 LruResourceCache,从LruResourceCachetrimMemory函数可以看出,当内存较低时,LruResourceCache会根据不同的内存低level级别主动的释放缓存。LruResourceCache继承了LruCache类, LruCache 的最大特性就是可以根据优先级移除优先级较低的资源,当资源被移除时在 onItemEvicted 函数内调用 listener.onResourceRemoved(item) 函数,listener的具体实现类也是Engine,同样也会被放入到 BitmapPool 缓存中。

  @Override
  public void onResourceRemoved(@NonNull final Resource<?> resource) {
    // Avoid deadlock with RequestManagers when recycling triggers recursive clear() calls.
    // See b/145519760.
    resourceRecycler.recycle(resource, /* forceNextFrame= */ true);
  }

BitmapPool
功能

我们知道 Bitmap 是可以复用的 ,从ActiveResourcesMemoryCache 释放的资源会调用ResourceRecycler.recycle(...)函数,最后都会调用BitmapResource.recycle(...) 被放入到 BitmapPool 中去,当有新的Bitmap需要加载时,会从BitmapPool缓存池中取出一个合适的Bitmap进行复用,将资源数据绑定到复用的Bitmap内。


class ResourceRecycler {
 
  ...
 
  synchronized void recycle(Resource<?> resource, boolean forceNextFrame) {
    if (isRecycling || forceNextFrame) {
        ...
    } else {
      isRecycling = true;
      resource.recycle();
      isRecycling = false;
    }
  }
 
  ...
}

public class BitmapResource implements Resource<Bitmap>, Initializable {
  private final Bitmap bitmap;
  private final BitmapPool bitmapPool;
  
  ...
  
  // bitmap 回收放入到 BitmapPool
  @Override
  public void recycle() {
    bitmapPool.put(bitmap);
  }

  ...
  
}

源码实现

BitmapPool 具体的实现是 LruBitmapPool, 从名字可以看出也是一个具有Lru特性的实现类,和MemoryCache一样实现了当接口到系统内存较低的回调时,自动回收清理内存。

public class LruBitmapPool implements BitmapPool {
  
  @Override
  public void clearMemory() {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(TAG, "clearMemory");
    }
    trimToSize(0);
  }

  @SuppressWarnings("checkstyle:UnnecessaryParentheses") // Readability
  @SuppressLint("InlinedApi")
  @Override
  public void trimMemory(int level) {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(TAG, "trimMemory, level=" + level);
    }
    if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
        || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
            && level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)) {
      clearMemory();
    } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
        || level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
      trimToSize(getMaxSize() / 2);
    }
  }

}

Glide使用注意点
  1. 避免使用 ApplicationContext作为Glide.with(Context) 函数的context参数 , 使用该方式加载的图片资源将会长期在 ActiveResources 缓存中,导致内存无法被释放, 严重情况下可能会导致OOM
Glide.with(applicationContext)
    .load("https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF")
    .into(findViewById(R.id.imageView))

  1. 避免在子线程中执行 Glide.with(Activity/Fragment/Context/View) , 如果在子线程中执行该方法,无论with()函数参数是是任何类型,均会被Glide替换为 ApplicationContext ,也会出现和场景1一样的问题,源码如下:
@NonNull
  public RequestManager get(@NonNull FragmentActivity activity) {
    // 如果在子线程,默认使用 ApplicationContext
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    }
    assertNotDestroyed(activity);
    frameWaiter.registerSelf(activity);
    boolean isActivityVisible = isActivityVisible(activity);
    Glide glide = Glide.get(activity.getApplicationContext());
    return lifecycleRequestManagerRetriever.getOrCreate(
        activity,
        glide,
        activity.getLifecycle(),
        activity.getSupportFragmentManager(),
        isActivityVisible);
  }

  1. 如果使用 android.app.Activity、android.app.Fragment 作为 Glide.with()参数,均会被Glide替换为 ApplicationContext 。 可以看到官方也将其标记为了 @Deprecated 方法,不再推荐使用。
@Deprecated
@NonNull
public RequestManager get(@NonNull Activity activity) {
  return get(activity.getApplicationContext());
}


@Deprecated
@NonNull
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public RequestManager get(@NonNull android.app.Fragment fragment) {
  if (fragment.getActivity() == null) {
    throw new IllegalArgumentException(
        "You cannot start a load on a fragment before it is attached");
  }
  return get(fragment.getActivity().getApplicationContext());
}

  1. 如果在一些场景下必须要使用 ApplicationContext或者在子线程中 Glide.with(context) ,需要注意在合理的时机释放缓存
val target = Glide.with(applicationContext)
                .load("https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF")
                //  添加这个方法,会在view detach的时候,自动清除图片请求
                .into(findViewById(R.id.imageView))
// 方式一 自动释放                
target.clearOnDetach()
// 方式二 手动释放                
Glide.with(applicationContext).clear(target)

== 如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料==

1、《Android性能优化实战篇》
2、《音视频精编源码解析》
3、24种设计模式介绍与6大设计原则
4、360°全方面性能调优
5、2021最新版数据结构与算法面试题手册 1
6、2023年Android中高级最全面试真题答案解析
7、Android Compose 强化实战
8、Android Framework 源码开发揭秘(2)
9、Android Jetpack Compose开发应用指南第三版
10、Android 音视频开发进阶指南-无水印(1)
11、Android车载操作系统开发揭秘
12、Android车载系统应用指南(1)
13、Android多媒体应用开发实战详解:图像、音频、视频、2D和3D-2
14、Android高级UI开源框架进阶解密(1)无水印版
15、Android源码解析(1)
16、Flutter技术解析与实战
17、Flutter技术进阶
18、Flutter入门与实战 无水印
19、Flutter完整开发实战详解
20、Jetpack架构组件从入门到精通
21、KMM跨平台框架入门教程无水印
22、Kotlin 入门教程指南(1)
23、kotlin从入门到精通
24、高级Android插件化强化实战(附源码)
25、高级Android组件化强化实战(附源码)
26、高级Jetpack强化实战
27、高级Kotlin强化实战(附Demo)
28、鸿蒙零基础入门学习指南
29、史上最详android版kotlin协程入门进阶实战指南
30、音视频开发教程(附面试题)
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )

  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值