[置顶] Android开发——常见的内存泄漏以及解决方案(一)

时间:2023-01-21 20:55:57

0. 前言  

转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52333954

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

Android Studio里可以通过一些分析工具比如MAT来找出潜在的内存泄漏,在Android Device Monitor中进行Dump HPROF File,并对这个文件在SDK的platform-tools目录下进行<hprof-conv infilePath outfileName>即可在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

@Overrideprotected 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)使用全局的线程池代替在类中创建子线程。


4Handler导致内存泄漏

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

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

private final Handler handler = new Handler() {    @Override    public void handleMessage(Message msg) {    }};@Overrideprotected 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里面还是外面


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

[置顶]        Android开发——常见的内存泄漏以及解决方案(一)