Android开发中常见内存泄漏问题

时间:2020-11-27 20:56:45

一、内存泄漏原因

当一个对象不再使用时,本该被回收,而另一个正在使用的对象持有它的引用导致不能被回收,就产生了内存泄漏。

二、内存泄漏的影响

Android系统为每个应用程序分配的内存有限,当应用中内存泄漏较多时,轻则造成可用空间不足,频繁发生gc,表现为应用运行卡顿;重则导致内存溢出应用crash。

三、常见内存泄漏及解决办法

3.1 单例造成内存泄漏

public class AppManager{
private static volatile AppManager instance = null;
private Context mContext;

private AppManager(Context context){
this. mContext = context;
}

public static AppManager getInstance(Context context){
if(null == instance){
synchronized(AppManager.class){
if(null == instance){
instance = new AppManager(context);
}
}
}
return instance;
}
}

ps:单例模式为何加双重检查?申明单例的静态引用为何加volatile关键字?

由于单例的生命周期和Application一样长,当Context所对应的Activity退出时,由于单例持有该Activity的引用,造成Activity退出时内存不能得到回收。
修正方法:
this. mContext = context;改为
this. mContext = context.getApplicationContext();
无论传入什么Context,最终都将使用Application的Context,防止了内存泄漏。

3.2 非静态内部类创建静态实例造成内存泄漏

public class OuterClassActivity extern Activity{
private static Inner mInner = null;

@Override
protected void onCreate (Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if(null == mInner){
mInner = new Inner();//创建静态实例
}
}

private class Inner{//非静态内部类
//TODO
}
}

由于非静态内部类会持有外部类的引用,当在内部类创建非静态内部类的静态实例后,导致该静态实例会持续持有外部类的应用,造成内存资源不能正常回收。
修正方法:将内部类设为静态内部类,静态内部类不会持有外部类实例的引用,当需要用到外部类的方法或属性时,使用外部类实例的弱引用。例如内部类可以这么写

private static class Inner{
private WeakReference<OuterClassActivity> mActivity;

public Inner(OuterClassActivity activity){
mActivity = new WeakReference<OuterClassActivity>( activity);
}
}

3.3 Handler造成的内存泄漏

public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {

@Override
public void handleMessage(Message msg) {
//TODO
}
};
}

由于mHandler是Handler的非静态匿名内部类的实例,它会持有外部类Activity的引用。消息队列是在一个Looper线程中不断轮询处理消息,如果当这个Activity退出时消息还未处理完毕,消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,导致Activity退出时无法释放内存,引发内存泄漏。
修正方法:创建静态的Handler内部类,然后对Handler持有的activity对象使用弱引用,这样可以避免Activity内存泄漏,不过Looper线程的消息队列还是可能会有待处理的消息,所以在Activity 的onDestory方法中应该移除消息队列中的消息。

public class MainActivity extends AppCompatActivity {
private Handler mHandler = new MyHandler (this);

private static class MyHandler extern Handler{
private WeakReference<Context> softRefContext;
public MyHandler(Context context) {
softRefContext = new WeakReference<Context>(context);
}

@Override
public void handleMessage(Message msg) {
//TODO
}
};


@Override
protected void onDestroy() {
if (mHandler != null){
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
super.onDestroy();
}
}

3.4 线程造成的内存泄漏

为避免阻塞主线程,在Activity中创建线程执行耗时操作是比较常见的,如

new Thread(new Runnable() {
@Override
public void run() {
Log.i(TAG, "线程创建成功,正在执行线程程序");
SystemClock.sleep(30*1000);
//TODO
}
}).start();

上面的Runnable是一个匿名内部类,因此它对当前Activity有一个隐式引用。如果Activity在销毁之前,任务还未完成,将导致Activity的内存资源无法回收,造成内存泄漏。
正确做法:使用静态内部类,

static class MyRunnable implements Runnable{
@Override
public void run() {
Log.i(TAG, "线程创建成功,正在执行线程程序");
SystemClock.sleep(30*1000);
}
}
//--------------------------------------------------------------------------
new Thread(new MyRunnable()).start();

在Activity销毁时应该取消相应的任务,避免在后台执行浪费资源。

3.5 资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

总结

非静态内部类、匿名内部类会隐式持有外部类对象,需要注意其生命周期,建议使用静态内部类配合弱引用访问外部类,避免一不小心造成内存泄漏。
并不是所有内部类只能使用静态内部类,当该类的生命周期不可控时,我们需要采用静态内部类。
内部类存在的意义:接口只能解决部分多重继承问题,而内部类可以使多重继承更加完善,当需要继承抽象类或者具体类时,只能使用内部类才能实现多重继承。

内存泄漏主要分为以下几种类型:
1.静态变量(包括但不限于单例)引起的内存泄漏。注意静态变量持有对象的生命周期。
2.非静态内部类引起的内存泄漏。静态内部类,弱引用访问。
3.匿名内部类引起的内存泄漏。静态内部类,弱引用访问。
4.资源未关闭引起的内存泄漏。退出前关闭资源。