Android内存泄漏的常见场景及解决方案

时间:2023-01-21 20:56:21

哲学老师说,看待事物无非是了解它是什么,为什么,怎么做

所以,首先,我们先了解一下什么是“内存泄漏”

 

摘自百度的一段话:用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。

是不是有点拗口,换一种说法,有天你去一家饭店吃饭,有个胖子吃完饭了,却霸占着一张桌子不走,然而现在一堆人等着吃饭,结果那死胖子等到饭店打烊了才离开。

在这个例子中,饭店的桌子就好比内存空间,那个胖子就是一个函数,吃饭就是所执行的事件。

这么说是不是好理解多了,现在,我们要做的就是赶走这个死胖子。

 

在探讨怎么处理内存泄漏的之前,我们先探讨一下为什么这些函数不能释放内存空间,内存泄漏我们要怎么取检测。

 

首先,我们需要有合适的工具来检测我们的内存使用情况

内存泄漏工具

推荐该博主写的利用 LeakCanary 来检查 Android 内存泄漏

 

现在,我们正式进入主题

我们来看看常见的内存泄漏的情况,及解决方案

因为静态变量造成的内存泄漏

 

单例模式应该是每个Android 开发者最熟悉的设计模式了,但是如果使用不当,在Android 应用中很可能会造成内存泄漏。举个例子


public class MyApplication {

private static MyApplication application;
private MyApplication(Context context){
}
public static MyApplication getApplication(Context context){
if(application==null){
application = new MyApplication(context);
}
return application;
}
}

我创建了一个MyApplication 类,在构造方法中,我传入一个ContextContext包括Application\Activity\Service,如果我传入的是Application,那么不会有什么影响,因为我所需要的时间是一整个程序的生命周期。

但是我如果传入的是Activity,那么,当我退出该Activity时,因为被static修饰,单例对象持有对该Activity的引用,导致该Activity不会被回收,从而产生内存泄漏。

解决方案:

在获取单例的方法中,把原有的context 改成application = new MyApplication(context.getApplicationContext());

把传入的Context的生命周期设置成与Application一样长,这样在使用单例的时候就和导入的Context类型无关,防止了内存泄漏。

Handler 的错误使用

Handler是开发中异步处理最常使用到的工具之一,但是如果错误地使用Handler也极容易产生内存泄漏。

举个栗子:

public class Act_test extends Activity{
private static final int WHAT = 1;
private Handler mHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case WHAT:
//handle it
break;
default:
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
Message message = Message.obtain();
message.what = WHAT;
mHandler.sendMessage(message);

}
}

怎么样,看起来是不是合情合理,但是其中隐藏着一个重要的隐患,当我们创建出一个Handler的时候,代码中的mHandlerHandler的非静态内部类的实例,所以mHandler持有对外部类,即Activity的引用。并且Handler中的Looper不断轮询消息队列中的messagemessage又持有mHandler的引用,但mHandler这玩意儿又持有Activity的引用,所以这下倒好,因为你一个message导致我整个Activity都无法被回收,你说气人不气人。

解决方案:

public class Act_test extends Activity{
private static final int WHAT = 1;
private MyHandler mHandler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
Message message = Message.obtain();
message.what = WHAT;
mHandler.sendMessage(message);

}
static class MyHandler extends Handler{
WeakReference<Context> reference;
public MyHandler(Context context) {
// TODO Auto-generated constructor stub
reference = new WeakReference<Context>(context);
}
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
if(reference!=null){
if(msg.what==WHAT){
//handle it
}
}
}
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}

解释:首先,创建一个Handler 的静态内部类,这样,它将不再持有外部类的引用,并且将持有它的Context 进行弱引用,确保Activity可以及时被回收。在前面分析过,Handler内存泄漏是因为消息队列中还有未处理的Message,所以当该Activity被销毁时,将消息队列中的Message清空即可。

非静态内部类的静态实例的错误使用

该错误与上例原因接近,先看错误代码:

public class Act_test extends Activity{

private static Test mTest;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(mTest==null){
mTest = new Test();
}
}
class Test{

}
}

这种情况多使用于反复使用同一个Activity 为了避免重复创建资源。

但是这种写法同样存在着隐患,因为在该Activity中,我创建了一个Test类的静态实例,每次启动Activity都会使用该单例。但是由于非静态内部类持有外部类的使用,并且该非静态内部类又创建了一个静态的实例,这导致了该单例的存活时间与application的生命周期一样长,于是会一直持有该Activity的引用,导致其无法被回收。

解决方案:static class Test{

}

Test 设置为静态内部类。

 

不正确使用线程,造成内存泄漏

由于创建线程时用到了Runnable 为匿名内部类,持有对外部类的引用,但是如果该外部类在销毁之前,线程中还有未完成的任务,这将导致该外部类无法被回收。

解决方案:对其进行弱引用

 

资源没有及时关闭

在开发中,例如CursorFileIOStream等资源在使用后要及时进行关闭,避免造成内存的浪费。

TypedArrayBitmap等要及时进行recycle

EventBus BroadcastReceiver注册后避免重复注册,使用后及时销毁。