Android 常见的内存泄漏以及解决方案(一)

转载来源: https://blog.csdn.net/seu_calvin/article/details/52333954

0. 前言

Android的内存泄漏是Android开发领域永恒的话题,那今天就总结一下常见的内存泄漏吧。

在Android Studio里可以通过一些分析工具比如MAT来找出潜在的内存泄漏,在Android Device Monitor中进行Dump HPROF File,并对这个文件在SDK的platform-tools目录下进行即可在platform-tools目录下生成使用MAT工具查看的hprof文件。

如果不想这么麻烦,可以使用LeakCanary进行自动化的OOM检测。使用起来非常简单,具体可以查看LeakCanary中文使用说明

//LeakCanary原理总结
//1. 使用ActivityLifecycleCallbacks监控所有Activity的onDestory(),将该Activity添加到内存泄漏监控队列中
//2. 在后台线程检查引用是否被清除,如果没有,调用GC
 //3. 如果引用还是未被清除,把heap内存dump到一个 .hprof 文件中
//4. 使用一个独立进程中的服务计算出到GC Roots的最短强引用路径来判断是否发生Leak
//5. 建立导致泄漏的引用链,结果在Log中打印出来

此篇将从静态变量引用Activity、匿名类、内部类、Handler、以及监听器等方面的实例说明内存泄漏的发生原因以及解决方案。

1. 单例模式导致内存泄漏(实质是静态变量引用Activity)

如果不了解单例模式的小伙伴可以查看我之前写过的设计模式——单例模式解析。已经对单例模式分析的很清楚了。这里就不多赘述了。

public class SingleUtils {
    private static SingleUtils mInstance = null;
    private Context context;
    private SingleUtils (Context context){
        this.context = context;
    }

    public static SingleUtils getInstance(Context context){
        if(mInstance == null){
            mInstance = new SingleUtils (context);
        }
        return mInstance;
    }

    public Object getObject(){//根据业务逻辑传入参数
        //返回业务逻辑结果,这里需要用到context
    }
}

单例由于它的静态特性使得其生命周期跟应用一样长,如果我们把上下文context(比如说一个Activity)传入到了单例类中的执行业务逻辑,这时候静态变量就引用了我们的Activity,如果没有及时置空,就会在这个Activity finish的时候,导致该Activty一直驻留在内存中,并发生内存泄漏。

解决方案:

在单例中我们尽可能的引用生命周期较长的对象,将第10行代码修改如下即可。

mInstance = new SingleUtils (context.getApplicationContext());

2. 内部类导致内存泄漏

如果我们在一个外部类中定义一个静态变量,这个静态变量是非静态内部类对象,这就会导致内存泄漏,因为非静态内部类会持有外部类的引用,从而间接导致静态地引用了外部类

public class MyActivity extends Activity {
    //非静态内部类InnerClass创建的静态实例mInnerClass
    private static InnerClass mInnerClass = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mInnerClass = new InnerClass();
    }
    class InnerClass{
    }
}

解决方案:

(1)在onDestroy方法中手动将mInnerClass置为null。

(2)将内部类定义为静态内部类,使其不能与外部类建立关系。

3.匿名内部类导致内存泄漏

匿名内部类同样会持有一个外部类的引用,比如说在Activity的onCreate()方法中定义了一个匿名的AsyncTask对象。如果Activity被销毁之后AsyncTask仍然在执行,那就会阻止垃圾回收器回收Activity对象,进而导致内存泄漏,直到执行结束才能回收Activity

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

    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            //子线程中持有Activity的引用
            //子线程在Activity销毁后依然会继续执行,导致该Activity内存泄漏
            while (true) ;
        }
    }.execute();
}

解决方案:

(1)在onDestroy中中断子线程的运行。

(2)使用全局的线程池代替在类中创建子线程。

4.Handler导致内存泄漏

如下所示我们在Activity中定义了一个handler,然后在Activity的onCreate()方法中,发送了一条延迟消息。

当Activity finish的时候,延时消息还保存在主线程的消息队列里。而且,这条消息持有对handler的引用,而handler又持有对Activity引用。这条引用关系会保持到消息被处理,从而,这就阻止了Activity被垃圾回收器回收,造成内存泄漏。

private final Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {

    }
};

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

    handler.postDelayed(new Runnable() {
        @Override
        public void run() { /* ... */ }
    }, Integer.MAX_VALUE); 
}

解决方案:

如果一个内部类实例的生命周期比Activity更长,那么我们就不要使用非静态的内部类。最好的做法是使用静态内部类,然后在该类里使用弱引用来指向所在的Activity。

public class SampleActivity extends Activity {

    private static class MyHandler extends Handler {
        private final WeakReference<SampleActivity> mActivity;

        public MyHandler(SampleActivity activity) {
            mActivity = new WeakReference<SampleActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            SampleActivity activity = mActivity.get();
            if (activity != null) {
                // ...
            }
        }
    }

    private final MyHandler mHandler = new MyHandler(this);

    private static final Runnable sRunnable = new Runnable() {
        @Override
            public void run() { /* ... */ }
        };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
        finish();
    }
}

5.监听器导致内存泄漏

很多系统服务负责执行某些后台任务。如果context对象想要在服务事件发生时被通知,就需要把自己注册到服务的监听器中。这就会让服务持有Activity的引用,如果忘记在Activity销毁时取消注册,那就会导致Activity泄漏了。

void registerListener() {
   SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
   Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
   sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}

View mButton = findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        registerListener();
        nextActivity();
    }
});

6. 使用IntentService

如果Service停止失败也会导致内存泄漏

因为系统会倾向于把这个Service所依赖的进程进行保留,如果这个进程很耗内存,就会造成内存泄漏。

解决方案:

所以推荐使用IntentService,它会在后台任务执行结束后自动停止,从而避免了Service停止失败导致发生内存泄漏的可能性。

7. Adapter中引用了Activity如何避免内存泄漏

有时需要点击ListView条目里的某个按钮实现界面跳转,getView()方法inflate布局的时候需要上下文,而且点击按钮后的跳转逻辑也需要上下文。所以我们经常会把Activity传入到Adapter中,如果Adapter中有很多耗时操作,可能就会防止Activity finish的时候被回收。

解决方案:

inflate布局是可以使用getView()方法里的parent参数的,通过parent.getActivity()获得上下文。

跳转逻辑的话,可以使用接口回调的方式,在Activity中实现这个接口并且覆写里面的方法,跳转逻辑就在这个回调方法里进行。

具体可以查看Android开发——告诉你Adapter应该写在Activity里面还是外面

下一篇将会从资源角度来说明内存泄漏的问题

猜你喜欢

转载自blog.csdn.net/chennai1101/article/details/87274639