Android内存泄露

做过Android的应该知道,如果不关心内存的泄露问题,就会导致内存溢出,那么应用就会出现随时崩溃的问题,用户体验很差,就比如我最早做的一个项目,其中包含考试的模块,一开始也没有做考试备份的功能,也就是放弃考试必须重新考试。有一次测试中,100道题快要做完时,因为有一题包含了图片,所以崩溃了,然后所有的都完蛋了,我自己作为开发者都快崩溃了,更不要说那些认真答题的使用者了。所以为了用户的满意度必须解决内存泄露的问题。因为Android上层使用的是Java,所以大家知道Java的特性就应该知道Android为什么会存在内存泄露了。

##1、static静态变量内存泄露##

问题:
有时我们为了方便各个组件之间传递大数据不得不使用static变量,因为static变量是属于类的,所以可以之间使用类调用,还是那个包含考试的项目,因为需要把试题传递到另一个Activity使用,但数据本身太大超过了Intent传递数据的上限。所以使用了静态变量,最后也没有释放,这就产生了泄露,还有就是类似于Activity的静态变量,也是一个很早的项目里面涉及到电话监听,我忘了是监听电话开始还是结束状态,但监听会调用两次,然后会启动两次Activity,这时问题就出来了,当时没什么技术,也没解决问题的理念,所以竟然把Activity当成了静态变量保存在了那个Activity中,具体的我已经忘了,但是我知道最后Activity中的静态对象没有释放,也就是永远会把最后一个实例化的Activity放在静态变量中,没有释放。
解决方案:
这样做本身没有太大的问题,但如果不释放内存就有问题了,在这中情况下,只需要在不需要的时候释放内存就行了,比如上面两个问题,其实只要在Activity的onDestory方法中设置为null,就把引用释放了。

##2、资源未关闭或未回收导致的内存泄露##
问题:
数据库Cursor没关闭,IO流没关闭,当使用SQLite数据库没有关闭Cursor,以及文件流没有关闭都会导致内存泄露。
解决方案:
调用Cursor.close(),以及IO的close方法。

##3、未取消注册广播接收者##
问题:
使用广播时只注册没有取消注册。
解决方案:
registerReceiver()和unregisterReceiver()要成对出现,通常需要在Activity的onDestory()方法去取消注册广播接收者。

##4、第三方jar包使用不当导致的内存泄露##
问题:
使用第三方jar包没有注意使用细节出现,比如eventBus只注册而忘记取消注册,百度地图或高德地图没有调用map的onDestory的方法等等。
解决方案:
使用第三方是要注意其使用注意事项及时释放引用。当然也有的第三方jar包本身就有问题,不提供释放方法,比如我就遇到过一个gif播放jar包,使用过后肯定会导致泄露,最后只能用反射把引用对象设置为null才把泄露解决,这也就要求我们选择jar包时要经过测试,不能遇到解决方法就用,当然还是我当时水平不行,其实当时应该修改源码再打成jar的。

##5、Context使用不当导致的内存泄露##
问题:
我以前写了并收集了一些公共方法类,然后在项目中使用而这些方法类中使用了Context,而且只要是Context就可以。当时觉得每次传一个Context参数太麻烦然后写了一个UtilsConfig类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UtilsConfig{
private static Context mContext;
private UtilsConfig(){
throw new RuntimeException("UtilsConfig不能实例化");
}
public static init(Context mContext){
UtilsConfig.mContext = mContext;
}
public static Context getContext(){
if(mContext == null){
throw new RuntimeException("UtilsConfig 未初始化");
}
return mContext;
}
}

可以看出如果在一个Activity中调用该方法,肯定会产生泄露。
解决方案:
该方法其实使用的是ApplicationContext,所以只要在Application类中初始化就可以了。当然我们要看到另一个问题,就是如果所需要的参数Application本身就可以那么就不要使用Activity等,因为Application的生命周期是全局的所以不会产生内存泄露。一个原则就是能用Application就用Application,当然我的上一个方法后来也进行了改进。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UtilsConfig{
private static Context mContext;
private UtilsConfig(){
throw new RuntimeException("UtilsConfig不能实例化");
}
public static init(Context mContext){
UtilsConfig.mContext = mContext.getApplicationContext();
}
public static Context getContext(){
if(mContext == null){
throw new RuntimeException("UtilsConfig 未初始化");
}
return mContext;
}
}

##6、Handler导致内存泄露##
问题:
知道Handler原理的都清楚,Handler模块主要有三个类组成,分别是Handler、Looper以及MessageQueue三部分组成。Handler负责消息处理,Looper主要负责消息循环获取,MessageQueue就是消息队列管理,而每个线程只能有一个Looper以及MessageQueue,可以有多个Handler,而我们使用时基本上是在主线程上使用,也就是说我们是在使用主线程的Looper、MessageQueue。而这个MessageQueue的生命周期是全局的,所以如果对象被MessageQueue持有引用则无法被回收。如果是使用其他线程的Handler,如果那个线程持续运行,而Looper也没停止·,结果是一样的。引用链为
MessageQueue 持有 Message ,
Message 持有 Handler,
Handler 持有 View 或 Activity。
从而导致内存泄露。
解决方案:
如果只是MessageQueue导致的内存泄露。只需要调用。Handler.removeCallbacksAndMessages(null);即可,这会请求MessageQueue中所有该Handler的Message。如果是线程持有该对象,该对象又是内部类导致的泄露放在下面。

##7、内部类或匿名类导致内存泄露##
问题:
我们知道非静态内部类持有外部类的引用,匿名类同样会持有定义它们的对象的引用。而匿名类或内部类对象又被其他类持有引用导致内存泄露,比如我写了一个天气预报的模块,我直接使用的中国天气网的数据,所以网络请求做了单独写,然后我用匿名类对象做了网络请求回调,然后在请求结束之前退出,网络请求继续,从而Activity也泄露了。
解决方案:
内部类泄露一般解决方案是该为静态内部类,然后静态内部类持有Activity的弱引用。匿名类也可以先该为静态类,然后再实例化,其他和内部类处理方式一致,其实如果在Activity执行onDestory时把持有内部类或匿名类对被持有引用的地方置为null即可。

##8、异步任务导致内存泄露##
问题:
这里的异步任务我把线程以及定时器都作为了异步任务。
解决方案: