LeakCanary直面项目中的内存泄露

作者: Donkor | 来源:发表于2017-01-18 11:23 被阅读145次

    前言
    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'
    }
    

    新建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());
            }
        }
    }
    

    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;
        }
    }
    

    相信很多人看到上述单例的代码,都会感到内心有一股泥石流,是的,没错,因为自己也是这么写的。而上述造成内存泄露的原因是传入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;
        }
    }
    

    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();
        }
    }
    

    从上述的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();
        }
    }
    

    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;
            }
        }
    }
    

    从上述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();
        }
    }
    

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


    Github下载地址:https://github.com/ChenYXin/LeakCanary_Demo

    关于我

    相关文章

      网友评论

      本文标题:LeakCanary直面项目中的内存泄露

      本文链接:https://www.haomeiwen.com/subject/kgwvbttx.html