转载:Glide内存优化

766 阅读9分钟

目录介绍

  • 01.Glide常见一些优化操作

    • 1.1 Recyclerview使用Glide加载大量图片导致内存溢出
    • 1.2 Recyclerview使用Glide加载图片滑动卡顿
    • 1.3 适当避免使用圆角的自定义ImageView控件
  • 02.配置好TrimMemory和LowMemory

    • 2.1 TrimMemory和LowMemory方法作用

    • 2.2 TrimMemory和LowMemory源码分析

    • 2.3 Glide低内存优化操作案例

01.Glide常见一些优化操作

1.1 在Recyclerview使用GlideAPP加载大量图片导致内存溢出

  • 第一种解决方案【不可行】

    • 在Recyclerview中重写这个方法,当item被隐藏的时候,调用Glide.with(fragment).clear(imageView);当然这个方法在图片画廊或者流式布局中体验不太好。
//在RecyclerView中重写这个方法,当item被隐藏的时候,调用 Glide.with(fragment).clear(imageView);
@Override
public void onViewRecycled(@NonNull MyViewHolder holder) {
    super.onViewRecycled(holder);
    ImageView imageView = holder.photoView;
    if (imageView!=null){
        Glide.with(mContext).clear(imageView);
    }
}
  • 第二种方案【可行】

    • 尽量减少图片资源的大小,这种方法是可行的。但是会导致刷新闪烁的问题,导致原因是glide设置了跳过内存缓存skipMemoryCache(true)导致的。
holder.loading.setVisibility(View.VISIBLE);
GlideApp.with(mContext)
        .asDrawable()               //相对而言,asDrawable()比asBitmap要省(具体相关可以去百度)
        .thumbnail(0.2f)
        .load(imageUrl)
        .skipMemoryCache(true)              //跳过内存缓存
        .diskCacheStrategy(DiskCacheStrategy.ALL)//全部使用磁盘缓存
        .error(R.mipmap.pic_load_fail)
        .addListener(new RequestListener<Drawable>() {
            @Override
            public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                holder.loading.setVisibility(View.GONE);
                if (e!=null){
                    LogUtils.e("加载图片"+"加载失败"+e.getMessage());
                }
                //显示默认图片
                holder.photoView.setImageResource(R.mipmap.pic_load_fail);
                return false;
            }
​
            @Override
            public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                holder.loading.setVisibility(View.GONE);
                return false;
            }
        })
        .into(holder.photoView);

02.配置好TrimMemory和LowMemory

2.1 TrimMemory和LowMemory方法作用

  • 对于一个 App 而言,在系统内存环境不足的情况下,会回调一些onTrimMemory()或者onLowMemory()等方法,这些都是在提醒开发者,当前设备的内存环境已经发生了变化,你最好调整你的内存使用策略,避免被系统清理掉或者出现 OOM 。

  • 什么是LowMemoryKiller的策略?

    • 在 Android 系统中,当运行的 App 被移动到后台的之后,Android 为了保证下次启动的速度,会将它移入 Cached 的状态,这个时候实际上,该 App 的进程依然存在,但是对应的组件是否存在,就不一定了。而既然该 App 的进程还存活着,下次启动的速度就会很快,这就是常说的热启动。

    • 但是这些被退出到后台的 App ,也并不是完全安全不会被清理掉的,他们可能只是没有持有任何的组件,并且是不占用 CPU 资源的,但是它依然会占据内存空间。而当系统认为内存不足的时候,就会按照优先级清理掉一些优先级不那么高的进程,来回收一些内存空间,供新启动的程序使用,这就是 LowMemoryKiller 的策略。

    • 那么,为了让我们的 App 在后台尽可能活的久一点,无非就是将内存降低,从而使得优先级提高,而实现不被系统回收的功能(这是一个常规的优化方案,而非保活方案)。那么,如果我们能明确当前 App 处于什么状态,就能在此时机,释放一些不需要持有的内存资源,来达到我们的目的。

2.2 TrimMemory和LowMemory源码分析

  • TrimMemory源码

    • 源码如下所示
