08. Android內(nèi)存泄露檢測(cè)工具Leakcanary

前言:學(xué)習(xí)筆記,如有不對(duì),多指教

參考資料:
https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/
https://www.liaohuqiu.net/cn/posts/leak-canary/
http://www.cnblogs.com/whoislcj/p/6001422.html

內(nèi)存泄露和內(nèi)存溢出的區(qū)別:
  • 內(nèi)存泄露:
    對(duì)象擁有有限的生命周期,當(dāng)這個(gè)對(duì)象做完了他們應(yīng)該做的事,我們希望它被回收掉,從而釋放它占用的內(nèi)存。但是,如果有一系列對(duì)這個(gè)對(duì)象的引用,這個(gè)對(duì)象就不會(huì)被回收,它所占用的內(nèi)存就不可用。這就是內(nèi)存泄露,內(nèi)存泄露和硬件沒有關(guān)系,它是軟件的設(shè)計(jì)缺陷。內(nèi)存泄露如果不及時(shí)解決最終會(huì)導(dǎo)致內(nèi)存溢出。
  • 內(nèi)存溢出:
    每一個(gè)應(yīng)用程序在啟動(dòng)時(shí)都需要申請(qǐng)一塊內(nèi)存,比如系統(tǒng)分配給你一塊2G的內(nèi)存,但是你非要用這2G內(nèi)存裝3G的數(shù)據(jù),這時(shí)就會(huì)導(dǎo)致內(nèi)存溢出。
LeakCanary的應(yīng)用

github:https://github.com/square/leakcanary

在你的build.gradle:

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

在你的Application類中:

public class ExampleApplication 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;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

這樣就ok了,在我們的debug包中,如果有內(nèi)存泄露,leakcanary就會(huì)自動(dòng)發(fā)送通知。

出現(xiàn)內(nèi)存泄露的場(chǎng)景

場(chǎng)景1:靜態(tài)變量導(dǎo)致的內(nèi)存泄露

public class MainActivity extends AppCompatActivity {

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

        sContext = this;
    }
}

當(dāng)我們destroy ManiActivity時(shí)候,這時(shí)候mainactivity本應(yīng)該被gc回收釋放內(nèi)存,但是由于靜態(tài)變量仍然持有對(duì)它的引用,所以它不能被回收,導(dǎo)致內(nèi)存泄露。

這里要說下靜態(tài)變量的生命周期問題,靜態(tài)變量什么時(shí)候銷毀呢?
參考文檔:http://blog.csdn.net/ctcwri/article/details/8858414
靜態(tài)變量的生命周期:類的加載——類的卸載

類在什么時(shí)候被加載?

當(dāng)我們啟動(dòng)一個(gè)app的時(shí)候,系統(tǒng)會(huì)創(chuàng)建一個(gè)進(jìn)程,此進(jìn)程會(huì)加載一個(gè)Dalvik VM的實(shí)例,然后代碼就運(yùn)行在DVM之上,類的加載和卸載,垃圾回收等事情都由DVM負(fù)責(zé)。也就是說在進(jìn)程啟動(dòng)的時(shí)候,類被加載,靜態(tài)變量被分配內(nèi)存。靜態(tài)變量在類被卸載的時(shí)候銷毀。

類在什么時(shí)候被卸載? 在進(jìn)程結(jié)束的時(shí)候。

說明:一般情況下,所有的類都是默認(rèn)的ClassLoader加載的,只要ClassLoader存在,類就不會(huì)被卸載,而默認(rèn)的ClassLoader生命周期是與進(jìn)程一致的,本文討論一般情況。

場(chǎng)景2:錯(cuò)誤使用單例造成內(nèi)存泄露
由于單例的靜態(tài)特性使得其生命周期跟應(yīng)用的生命周期一樣長(zhǎng),所以如果使用不恰當(dāng)?shù)脑挘苋菀自斐蓛?nèi)存泄漏。比如下面一個(gè)典型的例子:(由于單例中g(shù)etInstance的方法是靜態(tài)的,所以成員變量mInstance必須是靜態(tài)的,這樣這個(gè)變量的生命周期就和整個(gè)應(yīng)用的生命周期相同)

