Android内存泄漏
一、单例造成的内存泄露
单例模式是非常常用的设计模式,使用单例模式的类,只会产生一个对象,这个对象看起来像是一直占用着内存,但这并不意味着就是浪费了内存,内存本来就是拿来装东西的,只要这个对象一直都被高效的利用就不能叫做泄露。
但是过多的单例会让内存占用过多,而且单例模式由于其 静态特性,其生命周期 = 应用程序的生命周期
,不正确地使用单例模式也会造成内存泄露。
public class SingleInstanceTest {
private static SingleInstanceTest sInstance;
private Context mContext;
private SingleInstanceTest(Context context){
this.mContext = context;
}
public static SingleInstanceTest newInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstanceTest(context);
}
return sInstance;
}
}
上面是一个比较简单的单例模式用法,需要外部传入一个 Context 来获取该类的实例,如果此时传入的 Context 是 Activity 的话,此时单例就有持有该 Activity 的强引用(直到整个应用生命周期结束)。这样的话,即使该 Activity 退出,该 Activity 的内存也不会被回收,这样就造成了内存泄露,特别是一些比较大的 Activity,甚至还会导致 OOM(Out Of Memory)。
解决方法:
- 单例模式引用的对象的生命周期 = 应用生命周期(即使用Application Context);
- 使用弱引用(WeakReference)。
二、非静态内部类 / 匿名类
class 对比 | non static inner class | static inner class |
---|---|---|
与外部 class 引用关系 | 自动获得强引用 | 如果没有传入参数,就没有引用关系 |
被调用时需要外部实例 | 需要 | 不需要 |
能否调用外部 class 中的变量和方法 | 能 | 不能 |
生命周期 | 依赖于外部类,甚至比外部类更长 | 自主的生命周期 |
(一)非静态内部类
可以看到非静态内部类自动获得外部类的强引用,而且它的生命周期甚至比外部类更长,这便埋下了内存泄露的隐患。如果一个 Activity 的非静态内部类的生命周期比 Activity 更长,那么 Activity 的内存便无法被回收,也就是发生了内存泄露,而且还有可能发生难以预防的空指针。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyAsyncTask().execute();
}
class MyAsyncTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
可以看到我们在 Activity 中继承 AsyncTask 自定义了一个非静态内部类,在 doInbackground() 方法中做了耗时的操作,然后在 onCreate() 中启动 MyAsyncTask。如果在耗时操作结束之前,Activity 被销毁了,这时候因为 MyAsyncTask 持有 Activity 的强引用,便会导致 Activity 的内存无法被回收,这时候便会产生内存泄露。
解决方法:将 MyAsyncTask 变成静态内部类。
(二)匿名类
匿名类和非静态内部类最大的共同点就是 都持有外部类的引用,因此,匿名类造成内存泄露的原因也跟静态内部类基本是一样的。
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ① 匿名线程持有 Activity 的引用,进行耗时操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// ② 使用匿名 Handler 发送耗时消息
Message message = Message.obtain();
mHandler.sendMessageDelayed(message, 60000);
}
上面举出了两个比较常见的例子:
- new 出一个匿名的 Thread,进行耗时的操作,如果 MainActivity 被销毁而 Thread 中的耗时操作没有结束的话,便会产生内存泄露。
- new 出一个匿名的 Handler,这里我采用了 sendMessageDelayed() 方法来发送消息,这时如果 MainActivity 被销毁,而 Handler 里面的消息还没发送完毕的话,Activity 的内存也不会被回收。
解决方法:
- 继承 Thread 实现静态内部类
- 继承 Handler 实现静态内部类,以及在 Activity 的 onDestroy() 方法中,移除所有的消息 mHandler.removeCallbacksAndMessages(null);
三、集合类
集合类添加元素后,仍引用着集合元素对象,导致该集合中的元素对象无法被回收,从而导致内存泄露。
static List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object obj = new Object();
objectList.add(obj);
obj = null;
}
在这个例子中,循环多次将 new 出来的对象放入一个静态的集合中,因为静态变量的生命周期和应用程序一致,而且他们所引用的对象 Object 也不能释放,这样便造成了内存泄露。
解决方法:在集合元素使用之后从集合中删除,等所有元素都使用完之后,将集合置空。
objectList.clear();
objectList = null;
四、其他情况
(一)需要手动关闭的对象没有关闭
- 网络、文件等流忘记关闭
- 手动注册广播时,退出时忘记 unregisterReceiver()
- Service 执行完后忘记 stopSelf()
- EventBus、RxJava等观察者模式的框架忘记手动解除注册