LeakCanary直面项目中的内存泄露

LeakCanary一个直白的展示Android中内存泄露的工具。它是Square公司开源出来的内存泄露自动探测神器,能够在程序发生内存泄漏的时候在通知栏提示通知,而且学习成本巨低。通过学习本文,了解和如何使用LeakCanary工具,同时了解和解决实际开发中出现的经常遇到的内存泄露案例。

更多详细介绍请参见Github地址:https://github.com/square/leakcanary

好了,在学习如何使用LeakCanary之前,我们先对内存泄露与内存溢出做出概念性的理解。原因是大部分人对这两个的区别总是朦朦胧胧分不清楚。 
▲概念要点(什么是内存泄露,内存溢出)

  • 内存泄露(Memory Leak)指你用malloc或new申请了一块内存,但是没有通过free或delete将内存释放,导致这块内存一直处于占用状态内存泄露和硬件没有关系,它是由软件设计缺陷引起的。
  • 内存溢出(Memory Overflow)指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。比如你申请了10个字节的空间,但是你在这个空间写入11或以上字节的数据,就是溢出

▲内存泄露、溢出的异同(两者之间的区别) 
相同点:都会导致应用程序运行出现问题,性能下降或挂起。

不同点: 
1) 内存泄露是导致内存溢出的原因之一;内存泄露积累起来将导致内存溢出。 
2)内存泄露可以通过完善代码来避免;内存溢出可以通过调整配置来减少发生频率,但无法彻底避免。

android中会造成内存泄露的情景无外乎两种

  • 全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用的怪物。
  • 活在Activity生命周期之外的线程。没有清空对Activity的强引用。

了解了内存溢出与内存泄露之后,我们接下来看看如何使用LeakCanary工具减少我们项目中的内存泄露的问题。 
▲Android Studio集成LeakCanary 
在app的build文件加上:

dependencies {
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
    testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

新建MyApplication 中初始化,同时别忘了在AndroidManifest中配置Application标签中的name

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        enabledStrictMode();
        LeakCanary.install(this);
    }

    private void enabledStrictMode() {
        if (SDK_INT >= GINGERBREAD) {
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() //
                    .detectAll() //
                    .penaltyLog() //
                    .penaltyDeath() //
                    .build());
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

LeakCanary集成完成。接下来通过介绍三个经常会在项目中写错的内存泄露实例(很大众的实例!!!),介绍和使用如何LeakCanary。同时给出解决方法!!! 没错,不是五个,不是六个,只有三个。三个不多,但相比百度上搜索常见的内存泄露,写出了5个同时给出文!字!描!述!的解决方法,却不给demo,那才是在耍流氓。但在给出三种解决方法之前,常见的内存泄露情况,我们还是有必要过目一下。

▲常见的内存泄露

  • 持有Context引用造成的泄漏
  • 线程之间通过Handler通信引起的内存泄漏
  • 将变量的作用域设置为最小
  • 构造Adapter时,没有使用缓存的convertView
  • 资源对象没关闭造成的内存泄露(Cursor、IO 流)
  • 各种注册没取消
  • 集合容器对象没清理造成的内存泄露
  • static关键字的滥用
  • WebView对象没有销毁
  • GridView的滥用
  • Handler的使用
  • 线程的使用
  • Bitmap的回收和置空(对象内存过大) 
    (如有纰漏,还望指正)

▲接下来是很大众的内存泄露实例与解决方法 
1 . 单例模式造成的内存泄露

//X错误的示范
public class InsUtil {
    private static InsUtil instance;
    private Context mContext;

    private InsUtil(Context context) {
        this.mContext = context;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
//X错误的示范
public class InsUtil {
    private static InsUtil instance;
    private Context mContext;

    private InsUtil(Context context) {
        this.mContext = context;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

相信很多人看到上述单例的代码,都会感到内心有一股泥石流,是的,没错,因为自己也是这么写的。而上述造成内存泄露的原因是传入Activity的Context,当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。Context的引用超过了本身的生命周期,所以不会被回收。正确的写法是使用Application的Context,使得这个Context的生命周期跟Application一样长

//√正确的示范
public class InsUtil {
    private static InsUtil instance;
    private Context mContext;

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

    public static InsUtil getInsUtil(Context context) {
        if (instance == null) {
            instance = new InsUtil(context);
        }
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

2 . handler造成的内存泄露

//X错误的示范
public class HandlerActivity extends Activity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // do something you want
        }
    };

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

        mHandler.sendMessageDelayed(Message.obtain(), 10000);
        //just finish this activity
        finish();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

从上述的HandlerActivity 可以看出,在finish()的时候,该Message还没有被处理,Message持有Handler, Handler持有Activity,这样阻止了GC对Acivity的回收,就发生了内存泄露。正确的写法应该是使用显形的引用,静态内部类与 外部类。使用弱引用WeakReference。 最后在Activity调用onDestroy()的时候要取消掉该Handler对象的Message和Runnable

//√正确的示范
public class HandlerActivity extends Activity {
   private static class MyHandler extends Handler {
        private final  WeakReference<HandlerActivity> mActivityReference;

        public MyHandler(HandlerActivity activity) {
            mActivityReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            HandlerActivity handlerAct = mActivityReference.get();
            if (handlerAct == null) {
                return;
            }
            // Do something  you want
        }
    }

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

        mHandler=new MyHandler(HandlerActivity.this);
        //just finish this activity
        finish();
    }

    @Override
    protected void onDestroy() {
        //  Remove all Runnable and Message.
        mHandler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

3 . Thread造成的内存泄露

//X错误的示范
public class ThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread);
        //两种常见线程写法造成的内存泄露
        new MyAsyncTask().execute();

        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(10000);
            }
        }).start();
    }

    private class MyAsyncTask extends AsyncTask<Void,Void,Void>{

        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(10000);
            return null;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

从上述ThreadActivity可以看出 以上的异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。 如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。 正确的做法还是使用静态内部类的方式 最后在Activity销毁的时候,相对应的取消异步任务

//√正确的示范
public class ThreadActivity extends Activity {

    private MyAsyncTask myAsyncTask;

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

        myAsyncTask = new MyAsyncTask(ThreadActivity.this);
        myAsyncTask.execute();
        new Thread(new MyRunnable()).start();
    }

    private static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        private WeakReference<Context> weakReference;

        public MyAsyncTask(Context context) {
            weakReference = new WeakReference<>(context);
        }

        //doInBackground方法内部执行后台任务,不可在此方法内修改UI
        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(10000);
            return null;
        }

        //onPostExecute方法用于在执行完后台任务后更新UI,显示结果
        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            MainActivity activity = (MainActivity) weakReference.get();
            if (activity != null) {
                //...
            }
        }

        //onCancelled方法用于在取消执行中的任务时更改UI
        @Override
        protected void onCancelled() {
            super.onCancelled();
        }
    }

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            SystemClock.sleep(10000);
        }
    }

    @Override
    protected void onDestroy() {
        //判断异步任务是否存在
        if (myAsyncTask != null && myAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
            myAsyncTask.cancel(true);
        }
        super.onDestroy();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

最后下面祭出本案例使用LeakCanary会出现的效果图。 

猜你喜欢

转载自blog.csdn.net/wzy901213/article/details/77197039