Android内存泄漏的检测与分析以及解决方案小结
一 什么是内存泄漏
1.1 简述
内存泄漏也就时本该被GC回收掉的东西由于某种原因没有被回收掉,从而长期的存留在内存中无法被回收。这就是内存泄漏。其它的大道理可以自行百度。
1.2 什么原因引起内存泄漏的
初次遇到内存泄漏的时候一直以为是由于加载了大量的图片引起的,但是我采用的是Fresco图片加载框架,理论上不会是图片导致的,但我还是固执的以为是,折腾半天发现没有反应。这是自己踩的第一个坑,既然入坑了就得想法出坑,于是我就到处查找资料 大多数都是你抄袭我我抄袭你,说的有的没得,什么图片压缩啊之内的,但经过我的不懈努力找到一篇很NICE的文章,跟着前人的脚印,一步一步的进行分析,最终解决的项目的内存泄漏问题。
言归正传 什么原因导致内存的呢?
首先如果你的图片加载采用是市面上比较主流的加载框架,大概率可以排除,因为它们都会自己管理好自己的回收机制。
这里列出自己总结的几点
1. 内部类隐性的持有外部类的引用导致内存泄漏
我们开发中经常会遇到使用内部类,比如如下代码在你的代码中肯定有。
private Handler Myhander=new Handler(){
/***具体的业务逻辑*****/
}
//发送异步消息
Myhander.sendEmptyMessage(AllCode.TIME_RESTUR_1);
以上是我们常用的异步机制这里的Myhander其实就是一个内部类,它隐性的持有着外部的引用,当这个外部引用被销毁时这个内部类还在继续工作的话就会让GCRoot的标记可达,GC也就无法回收这个Activity了,你的少的可怜的内存就这样被占用掉而无法回收。
这种问题我们需要根据自己的实际情况去解决。大致的方向是
将非静态的内部类或者匿名内部类修改为静态内部类,就如这里的Handler 修改为静态的内部类,然后弱引用外部的Activity。在Activity时停止你的任务,这样GC就能够正常的回收这个Activity了。
修改方法
/**以下为伪代码**/
private MyHaner myHaner=new MyHaner(this);
private static class
MyHaner extends Handler {
WeakReference<Activity> activity;
MyHaner(Activity activity) {
this.activity=newWeakReference<Activity>(activity);
}
public void handleMessage(Message msg) {
/*****自己的业务逻辑***/
activity.get().xxx;//调用这个引用的一些方法或者变量等
}
}
}
在需要使用的地方
myHaner.sendEmptyMessage(AllCode.TIME_RESTUR_1);
在Activity销毁的时候释放掉。
myHaner.removeCallbacksAndMessages(null);`
myHaner=null;
以上方法只是这种情况下的处理方式,后面我会介绍如何进行内存分析。找到你自己应用内存泄漏的原因才能去做修复。
2. 静态变量导致内存泄漏
由于静态变量的生命周期和应用一样长,所以如果静态变量持有 Activity 或者 Activity 中 View 对象的应用,就会导致该静态变量一直直接或者间接持有 Activity 的引用,导致该 Activity 无法释放,从而引发内存泄漏,不过需要注意的是在大多数这种情况下由于静态变量只是持有了一个 Activity 的引用,所以导致的结果只是一个 Activity 对象未能在退出之后释放,这种问题一般不会导致 OOM 问题,只能通过上面介绍过的几种工具在开发中去观察发现。
这种问题的解决思路很简单,就是不让静态变量直接或者间接持有 Activity 的强引用,可以将其修改为 soft reference(软引用) 或者 weak reference(弱引用) 等等之类的,或者如果可以的话将 Activity Context 更换为 Application Context,这样就能保证生命周期一致不会导致内存泄漏的问题了。
3. 错误的使用Activity Context
这个很好理解,在一个错误的地方使用 Activity Context,造成 Activity Context 被静态变量长时间引用导致无法释放而引发的内存泄漏,这个问题的处理方式也很简单,如果可以的话修改为 Application Context 或者将强引用变成其他引用。
4. 资源对象没关闭造成的内存泄漏
这里在新手玩家可能会出现这些错误,但我们开发项目数据库这样的操作大概率会集成GreenDao这样高效的数据框架。所以一下的问题可以大概看下。
资源性对象比如(Cursor,File 文件等)往往都用了一些缓冲,我们在不使用的时候应该及时关闭它们,以便它们的缓冲对象被及时回收,这些缓冲不仅存在于 java 虚拟机内,还存在于 java 虚拟机外,如果我们仅仅是把它的引用设置为 null 而不关闭它们,往往会造成内存泄漏。但是有些资源性对象,比如 SQLiteCursor(在析构函数 finalize(),如果我们没有关闭它,它自己会调 close() 关闭),如果我们没有关闭它系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的 close() 函数,将其关闭掉,然后再置为 null,在我们的程序退出时一定要确保我们的资源性对象已经关闭。
5. 集合中对象没清理造成的内存泄漏
在实际开发过程中难免会有把对象添加到集合容器(比如 ArrayList)中的需求,如果在一个对象使用结束之后未将该对象从该容器中移除掉,就会造成该对象不能被正确回收,从而造成内存泄漏,解决办法当然就是在使用完之后将该对象从容器中移除。
我项目中之前有个集合负责容纳所有被打开的Activity 在用户退出应用时释放掉集合中的所有Activity。看起来没有什么问题,但是我在一个Activity启动后加入进去,却忘记在销毁的时候去移入,从而导致了集合一直持有着Activity从而内存无法被释放。所以在开发中一定要注意这些问题。
二 内存泄漏检测与分析
2.1 使用Android Studio 自带的工具分析
2.1.1 如何判断内存释放泄漏
第一步:首先我们可以打开以下页面,观察一下你应用消耗的内存。
打开内存查看面板第二步:然后操作你的应用查看比如打开你觉得比较消耗内存的页面,然后退出这个页面。点击一下上图的小货车按钮手动进行一次GC操作(进行分析前必须要做的操作)。 在分析前手动进行一次GC操作
第三步:Dump一下当前的内存情况
进行Dump 等待分析结果分析完成后会自动打开这个报告
查看分析报告 进行内存的自动分析第四步:进行自动分析 进行自动分析 查看分析结果
当你看到分析结果时如果出现上图中的Leaked Activities那真的很不幸 你的应用出现了内存泄漏那么你就要去查看对应的类是如何造成的了。这里一大堆东西 该从何入手确实比较迷茫,如果那么这么多东西应该怎么去分析呢?那么我们就需要借助另外一个工具了------ MAT
2.2 利用MAT工具进行进一步分析 确定问题来源
第一步 下载并安装MAT
你可以自行百度MAT的独立运行版本到你的电脑,完成安装。
第二步 从Android Studio导出标准的hprof文件
先前通过Android Studio Dump后生产的文件并不能直接导入到MAT中使用 我们需要转成标准的格式步骤如下
第三步 将文件导入到MAT中 打开刚刚导出的文件
第四步 利用工具进行分析
打开后的页面 点击图上的按钮 查看分析结果 点击OQL按钮 进行精确查找 泄漏的Acitivyt 查看泄漏的点 工具查找的结果 经过工具的查找 给你列举了对应的持有链,你要让这个Activity能够正常回收,你必须根据这些数据来分析到底是代码那个位置的引用造成了引用链可达,导致GC无法回收掉这个Activty。通过上面这里 发现有个Toast持有了引用,然后又两个内部类持有了引用。
那么我回到对于的Activity中去查看一下具体情况。这个Activity非常简单 有两个按钮 一个是返回,一个是启动一个异步线程 然后隔一段时间弹出一个Toast.一下是代码片段
通过MAT给出的线索以及上面提到的内部类会持有外部引用,所以初步断定是由于我启动了异步线程,然后点击了返回按钮,页面释放,但是这个Handler会在10秒后弹出提示框导致这个Activty的引用在销毁后还是处于可达的状态,那么内存回收机制就无法顺利的回收掉这个Activty了,这个Activity会常驻与内存中,最后极少成多 导致OOM异常。那么解决方法如下
修改后的代码片段通过以上的软引用可以让系统顺利的回收掉这个Activity。
三 利用LeakCanary进行简单粗暴的分析
LeakCanary 是一个 Android 内存泄漏检测的神器,正确使用可以大大减少内存泄漏和 OOM 问题,它能将具体的泄漏点已经引用链清晰的列举出来,你要做的就是去查看具体代码,然后想方设法的去剪断引用链,让GC能够回收。地址:
四 最后小结
这里只是一个小的DMO 实际的开发中情况会比这个复杂很多,你要根据工具给出的线索来找到代码中的问题,记住几个要点来排查和解决问题,需要细心和耐心。不要着急和害怕,冷静的去分析问题想法设法的去解决。
我这里介绍的不够完整,是自己总结起来方便自己以免以后踩坑。
参考的文章