写点什么

Android:彻底消灭 OOM 的实战经验分享(千分之 1-5---- 万分之 0-2)

用户头像
Android架构
关注
发布于: 1 小时前

final int childCount = root.getChildCount();


for (int i = 0; i < childCount; ++i) {


final View child = root.getChildAt(i);


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 中。



其实我们主要还是需要去关注 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 的内存缓存都会被清空,


直接上代码图:



代码看到这里,似乎 Fresco 不用担心了,既然会清空 Fresco 的内存缓存,何愁会引起内存峰值过高,如果读者看到这里,也有这个想法,那就大错特错了。话不多说,直接上图。



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;/**


  • 开启低内存监测,


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


如果低内存了,作出相应的反应*/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() {@Overridepublic 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 进行监控


一些其他的细节暂时想不起来了,凌晨四点脑子不清醒了


后续关于这里面涉及到的 Fresco 的部分源码分析、Profiler 的最佳使用姿势(经过这一次的折腾,总结出来一句话,Profiler 真香)、以及前段时间在做的 App 的启动速度优化等等等等等都会单独拎文章去分享,后续也会带来更多,涉及的内容包括但不限于:


  • 主流框架的一些设计思想的分享

  • 工作项目中遇到的麻烦和坑

  • 工作中蹚坑的一些经验

  • 好代码

  • 坏代码

  • 坏的设计

  • 程序员从头发浓密到成为下雨天报警员的心路历程

  • 。。。


原文链接:https://juejin.im/post/5d617d6851882575e8054fab


好了,文章到这里就结束了,如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。


希望读到这的您能转发分享和关注一下我,以后还会更新技术干货,谢谢您的支持!


转发+点赞+关注,第一时间获取最新知识点


Android 架构师之路很漫长,一起共勉吧!

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android:彻底消灭OOM的实战经验分享(千分之1-5----万分之0-2)