Android 关于内存泄漏的检测与优化

在开发app中,盲目的去撸代码,从没为内存考虑的写,这样到了后面出问题的时候,就想哭了,最近上传小米平台的应用被反驳回来,真心的尴尬,因为在公司几台手机都没有出现oom的情况,到了小米官方测试,还是小米3(Android6.0)的就出现了.原因的oom,看这报错文档,懵逼了,一开始以为是bitmap引起的,拼命去查看bitmap处的代码,感觉已经优化了,没啥问题啊!后来使用了leakCanary去测试再知道,不是bitmap引起的,是这些小细节的处理不当.

(1)Handler

错误示范:

public class MainActivity extends AppCompatActivity {

    private Handler mHandler = new Handler() {

        @Override

        public void handleMessage(Message msg) {

            //...

        }

    };

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        loadData();

    }

    private void loadData(){

        //...request

        Message message = Message.obtain();

        mHandler.sendMessage(message);

    }

}

这样使用运行确实没什么问题,但开发app,内存慢慢泄漏积累多了,那就呵呵了.

原因:我们知道在activity中new Handler 该实例指向的是该Activity的引用,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏.


正确一做法:



@Override
    public void onDestroy() {
        super.onDestroy();
        Log.e("onDestroy", "onDestroy()");

//移除有关Activity的所有的消息和回调引用,就相当于切断了Handler和外部连接的线, Activity自然会在合适的时候被回收。
        handler.removeCallbacksAndMessages(null);

}

正确二做法:将Handler声明为静态类+使用弱引用

静态类不持有外部类的对象,所以Activity中的Handler不持有他的引用,该Activity就可以完全被回收掉.但是存在一个问题,在handler中如何拿到该Activity的上下文?需要在Handler中增加一个对Activity的弱引用(WeakReference):


WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了


static class MyHandler extends Handler {
    WeakReference<Activity > mActivityReference;

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

    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mActivityReference.get();
        if (activity != null) {
            mImageView.setImageBitmap(mBitmap);
        }
    }
}


(2)Webview

使用webview的使用,一般我们都是在xml文件直接使用,这时候,webview加载的时候指向的是Activity的引用,同理activity销毁时,但是webview还持有该Activity的引用,也会造成内存泄漏.所以我们需要避免使用Activity的上下文,Application的上下问是整个应用就长的,也相当于一个静态的,很好处理,使用它就可以了.

解决办法:动态添加webview

在xml中使用一个装载webview的容器,这样就不会有布局的麻烦了.


<FrameLayout
        android:id="@+id/webViewLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

动态添加:

 webviewLayout = (FrameLayout) onfindViewById(R.id.webViewLayout);

 //注意这里的使用getApplicationContext()
 webView=new WebView(getApplicationContext());
 ViewGroup.LayoutParams lp=new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,  ViewGroup.LayoutParams.MATCH_PARENT);
 webviewLayout.addView(webView,lp);


(3)线程

我们知道使用线程是非常消耗内存的.

new Thread(new Runnble).start(),每次都是用new,内心都觉得痛,避免我们就需要使用线程池, Executors.newFixedThreadPool(3);,开启最大线程数3条,执行后就复用,3条都在使用,其他消息就需要在队列等待.

这样大大减少内存消耗.


4(Activity.Context)

Activity的context是一个危险的对象,使用不好很容易造成泄漏.有一句话说得好不要"拖泥带水",要保证Activity销毁的时候,使用的该Activity的context引用全部也要无效.

单例的使用:很多时候使用单例都需要Context,但是因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。所以这里建议使用Application的上下文.

 数据库的上下文:很多时候当页面切换其他应用的时候,如果操作数据库对象使用的是Activity的上下文,这时候有可能被回收掉,那你再切换回app,你会发现会出现null指针,所以这里建议使用Application的上下文,与应用的存亡共在.

避免Context使用static修饰:使用static修饰也是最容易造成内存泄漏.

 Toast的上下文使用Activity的Context也会内存泄漏.


所以能尽量使用Application的context就使用它,像一些dialog之类的就要使用Activity的Contextl了.


(5)资源未关闭

BraodcastReceiver,service,File,Cursor,Stream,Bitmap等等使用后,该关闭就关闭,该取消就取消.该致null就null.

(6)第三放SDK

关于使用第三方SDK也会泄漏,对于这种问题,要反馈到该平台去处理了,要不就只能换了.

内存泄漏很多都会存在,问题是泄漏的大小,像一些handler,statice,可以打到几时兆的大小,这还会不闪退?



顺便介绍下LeakCanary.

这个测试内存是不错的,至少配置简单,有内存泄漏自动通过dialog通知,泄漏大小,类名,全部一清二楚,至少我用起来很方便.

配置bulid.gradle

 debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
   

在你地Application的oncreat()中加入

refWatcher = LeakCanary.install(this);


这样就可以满足你地有的Activity的context的泄漏检测了


如果需要对其他fragment,一些变量的检测:

public static RefWatcher getRefWatcher(Context context) {
        BTApplication application = (BTApplication) context.getApplicationContext();
        return application.refWatcher;
    }

并在在Activity中ondestry()调用

@Override
    protected void onDestroy() {
        super.onDestroy();
 RefWatcher refWatcher = BTApplication.getRefWatcher(this);
        refWatcher.watch(this);
    }



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章