@CallSuper
public void onTrimMemory(int level) {
    Object[] callbacks = collectComponentCallbacks();
    if (callbacks != null) {
        for (int i=0; i<callbacks.length; i++) {
            Object c = callbacks[i];
            if (c instanceof ComponentCallbacks2) {
                ((ComponentCallbacks2)c).onTrimMemory(level);
            }
        }
    }
}
    • onTrimMemory() 方法会传递一个 level 参数,那么就先来看看,各种 level 参数所代表的含义。

      • TRIM_MEMORY_UI_HIDDEN:App 的所有 UI 界面被隐藏,最常见的就是 App 被 home 键或者 back 键,置换到后台了。

      • TRIM_MEMORY_RUNNING_MODERATE:表示 App 正常运行,并且不会被杀掉,但是目前手机内存已经有点低了,系统可能会根据 LRU List 来开始杀进程。

      • TRIM_MEMORY_RUNNING_LOW:表示 App正常运行,并且不会被杀掉。但是目前手机内存已经非常低了。

      • TRIM_MEMORY_RUNNING_CRITICAL:表示 App 正在正常运行,但是系统已经开始根据 LRU List 的缓存规则杀掉了一部分缓存的进程。这个时候应该尽可能的释放掉不需要的内存资源,否者系统可能会继续杀掉其他缓存中的进程。

      • TRIM_MEMORY_BACKGROUND:表示 App 退出到后台,并且已经处于 LRU List 比较靠后的位置,暂时前面还有一些其他的 App 进程,暂时不用担心被杀掉。

      • TRIM_MENORY_MODERATE:表示 App 退出到后台,并且已经处于 LRU List 中间的位置,如果手机内存仍然不够的话,还是有被杀掉的风险的。

      • TRIM_MEMORY_COMPLETE:表示 App 退出到后台,并且已经处于 LRU List 比较考靠前的位置,并且手机内存已经极低,随时都有可能被系统杀掉。

    • 其实从 level 值的取名来看,大致可以分为三类:

      • UI 置于后台:TRIM_MEMORY_UI_HIDDEN 。
      • App 正在前台运行时候的状态:TRIM_MEMORY_RUNNING_Xxx
      • App 被置于后台,在 Cached 状态下的回调:TRIM_MEMORY_Xxx
      • 这三类中,通常我们只需要关心 App 被置于 Cached 状态下的情况,因为系统是不会杀掉一个正在前台运行的 App 的(但可能会触发 OOM),但是如果该 App 有一些后台服务正在运行,这个服务也是有被杀的风险的。而在 Cached 状态下的时候,当收到 TRIM_MEMORY_Xxx 的回调,就需要注意了,这些只是标记了当前 App 处于 LRU List 的位置,也就是说,如果回收了靠前的App进程之后,依然达不到内存使用的要求,可能会进一步去杀进程,也就是说,极端情况下,可能从 TRIM_MEMORY_BACKGROUND 到 TRIM_MEMORY_COMPLETE 是瞬间完成的事情,所以我们需要慎重处理它们,尽量对这三个状态都进行判断,然后做统一的回收内存资源的处理
    • 哪些组件可以监听 onTrimMemory

      • 和 App 相关的,所以最少在 Application 中,应该是可以对其进行重写来监听回调的。但是除了 Application,其他的一些组件中,也是可以监听它的。

      • 这些可以监听 onTrimMemory 的组件有:

        • Application
        • Activity
        • Fragment
        • Service
        • ContentProvider
  • onLowMemory()源码

    • 为什么需要 onTrimMemory()

      • Android 系统会在自身内存不足的情况下,清理掉一些不重要的进程来释放内存资源,以供优先级更高的进程使用。而这个顺序,主要是按照 LRU List 中的优先级来清理的,但是它也同时会考虑清理掉哪些占用内存较高的进程来让系统更快的释放跟多的内存。
      • 所以,尽可能的让 App 在系统内,占用足够小的内存资源,就可以降低被杀的概率,从而下次启动的时候走热启动的方式,提升用户的体验。
      • 换一个角度来说,让 App 占用较小的内存,也可以优化系统的速度,毕竟系统清理进程释放内存的过程,也是需要占用 CPU 资源的。在大环境下,也是有意义的。
    • onTrimMemory 回调中,应该释放哪些资源

      • 在 onTrimMemory() 回调中,应该在一些状态下清理掉不重要的内存资源。对于这些缓存,只要是读进内存内的都算,例如最常见的图片缓存、文件缓存等。拿图片缓存来说,市场上,常规的图片加载库,一般而言都是三级缓存,所以在内存吃紧的时候,我们就应该优先清理掉这部分图片缓存,毕竟图片是吃内存大户,而且再次回来的时候,虽然内存中的资源被回收掉了,我们依然可以从磁盘或者网络上恢复它。
      • 除了资源缓存之外,还有一些页面相关的资源,也是占据内存的,可以考虑清理掉 Activity Task 中,多余的 Activity,只保留 Root Activity 。
      • 其实核心思想,就是根据 onTrimMemory() 回调的一些信息,来释放我们持有的可被恢复,不那么重要的内存资源,以提高系统性能,已经保证当前 App 的进程不那么容易被系统回收。

