如何防止内存泄漏
#Memory Leak
考虑下面的代码块:
1 | public class SampleActivity extends Activity { |
也许不是那么明显,这块代码能造成大量的内存溢出。Android Lint 会提示如下警告
In Android, Handler classes should be static or leaks might occur.
但是内存是在哪儿溢出的,是如何发生的?首先通过列出我们已经知道的事实来找问题的根源:
1.当Android应用程序第一次启动时,framework在应用程序的主线程里创建了一个Looper对象。Looper实现了一个简单的消息队列,依次处理队列里的消息对象。所有的应用程序框架里的事件(比如Activity的生命周期函数调用,按钮点击事件等等)都封装为消息对象,被加入到Looper的消息队列中并依次处理。主线程的Looper在整个应用的生命周期里会一直存在。
2.当在主线程里实例化一个Handler时,就和Looper的消息队列关联在一起了。发送到消息队列的消息会持有Handler的引用,使得当Looper最后处理消息时候,framework可以调用Handler#handleMessage(Message).
3.在Java中,非静态匿名内部类会持有它所在外部类的隐式引用。而静态内部类不会。
那么到底内存是在什么地方泄漏的?非常巧妙,思考下面一段示例代码:
1 | public class SampleActivity extends Activity { |
当activity结束掉,延迟的消息会在主线程的队列中生存10分钟,直到被处理。该消息持有activity Handler
的引用,且Handler
持有外部类的隐式引用(当前情况下是SampleActivity).该引用会持续存在直到消息被处理,因此妨碍了Activity Context
被垃圾回收,从而导致应用程序资源泄漏。注意在15行的匿名内部类会导致一样的情况。非静态匿名内部类的实例持有它们外部类的的隐式引用,因此Context
会泄漏。
为了修复这个问题,创建一个类继承Handler
或者使用静态内部类。静态内部类不会持有它的外部类的隐式引用,所以activity不会溢出。如果需要在Handler
中调用外部activity的方法,使Handler
持有activity的WeakReference
,因此不会导致Context
意外溢出。当实例化匿名的Runnable
类时,为了修复内存溢出,为这个类创建一个静态域(因为匿名内部类的静态实例不会持有它的外部类的引用):
1 | public class SampleActivity extends Activity { |
静态和非静态内部类之间的区别很微妙,但每个Android程序员都应该理解它。底线是什么?如果内部类的实例比activity的
生命周期长,避免在activity中使用非静态内部类。相反,推荐使用静态内部类,持有activity的弱引用。