继介绍APP稳定性问题:冻屏ANR、应用崩溃篇:Crash/Tombstone后,本期我们将重点介绍资源泄露问题。
资源泄露问题原因分析
典型泄露场景
静态变量长期维持到大数据对象的引用,阻止垃圾回收;
非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被回收掉;
Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。在Message中存在一个 target,是Handler的一个引用,如果Message在Queue中存在的时间越长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。由于AsyncTask内部也是Handler机制,同样存在内存泄漏的风险,这种内存泄露,一般是临时性的;
资源对象未关闭
- Cursor
- InputStream OutputStreamIO流
- BraodcastReceiver
- ContentObserver
- Bitmap
应该在资源使用完毕,或者Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
常见内存泄露代码举例
单例造成的内存泄露
context改为context.getApplicationContext():因为单例的生命周期和Application的一致。
非静态内部类创建静态实例造成的内存泄漏
这样就在Activity内部创建了一个非静态内部类的实例,每次启动Activity时都会使用该实例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为: 将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext。
Handler造成的内存泄露
当这个Activity被finished后,延时发送的消息会继续在主线程的消息队列中存活10分钟,直到他们被处理。这个message持有handler对象,这个handler对象又隐式持有着MainActivity对象.直到消息被处理前,这个handler对象都不会被释放, 因此MainActivity也不会被释放。注意,这个匿名Runnable类对象也一样。匿名类的非静态实例持有一个隐式的外部类引用,因此MainActivity将被泄露。
内存分析工具、常用命令及分析步骤
1. 内存分析工具
Android Monitor
MAT(MemoryAnalyzer Tool)
2. 内存分析常用命令
adb shell getprop | findstr heapgrowthlimit //查看当前产品单个进程支持最大内存
adb shell dumpsys meminfo //获取系统各个应用内存信息
adb shell dumpsys meminfo com.android.mms //获取短信内存信息
adb shell am dumpheap com.android.mms /sdcard/mms1.hprof //导出当前应用hprof文件
adb shell cat /proc/meminfo //查看当前内存占用情况
hprof-conv from.hprof to.hprof //hprof文件转换
3. 分析内存泄露步骤
通过Android Monitor的内存监控工具找到内存增长且GC后无法降低的必现条件;
根据dump的hprof文件,使用AnalyzerTasks找到疑似泄露的Activity;
使用MAT查看疑似泄露Activity的GC Root ,找到引用链中的泄露点;
解除依赖关系。
避免内存泄露的建议
1)对于生命周期比Activity长的对象如果需要应该使用ApplicationContext。
2)在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:
其中:NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建。
3)对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。
4)对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,将内部类改为静态内部类,静态内部类中使用弱引用来引用外部类的成员变量。
5)对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null。
6)保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。