2.3 Glide低内存优化操作

  • Glide内部低内存处理

    • Glide 也为我们提供了类似方法的接口,开发者只需要调用即可,它在内部会随着不同的内存情况,帮我们对缓存的图片进行优化。

    • 在这里,主要用到Glide的trimMemory()和cleanMemroy()方法,它们一个用来裁剪Glide缓存的图片内存空间,一个用来清理 Glide 缓存的内存空间。

  • 使用onTrimMemory()的两种操作方法【application处理】

    • 第一种:通过registerComponentCallbacks()方法进行注册
registerComponentCallbacks(new ComponentCallbacks2() {
    @Override
    public void onTrimMemory(int level) {
        //HOME键退出应用程序
        //程序在内存清理的时候执行
    }
​
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
​
    }
​
    @Override
    public void onLowMemory() {
        //低内存的时候执行
    }
});
    • 第二种:直接在Application中,重写对应的方法
/**
 * 低内存的时候执行
 */
@Override
public void onLowMemory() {
    Log.d("Application", "onLowMemory");
    super.onLowMemory();
}
​

/**
 * HOME键退出应用程序
 * 程序在内存清理的时候执行
 */
@Override
public void onTrimMemory(int level) {
    Log.d("Application", "onTrimMemory");
    super.onTrimMemory(level);
}
​
​
/**
 * onConfigurationChanged
 */
@Override
public void onConfigurationChanged(Configuration newConfig) {
    Log.d("Application", "onConfigurationChanged");
    super.onConfigurationChanged(newConfig);
}
  • 具体优化方法

    • 大概的思路如下所示

      • 在lowMemory的时候,调用Glide.cleanMemroy()清理掉所有的内存缓存。

      • 在App被置换到后台的时候,调用Glide.cleanMemroy()清理掉所有的内存缓存。

      • 在其它情况的onTrimMemroy()回调中,直接调用Glide.trimMemory()方法来交给Glide处理内存情况。

    • 具体代码如下所示
/**
 * 低内存的时候执行
 */
@Override
public void onLowMemory() {
    super.onLowMemory();
    Log.e("Application----------", "onLowMemory");
    //在低内存的情况下,可以清除glide缓存
    Glide.get(this).clearMemory();
}
​
​
/**
 * HOME键退出应用程序
 * 程序在内存清理的时候执行
 */
@Override
public void onTrimMemory(int level) {
    super.onTrimMemory(level);
    Log.e("Application----------", "onTrimMemory"+level);
    if (level == TRIM_MEMORY_UI_HIDDEN){
        Glide.get(this).clearMemory();
    }
    Glide.get(this).trimMemory(level);
}
  • glide中clearMemory()和trimMemory(level)源码分析

    • clearMemory()源代码

    • trimMemory(level)源码
/**
* Clears some memory with the exact amount depending on the given level.
*
* @see android.content.ComponentCallbacks2#onTrimMemory(int)
*/
public void trimMemory(int level) {
    // Engine asserts this anyway when removing resources, fail faster and consistently
    Util.assertMainThread();
    // memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687.
    memoryCache.trimMemory(level);
    bitmapPool.trimMemory(level);
    arrayPool.trimMemory(level);
}
  • 这两个方法共同的逻辑

    • 都会去操作 memoryCache 和 bitmapPool 这两个对象,实际上它们是两个接口,这里如果做特殊处理,操作的都是 Glide 对它们的默认实现,LruResourceCache 和 LruBitmapPool 。从名称上可以看出来,它们都是遵循 Lru 算法的。
    • MemoryCache 是 Glide 用来在内存中缓存图片资源,使其在需要使用的时候立刻就可以使用,而不必执行磁盘的 I/O 操作,而 bitmapPool 则是 Glide 维护了一个图片复用池,LruBitmapPool 使用 Lru 算法保留最近使用的尺寸的 Bitmap