if (child instanceof ViewGroup) {
child.setBackground(null);
traverse((ViewGroup) child);
} else {
if (child != null) {
child.setBackground(null);
}
if (child instanceof ImageView) {
((ImageView) child).setImageDrawable(null);
} else if (child instanceof EditText) {
((EditText) child).cleanWatchers();
}
}
}
}
我们在基类BaseActivity的onDestory()方法中进行了一些资源和引用的清除
## 三、内存峰值太高
在我们把能fix的内存泄漏都盘了一便之后,上线一周并没有发现数据好转,OOM率还是高居不下,于是乎,我们开始怀疑内存峰值太高的问题,在我们的项目中不仅仅只有native的部分模块,还有混合的H5、RN模块,当起一个ReactActivity的实例时,内存峰值总是涨的特别特别厉害,同时项目中有消息流的展现,其中会包含着大量的图片展示,这也是导致内存峰值太高的原因(Bitmap对象太大以及太多)
我们又拿出了老伙伴 - Profiler,这可是分析bitmap对象的利器,可以直接看到大小、图片的预览,以及可以通过 go to instance一层一层的找到到底是谁在引用它。比如下面这个例子,直接看引用就知道是被Fresco所引用了~ 直接就在CountingMemoryCache中。
![](https://upload-images.jianshu.io/upload_images/15233854-d5c220038b41e3e8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
其实我们主要还是需要去关注Bitmap对象的分配和不合法持有导致的内存峰值问题,如果一个bitmap对象有3M,然后持有一个几十上百个在内存中,这谁吃得消,低端机器老早直接OOM了。
### 查Bitmap分配查出来的问题
目前我们项目中用的图片加载框架有两个,UIL、Fresco,UIL我吐槽很久了,这么多年没更新,老早就该换了~?
**1\. UIL加载图片在我们项目中的问题:**
* 没有传入合适的Config,绝大多数地方传的都是ARGB_8888,其实根本没必要,改成565直接少一半内存占用
* 用UIL进行loadImage时,没有传入targetSize,这就直接导致了UIL内部是以屏幕的尺寸去Decode的Bitmap对象,想象一下,一个特别小的头像View,持有着一个屏幕大小尺寸的Bitmap对象,这谁顶得住。
* 许多地方不需要存内存缓存,比如闪屏广告图,app启动之后就不会再使用了,可以加载的时候 memoryCache(false)
* 许多地方不需要磁盘缓存,比如发布动态,从图库中选图,不需要再存一份磁盘缓存了,本身那些图片都是本地图片。直接 diskCache(false)
**2.Fresco在RN页面中使用的问题,**
通过看代码可以知道,RN页面销毁的时候,连带着Fresco的内存缓存都会被清空,
直接上代码图:
![](https://upload-images.jianshu.io/upload_images/15233854-c11be42449899c72.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
代码看到这里,似乎Fresco不用担心了,既然会清空Fresco的内存缓存,何愁会引起内存峰值过高,如果读者看到这里,也有这个想法,那就大错特错了。话不多说,直接上图。
![](https://upload-images.jianshu.io/upload_images/15233854-22712ddad26f551f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
Fresco相关源码的逻辑这篇文章就不分析了,主要讲思路,具体的源码分析后面我会用单独的篇幅去讲~?
为什么我会对Fresco的动图缓存这么敏感,那还是Profiler的功劳,我在用Profiler查看内存中bitmap的分配的时候,发现有上百张的Loading图没有销毁(我们Loading图是动图,大概每帧的Bitmap对象在360K左右), 且打开的页面越多,Loading的bitmap就会越多。(这是因为我们每一个RN页面都会带一个Loading动画)
0.3M * 100 = 30M,不少了。。。,说实话有点恐怖
于是乎,干掉他们,这里用了反射,正常情况下不需要反射。直接拿ImagePipelineFactory中的对象来clear就好
public static void clearAnimationCache() { if (frescoAnimationCache == null) { //采用反射的方法,如果native、rn同时初始化Fresco,会造成Fresco内部存储动图的CountingMemoryCache不是Fresco.getImagePipelineFactory().getBitmapCountingMemoryCache()了 //暂时用反射的方法,拿到存储动图缓存的cache,并清空 try { Class imagePipelineFactoryClz = Class.forName(“com.facebook.imagepipeline.core.ImagePipelineFactory”); Field mAnimatedFactoryField = imagePipelineFactoryClz.getDeclaredField(“mAnimatedFactory”); mAnimatedFactoryField.setAccessible(true); AnimatedFactoryV2Impl animatedFactoryV2 = (AnimatedFactoryV2Impl) mAnimatedFactoryField.get(Fresco.getImagePipelineFactory()); Class animatedFactoryV2ImplClz = Class.forName(“com.facebook.fresco.animation.factory.AnimatedFactoryV2Impl”); Field mBackingCacheField = animatedFactoryV2ImplClz.getDeclaredField(“mBackingCache”); mBackingCacheField.setAccessible(true); frescoAnimationCache = (CountingMemoryCache) mBackingCacheField.get(animatedFactoryV2); } catch (Exception e) { Log.e(“FrescoUtil”, e.getMessage(), e); } } if (frescoAnimationCache != null) { frescoAnimationCache.clear(); } Fresco.getImagePipelineFactory().getBitmapCountingMemoryCache().clear(); Fresco.getImagePipelineFactory().getEncodedCountingMemoryCache().clear(); }
### 又一个兜底方案
为了防止峰值过高,我们还起了一个线程,定时的去监控实时的内存使用情况,如果内存紧急了,直接清空UIL/Fresco的内存缓存救急
private static Handler lowMemoryMonitorHandler;
private static final int MEMORY_MONITOR_INTERVAL = 1000 * 60;
/**
* 开启低内存监测,如果低内存了,作出相应的反应
*/
public static void startMonitorLowMemory() {
HandlerThread thread = new HandlerThread("thread_monitor_low_memory");
thread.start();
lowMemoryMonitorHandler = new Handler(thread.getLooper());
lowMemoryMonitorHandler.postDelayed(releaseMemoryCacheRunner, MEMORY_MONITOR_INTERVAL);
}
/**
* 低内存时清空Fresco、UIL的内存缓存
* 如果已用内存达到了总的 80%时,就清空缓存
*/
private static Runnable releaseMemoryCacheRunner = new Runnable() {
@Override
public void run() {
long alreadyUsedSize = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long maxSize = Runtime.getRuntime().maxMemory();
if (Double.compare(alreadyUsedSize, maxSize * 0.8) == 1) {
BitmapUtil.clearMemoryCaches();
}
lowMemoryMonitorHandler.postDelayed(releaseMemoryCacheRunner, MEMORY_MONITOR_INTERVAL);
}
};
## 五、特大图排查优化
我想大家都不会想到,在我们app的登录注册页,会有一个图片轮播控件,它轮播着五六张单张6M+的Bitmap。。。当然,特大图不仅限于此,还有其他地方会有相同情况,我们通过Profiler找出那些大的bitmap对象,然后预览之后确定是哪里在用的。
直接优化掉。最不济 8888 -> 565就少一半内存占用
**怎么讲呢,,OOM这个东西,还没咋僵持呢,就没了。**。
## 六、总结
深夜一时兴起想分享和记录一些什么,就随便写了这一篇博客,写的不详细,没有排版和良好的语言组织,单纯的就是想分享
总结一下吧,我们为了fix OOM所做的事情:
**1. 检查内存泄漏**,包括常见的Context泄漏、单例泄漏、EditText的TextWatcher泄漏等等,找到并fix他们,最简单的例子,能传application的地方就不要硬传个activity过去
**2. 兜底方案:**
* 在Activity onDestory的时候,遍历View树,清空backGround、Drawable、EditText的TextWatcher等
**3. 内存峰值的优化**。内存泄漏会导致内存峰值,内存峰值是OOM的大锅,举个例子当可用内存不够分配一个Bitmap对象时,就会OOM,Android上大多数的内存峰值都是图片的加载带来的。现在许多的app中都有信息流的展现,可能会有许多的九宫格展示图片,且Bitmap对象本身就可以非常大。
* 优化UIL的使用
* memoryCache选用,不是所有的图片加载都需要UIL去塞一份内存缓存的,比如闪屏图
* ImageLoader.getInstance().displayImage()的时候,传进去的Option不要无脑ARGB_8888,讲道理来说,无脑RGB_565都是没啥问题的。。
* 调用displayImage的时候,最好传一个ImageSize作为targetSize,这个size可以是你的ImageView的尺寸,当View尺寸本身不确定的时候,可以传一个大概值,比如我们app中有好些个的头像标准尺寸,为了偷懒,直接传MaxAvatarSize就ok
* Fresco的优化
* RN中使用Fresco加载图片,在RN Activity销毁的时候,会将Fresco默认的memory cache清空,但是动图的缓存没有清。手动清一下。我们项目中每个RN页面都会带一个Loading动图,所以吃了大亏。。
**5. 持续的后台监控内存,**起一个HandlerThread,一直在后台拿内存使用的状态,达到了危险警戒线就清空一把UIL、Fresco的memory cache,先让世界安静一下
**6. 需要对内存泄漏、OOM、Crash、ANR进行监控**
|