一张图掌握Glide内存管理
作者:访客发布时间:2023-12-22分类:程序开发学习浏览:454
前言
Glide源码异常庞大, 其中的代码是非常复杂的, 所以笔者不会逐行的分析源码,避免读者陷入代码黑洞中无法自拔,而是通过分析关键的代码和流程来让读者快速的理解和掌握Glide内存管理策略的核心思想 。
Glide缓存可以分为三块
- ActiveResources
- MemoryCache
- BitmapPool
结论
- 缓存类型和说明
| 类型 | 名称 | 说明 |
|---|---|---|
| ActiveResources | 活跃内存缓存 | 正在被页面引用展示的缓存资源 |
| MemoryCache | 普通内存缓存 | 无任何页面引用缓存资源,但是Bitmap是有效的, 通过key获取后可以直接展示 |
| BitmapPool | Bitmap回收池缓存 | 不可以用来直接展示,只是具有一块内存空间,可以理解为是一个dirty的Bitamp,需要再次绑定图片数据后展示 |
- 缓存获取优先级顺序 :
ActiveResources -> MemoryCache -> BitmapPool复用Bitmap从磁盘或者网络中decoded数据到 `Bitamp中。
功能
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函数通知 Engine类onResourceReleased函数表示资源可以被回收。源代码实现如下:
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 也被称为普通缓存,它的主要作用有两个:
- 从上面的分析代码中可以看到,从活跃缓存中被释放的资源,即无任何页面引用的资源,会被放入到
MemoryCache内。 - 当从缓存中加载一个图片时如果
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,从LruResourceCache 的 trimMemory函数可以看出,当内存较低时,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 是可以复用的 ,从ActiveResources 和 MemoryCache 释放的资源会调用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使用注意点
- 避免使用
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))
- 避免在子线程中执行
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);
}
- 如果使用
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());
}
- 如果在一些场景下必须要使用
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)
相关推荐
- 程序开发学习排行
- 最近发表


