这篇博文总结一下最近开发中遇到的内存泄漏的场景,并提供一些我所能找到的解决方案。
静态全局类
说起静态全局类,在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定义在单独的类文件中。方法的选择可以依个人喜好决定。