IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android:彻底消灭OOM的实战经验分享,阿里专家原创 -> 正文阅读

[移动开发]Android:彻底消灭OOM的实战经验分享,阿里专家原创

     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进行监控**
 
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-01 12:02:26  更:2021-09-01 12:04:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年10日历 -2024/10/24 8:30:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码