LeakCanary直面项目中的内存泄露

来源:互联网 发布:蜂窝移动数据开启无效 编辑:程序博客网 时间:2024/06/07 09:30

转载请标明出处:http://blog.csdn.net/donkor_/article/details/54095110

前言
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;    }}
//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会出现的效果图。



Demo_CSDN 下载地址 : http://download.csdn.net/detail/donkor_/9729808

结尾:
希望这篇文章有帮助到您。欢迎关注我的微信公众号,扫一扫下方二维码,即可关注。有什么问题也可以直接留言,看到之后我会及时回复您。

12 0
原创粉丝点击