一张图掌握Glide内存管理
作者:访客发布时间:2023-12-22分类:程序开发学习浏览:120
前言
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)
相关推荐
- 程序开发学习排行
-
- 1鸿蒙HarmonyOS:Web组件网页白屏检测
- 2HTTPS协议是安全传输,为啥还要再加密?
- 3HarmonyOS鸿蒙应用开发——数据持久化Preferences
- 4记解决MaterialButton背景颜色与设置值不同
- 5鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
- 6鸿蒙HarmonyOS实战-ArkUI组件(Stack)
- 7[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- 8Android广播如何解决Sending non-protected broadcast问题
- 9鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)
- 最近发表
-
- WooCommerce最好的WordPress常用插件下载博客插件模块的相关产品
- 羊驼机器人最好的WordPress常用插件下载博客插件模块
- IP信息记录器最好的WordPress常用插件下载博客插件模块
- Linkly for WooCommerce最好的WordPress常用插件下载博客插件模块
- 元素聚合器Forms最好的WordPress常用插件下载博客插件模块
- Promaker Chat 最好的WordPress通用插件下载 博客插件模块
- 自动更新发布日期最好的WordPress常用插件下载博客插件模块
- WordPress官方最好的获取回复WordPress常用插件下载博客插件模块
- Img to rss最好的wordpress常用插件下载博客插件模块
- WPMozo为Elementor最好的WordPress常用插件下载博客插件模块添加精简版