内存泄露及LeakCanary的使用

基本概念

  • 内存泄露,即MemeryLeak,是平时Coding非常容易忽视的点..像我之前对泄露的认识和针对性都不强,写的代码马马虎虎就可以,毕竟Leak并不像OOM那样,小白都可以轻易发现且容忍度为0。对泄露的定义,参考Wiki:

内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

  • 应该说优秀的程序设计完全可以避免内存泄露。以下是我对Android中内存泄露的简单理解和常见情景分析。

    简单举例

  • 类描述如下

    class A{
        B b;
        void setB(B b){
            this.b = b
        }
    }
    
    class B{
    
    }
    
  • A可以通过setB方法,持有B的引用。这时B对象若要销毁,但因为A还保存着B的强引用,所以GC无法回收,内存(对象B)泄露。

    容易Leak的场景

  • Context
    • Android开发中许多场景都要用到Context,但是Context由于占用内存大/传递频繁,所以是造成内存泄露的大户。比如一后台Service需要拿到Context,传入当前Activity,但是由于Service的生命周期往往大于Activity,当退出该Activity时这部分内存无法回收,造成泄露。
  • 匿名内部类

    • 会默认持有外部类的引用,同Context类似,如果该内部类生命周期大于外部类,内部类还未销毁/释放资源时外部类试图销毁,就会造成外部类这部分内存泄露。
    • 最常见的就是Handler的使用。多数写法为:

      Handler mHandler = new Handler(){
          @Override
          public void handleMessage(Message msg) {
              super.handleMessage(msg);
          }
      };
      

      应改为内部持有weakreference

      protected MyHandler mHandler;
      
      public static class MyHandler extends Handler {
          WeakReference<BaseActivity> mWeakReference;
      
          public MyHandler(BaseActivity mAct) {
              this.mWeakReference = new WeakReference<>(mAct);
          }
      
          @Override
          public void handleMessage(Message msg) {
              BaseActivity act = mWeakReference.get();
              if (act != null) {
                  act.handleMessage(msg);//转调Activity方法
              }
          }
      
      }
      
  • 心得
    • 使用匿名内部类时要记住,内部类生命周期一定要小于外部类。
    • 使用Context时尽量考虑使用ApplicationContext,它的生命周期和整个进程所绑定。
    • 使用Weakreference持有引用,这样不会影响GC。

      使用LeakCanary

  • Square出品,见https://github.com/square/leakcanary,使用起来超级方便。只需要在Application中onCreate添加一句话:

    LeakCanary.install(this);
    

    不过这里只考虑了监听Activity,因为内存泄露影响最大的就是Activity的泄露,这块内存大而且泄露后会一定程度造成程序卡顿,除非退出该进程;如需单独监视一块内存,可以使用LeakCanary.install(this)返回的RefWatcher对象,调用其watch方法。

  • 一个简单的使用Demo,LeakActivity开线程跑任务,退出后会提示Leak,如图

    public class LeakActivity extends BaseActivity implements View.OnClickListener {
        @InjectView(R.id.btn_1)
        Button mBtn1;
        @InjectView(R.id.btn_2)
        Button mBtn2;
        @InjectView(R.id.btn_3)
        Button mBtn3;
        private BackgroundTask mBackgroundTask;
        private Thread mBackgroundThread;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            LeakCanary.install(this);
            setContentView(R.layout.activity_leak);
            ButterKnife.inject(this);
            mBtn1.setOnClickListener(this);
            mBtn2.setOnClickListener(this);
            mBtn3.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.btn_1:
                    //开启后台线程,持有该Activity
                    if (mBackgroundTask == null) {
                        mBackgroundTask = new BackgroundTask();
                        mBackgroundTask.setLeakObject(this);
                        mBackgroundThread = new Thread(mBackgroundTask);
                        mBackgroundThread.start();
                    }
                    break;
                case R.id.btn_2:
                    //开启后台线程,持有该Activity的弱引用
                    if (mBackgroundTask == null) {
                        mBackgroundTask = new BackgroundTask();
                        mBackgroundTask.setWeakObj(new WeakReference<Object>(this));
                        mBackgroundThread = new Thread(mBackgroundTask);
                        mBackgroundThread.start();
                    }
                    break;
                case R.id.btn_3:
                    //停止线程
                    mBackgroundTask.stop = true;
                    mBackgroundThread.interrupt();
                    break;
            }
    
        }
    }
    
    public class BackgroundTask implements Runnable {
        private WeakReference<Object> weakObj;
        private Object object;
        private int count;
        public boolean stop;
    
        public void setLeakObject(Object leakObject) {
            this.object = leakObject;
        }
    
        public void setWeakObj(WeakReference<Object> weakObj) {
            this.weakObj = weakObj;
        }
    
        @Override
        public void run() {
            try {
                while (!stop) {
                    Thread.sleep(1000);
                    count += 1;
                    L.d("background task count:" + count);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    }
    

Leak Show