问题介绍
在Android开发过程中,我们经常用Handler来处理异步操作的返回结果,却没注意到很容易发生内存泄漏,代码通常如下:
1 | public class MainActivity extends Activity { |
上面代码很常见,但是却是能引起内存泄露的,我们用Android Lint工具检查一下代码,会发现如下警告:
1 | Handler reference leaks |
然后具体解释如下:
1 | Since this Handler is declared as an inner class, it may prevent the outer class from |
问题分析
1. Android 方面
当Android应用启动的时候,系统会自动创建一个供主线程使用的Looper实例。
1 | /// file: frameworks/base/core/java/android/app/ActivityThread.java |
Looper的主要工作就是一个一个处理消息队列(MessageQueue, Looper构造方法中创建)中的消息对象。在Android中,所有Android框架的事件(比如Activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到Looper要处理的消息队列中,由Looper负责一条一条地进行处理。
我们在主线程中实例化一个Handler时,如果没有指定其它的Looper,那么它就会自动使用主线程的Looper,如下图所示log:
所以我们发送一条消息到此Handler时,实际上消息是进入了主线程的消息处理队列,而此消息已经包含了一个Handler实例的引用:
1 | /// file: frameworks/base/core/java/android/os/Handler.java |
当Looper来处理消息时,会据此引用来回调[Handler#handleMessage(Message)]:
1 | public static void loop() { |
2. Java方面
Java中的非静态内部类
以及匿名内部类
会持有外部类的引用。静态内部类
不会持有外部类的引用。
3. 内存泄漏所在
结合Android和Java方面的分析,我们应该很容易就知道了为什么会产生内存泄漏:
- 发消息到Handler,消息进入到主线程的消息队列后
持有对Handler的引用
; 做为非静态内部类,Handler又持有外部类(在这里是MainActivity)的引用
;- 只要消息没被处理,那么
MainActivity对象
就无法被垃圾回收器回收,进而导致MainActivity持有的很多资源都无法回收,这就是我们常说的内存泄漏。
泄漏解决方案
- 避免使用非静态内部类,前面有提到过
静态内部类
不会持有外部类的引用; - 将Handler放在单独的文件中,而不是以内部类方式存在;
- 当需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class MainActivity extends AppCompatActivity {
// 静态内部类,不会持有外部类的引用
private static class MyHandler extends Handler {
// 外部类的弱引用
private WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
// 确保外部类没有被回收
if (activity != null) {
// Do something ...
}
}
}
}
小结
其实在Android中很多的内存泄露都是由于在Activity中使用了非静态内部类导致的,就像本文提到的一样,所以当我们使用时要非静态内部类时要格外注意,如果其实例的持有对象的生命周期大于其外部类对象,那么就有可能导致内存泄露。个人倾向于使用文章的静态类和弱引用的方法解决这种问题。
课后思考
如下代码是否存在内存泄漏?
1 | public class MainActivity extends AppCompatActivity { |