Fresco图片闪烁分析 & 缓存分析记录

4,930 阅读5分钟

一、页面切换导致Fresco图片闪烁的原因

平时公司使用Fresco使用的比较多,偶尔会发现切换页面导致图片闪烁的问题,我们大致来分析下产生的原因。(这种问题在公司客户端比较庞大臃肿,但缺少清晰标准的项目中更容易出现)

  • 为什么闪?

    如果录屏,你会发现,闪烁的现象大致是占位图和实际图片不一致导致的,流程:实际图-> 占位图 -> 实际图。

  • 图片什么时候会被重新加载?

    如果你debug的话,会发现SimpleDraweeView在由不可见到可见,它大都会触发重新加载图片的流程。比如是一个图片url的话,首先会判断它要加载的图片存不存在, 如何判断的?不好意思,fresco认为只有这张图片在它的缓存池中存在,才会直接加载该图,否则就会走 占位图->实际图 的流程。

  • 内存中存在的图片,Fresco为什么还会按不存在图片进行重新加载?

    这个问题,其实就是图片闪烁的本质原因,图片在内存,但是还是走了重新加载,就说明该图片不在fresco的缓存池中。也可以理解,Fresco也只能通过这种办法来确认一个对象是否存在。这种情况八成是缓存池被清空了!

  • 谁可能会清空缓存池?

    目前我知道的,Native中Fresco.initialize的重复初始化、RN中的FrescoMoudle中的mClearOnDestroy属性,都会清空缓存池,至少是内存缓存池。 FrescoModule:

    所以如果出现了类似问题,可以查下看是否有类似的调用

二、Fresco 缓存

1、Fresco缓存池大小

  • maxCacheEntries (bitmap实例最大值): 256个

  • maxCacheSize(bitmap总量大小):可能为4M、6M、8M,或者maxMemory/4

    private int getMaxCacheSize() {
        final int maxMemory = Math.min(mActivityManager.getMemoryClass() * ByteConstants.MB, Integer.MAX_VALUE); //Application分配的内存大小,由手机/system/build.prop中指定
          if (maxMemory < 32 * ByteConstants.MB) { 
            return 4 * ByteConstants.MB;
          } else if (maxMemory < 64 * ByteConstants.MB) {
            return 6 * ByteConstants.MB;
          } else {
            // We don't want to use more ashmem on Gingerbread for now, since it doesn't respond well to
            // native memory pressure (doesn't throw exceptions, crashes app, crashes phone)
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
              return 8 * ByteConstants.MB;
            } else {
              return maxMemory / 4;
            }
          }
    }
    

    实际测量:大客户端连续多个页面,峰值得确会达到256个数,内存大小一般没超过10M

2、Fresco.getImagePipeline().clearCaches()方法分析:

  • 结论: clearCaches方法会将自身fresco的一级二级缓存池清空,并且释放掉Bitmap强引用的连接,但是并不会主动触发Bitmap对象回收,即不会调用Bitmap.recycle()。只有在下次gc的时候这些BitMap才会被回收。

  • 分析过程: 通过Debug.dumpHprofData(absolutePath)方法抓取当前内存堆栈信息, 分别记录调用clearCaches和不调用的区别。 具体流程:从Activity B 通过startActivityForResult打开图片很多的Activity A页面,然后退出A页面。

    1、在图片很多的 Activity A界面的onPause()中记录原始BitMap信息

    2、在onPause方法中一组调用clearCaches,一组不调用,并在后面再记一次堆栈

    3、回到上一个Activity之后,在onActivityResult中再次记录内存堆栈,并触发一次GC

结果数据:
操作 clear
退出页面前
上一个页面
gc之后
  • 对比前两组数据,clear调用之后,bitmap的实例数量并没有大幅下降,只有触发gc之后,才大幅下降,说明clear并没有触发bitmap的收回,真正回收还是通过系统来回收的
操作 noClear
退出页面前
上一个页面
gc之后
  • 而对比noclear的数据,发下如果不释放缓存池直接GC,bitmap的数量并没有较大变化,说明GC并不能收回这些对象,可以推测fresco和bitmap的关系为强引用。

三、缓存池优化时机

Android系统内存Level :(ComponentCallbacks2类)

1、当应用处于缓存状态(例如切到后台了):
TRIM_MEMORY_COMPLETE = 80 :表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的最边缘位置,系统会最优先考虑杀掉我们的应用程序;
TRIM_MEMORY_MODERATE = 60 :表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的中间位置,如果手机内存还得不到进一步释放的话,那么我们的程序就有被系统杀掉的风险;
TRIM_MEMORY_BACKGROUND = 40 表示手机目前内存已经很低了,系统准备开始根据LRU缓存来清理进程。这个时候我们的程序在LRU缓存列表的最近位置,是不太可能被清理掉的,但这时去释放掉一些比较容易恢复的资源能够让手机的内存变得比较充足;

2、当应用处于运行时:
TRIM_MEMORY_RUNNING_MODERATE = 5 :表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经有点低了,系统可能会开始根据LRU缓存规则来去杀死进程了;
TRIM_MEMORY_RUNNING_LOW = 10 :    表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经非常低了,我们应该去释放掉一些不必要的资源以提升系统的性能,同时这也会直接影响到我们应用程序的性能;
TRIM_MEMORY_RUNNING_CRITICAL = 15:表示应用程序仍然正常运行,但是系统已经根据LRU缓存规则杀掉了大部分缓存的进程了。这个时候我们应当尽可能地去释放任何不必要的资源,不然的话系统可能会继续杀掉所有缓存中的进程,并且开始杀掉一些本来应当保持运行的进程,比如说后台运行的服务

3、其他: 
TRIM_MEMORY_UI_HIDDEN = 20 : 表示应用程序的所有UI界面被隐藏了,UI相关的资源最好在这时释放掉。
Fresco中提供的Trim Level

Fresco中提供的几种策略

OnCloseToDalvikHeapLimit(0.5)              // The application is approaching the device-specific Java heap limit
OnSystemLowMemoryWhileAppInForeground(0.5) // The system as a whole is running out of memory, and this application is in the foreground
OnSystemLowMemoryWhileAppInBackground(1),  // The system as a whole is running out of memory, and this application is in the background. 
OnAppBackgrounded(1);                      // This app is moving into the background, usually because the user navigated to another app.