public class LoginManager {
    private static LoginManager mInstance;
    private Context mContext;

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

    public static LoginManager getInstance() {
        if (mInstance == null) {
            synchronized (LoginManager.class) {
                if (mInstance == null) {
                    mInstance = new LoginManager(mContext);
                }
            }
        }
        return mInstance;
    }

    public void dealData() {
    }
 
}

當(dāng)我們?cè)谠谝粋€(gè)Activity中調(diào)用,再關(guān)閉這個(gè)Activity時(shí)就會(huì)出現(xiàn)內(nèi)存泄露。

LoginManager.getInstance(this).dealData();

這是一個(gè)普通的單例模式,當(dāng)創(chuàng)建這個(gè)單例的時(shí)候,由于需要傳入一個(gè)Context,所以這個(gè)Context的生命周期的長(zhǎng)短至關(guān)重要:

  • 如果此時(shí)傳入的是 Application 的 Context,因?yàn)?Application 的生命周期就是整個(gè)應(yīng)用的生命周期,所以這將沒有任何問題。
  • 如果此時(shí)傳入的是 Activity 的 Context,當(dāng)這個(gè) Context 所對(duì)應(yīng)的 Activity 退出時(shí),由于該 Context 的引用被單例對(duì)象所持有,其生命周期等于整個(gè)應(yīng)用程序的生命周期,所以當(dāng)前 Activity 退出時(shí)它的內(nèi)存并不會(huì)被回收,這就造成泄漏了。

正確的寫法有兩種:

public class LoginManager {
    private static LoginManager mInstance;
    private Context mContext;

    private LoginManager(Context context) {
        this.mContext = context.getApplicationContext();   // 使用Application 的context
    }

    public static LoginManager getInstance() {
        if (mInstance == null) {
            synchronized (LoginManager.class) {
                if (mInstance == null) {
                    mInstance = new LoginManager(mContext);
                }
            }
        }
        return mInstance;
    }

    public void dealData() {
    }
 
}

或者,在你的 Application 中添加一個(gè)靜態(tài)方法,getContext() 返回 Application 的 context

//在application的oncreate方法中
context = getApplicationContext();
//提供公共的方法
public static Context getContext(){
     return context;
}


public class LoginManager {
    private static LoginManager mInstance;
    private Context mContext;

    private LoginManager() {
         this.mContext = MyApplication.getContext();
    }

    public static LoginManager getInstance() { 
        if (mInstance == null) {
            synchronized (LoginManager.class) {
                if (mInstance == null) {
                    mInstance = new LoginManager();
                }
            }
        }
        return mInstance;
    }

    public void dealData() {
    }
 
}

場(chǎng)景3:屬性動(dòng)畫導(dǎo)致的內(nèi)存泄露
從Android3.0開始,google提供了屬性動(dòng)畫,屬性動(dòng)畫中有一類無限循環(huán)的動(dòng)畫,如果在Activity中播放此類動(dòng)畫,且沒有在onDestroy中取停止動(dòng)畫,那么動(dòng)畫會(huì)一直播放下去。盡管已經(jīng)無法在界面上看到動(dòng)畫效果了,但此時(shí)Activity的view被動(dòng)畫持有,而View又持有Acitity,最終Activity無法釋放。導(dǎo)致內(nèi)存泄露,解決的方法是在Activity的onDestroy中調(diào)用animator.cancle()來停止動(dòng)畫。

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

        mButton = (Button)findViewById(R.id.button);
        ObjectAnimator animator = ObjectAnimator.ofFloat(mButton,"rotation",0,360).setDuration(2000);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.start();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        animator.cancle();
    }

結(jié)束……

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容