一张图掌握Glide内存管理

前言

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

Glide缓存可以分为三块

  1. ActiveResources
  2. MemoryCache
  3. 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移除。

arduino 复制代码
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函数表示资源可以被回收。源代码实现如下:

typescript 复制代码
 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活动。
scss 复制代码
 // 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;
  }
源码实现
java 复制代码
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 缓存中。

less 复制代码
  @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内。

java 复制代码
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一样实现了当接口到系统内存较低的回调时,自动回收清理内存。

typescript 复制代码
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
csharp 复制代码
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一样的问题,源码如下:
less 复制代码
@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 方法,不再推荐使用。
less 复制代码
@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) ,需要注意在合理的时机释放缓存
scss 复制代码
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 分钟前
Android开发-常用布局
android·gitee
程序员老刘26 分钟前
Google突然“变脸“,2026年要给全球开发者上“紧箍咒“?
android·flutter·客户端
Tans530 分钟前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
雨白1 小时前
实现双向滑动的 ScalableImageView(下)
android
峥嵘life1 小时前
Android Studio新版本编译release版本apk实现
android·ide·android studio
参宿四南河三3 小时前
Kotlin的Flow用法(实例加长加倍版)
app·响应式编程
studyForMokey3 小时前
【Android 消息机制】Handler
android
敲代码的鱼哇3 小时前
跳转原生系统设置插件 支持安卓/iOS/鸿蒙UTS组件
android·ios·harmonyos
翻滚丷大头鱼3 小时前
android View详解—动画
android
我是好小孩4 小时前
[Android]RecycleView的item用法
android