这篇博文总结一下最近开发中遇到的内存泄漏的场景,并提供一些我所能找到的解决方案。
静态全局类
说起静态全局类,在Android里面用的最多的要数全局单例类(单例模式)。大多数人在构造单例类的时候,都会毫不犹豫地使用这种模板:
1 | public class Singleton { |
这个时候问题来了:类中的Context
指的是什么?想想平时用的时候是不是都会毫不犹豫地传入this
,就是Activity
本身。而对于单例类来说,mContext
成员是永远不会被回收的,也就是说,这个引用指向的Activity
无法被回收,从而造成内存泄漏。Google在这篇博客http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html 中提出了解决办法,用Application
代替Activity
,因此我们可以这样改进:
1 | public class Singleton { |
在传入Context
的时候检查类型,强制外部传入Application
。由于后者的生命周期属于整个应用,所以不存在内存泄漏问题。
如果单例类确实需要Activity
的上下文(比方说需要做些UI相关的操作),那么可以提供一个detech()
方法:
1 | public void detech() { |
在onDestroy
方法中调用单例类的detech()
来解除引用关系,防止泄漏。
但通常来说,涉及到UI相关的工作,更提倡用回调来执行,于是就有了更进一步的改进:
1 | public class Singleton { |
这样,可以在Activity
中向单例类传入一个CallBack
,并在doSomethingInUI()
回调中做一些UI相关的事情。需要注意的是,由于这个CallBack
属于Activity
的内部类,这个内部类会拥有外部类Activity
的引用,所以需要在onDestroy()
方法中调用单例类的removeCallback()
类来解除引用关系,防止内存泄漏。
<br>
WebView
WebView
一直是我不敢轻易使用的组件(个人感觉越强大的组件,菜鸟用起来越危险)。最近需要重点使用到这个组件,但却遭遇内存泄漏的问题。上网一查才知道,这是Google留给开发者的大坑:WebView
自带内存泄漏属性。而且考虑到Android的碎片化情况严重,不同版本的系统泄漏的问题可能还不一样,比如,Android4.4以前的内核采用webkit,而4.4及以后就用chromium内核,所以4.4之前的解决方法可能在4.4及以后的系统不适用。简直蛋疼到极点~囧~
在实战的时候,我发现WebView
会持有原Activity
的引用,即使在onDestroy()
中将WebView
置空也会导致Activity
泄漏(大概是jni层有指针没有释放这个Activity
),加上这个Activity
的内容比较多,稍不留神便OOM。
再经过一天的搜索后,我找到一种可以暂时解决这种问题的方法,关键代码是这样的:
1 | private WebView webView; |
个人认为这种方法的关键是将WebView
与Application
绑定,也就是这句代码
webView = new WebView(getApplicationContext());
。而这也意味着你不能在xml中声明webview
节点,而必须动态添加进去。另外,在onDestroy()
方法中必须手动将WebView
从整棵View树中移除(至少在Android4.4的华为P7上需要这样做)。
前面说这种做法能暂时解决问题,但如果WebView
需要用到Activity
其他的元素,那么它会将Context
强转为Activity
对应的Context
,这时这种做法就会出问题。
那有没有什么方法来“根治”这种问题呢?胡凯在他的文章中提到这种方法:
Android中的WebView存在很大的兼容性问题,不仅仅是Android系统版本的不同对WebView产生很大的差异,另外不同的厂商出货的ROM里面WebView也存在着很大的差异。更严重的是标准的WebView存在内存泄露的问题,看这里WebView causes memory leak - leaks the parent Activity。所以通常根治这个问题的办法是为WebView开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
也就是通过一条新的进程来控制WebView
的生命周期。这种方法可以更好地防止内存泄漏影响到主进程。目前我尚未尝试这种方法(好吧还没学会)。
<br>
内部类
内部类指的是那些在类的内部定义的类,又称嵌套类。Java中的内部类会持有外部类的引用,这是虚拟机帮我们处理的(所以才能在内部类中通过.this
获得外部类)。如果内部类泄漏了,那么会进一步导致外部类也泄漏。
这种情况发生最多的案例是Activity中持有Handler的引用。Handler的生命周期比较特殊,当Handler发送Message到MessageQueue时,Message会持有一个名为target
的引用,这个引用就是Handler本身。熟悉Handler的童鞋知道,MessageQueue
是跟线程绑定在一起的消息队列,而我们用的最多的一般都是UI线程的MessageQueue
。所以如果Activity有一个继承自Handler的内部类,在Activity启动finish()
方法的时候,如果该Handler还在发送消息(即MessageQueue
间接持有了该Handler
的引用),便容易导致Activity的泄漏。
解决办法有两个,其一是将Handler声明为static,因为静态内部类不会持有外部类的引用,如果要在静态内部类中使用外部类的成员,可以通过WeakReference
来持有外部类的引用。另一种方法是将Handler定义在单独的类文件中。方法的选择可以依个人喜好决定。