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



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