Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 深入理解LeakCanary的內存洩露檢測機制(中)

Android 深入理解LeakCanary的內存洩露檢測機制(中)

編輯:關於Android編程

上篇文章主要介紹了Java內存分配相關的知識以及在Android開發中可能遇見的各種內存洩露情況並給出了相對應的解決方案,如果你還沒有看過上篇文章,建議點擊這裡閱讀一下,這篇文章我將要向大家介紹如何在我們的應用中使用square開源的LeakCanary庫來檢測應用中出現的內存洩露,如果你已經對LeakCanary的使用非常熟悉了請跳過本文(*^__^*) ……

以介紹來自英文LeakCanary: Detect all memory leaks!的翻譯,原文在這裡。

java.lang.OutOfMemoryError
        at android.graphics.Bitmap.nativeCreate(Bitmap.java:-2)
        at android.graphics.Bitmap.createBitmap(Bitmap.java:689)
        at com.squareup.ui.SignView.createSignatureBitmap(SignView.java:121)

沒人喜歡OutOfMemoryError

在Square Register中,在簽名頁面我們把客戶的簽名畫在bitmap cache上,這個Bitmap的尺寸幾乎和屏幕的尺寸一樣大,在創建這個Bitmap對象時,經常會引發OutOfMemoryError,簡稱OOM。
\

當時,我們嘗試過一些解決方案,但是都沒解決問題:

使用Bitmap.Config.ALPHA_8,因為簽名僅有黑色。捕捉OutOfMemoryError,嘗試GC並重試(受GCUtils啟發)。我們沒想過在Java Heap內存之外創建Bitmap,苦逼的我們,那會Fresco等庫還不存在。

路子走錯了

其實Bitmap的尺寸不是真正的問題,當內存吃緊的時候,到處都有可能引發OOM,在創建大對象,比如Bitmap的時候,則更有可能引發OOM,OOM只是一個表象,更深層次的問題可能是:內存洩露。

什麼是內存洩露

一些對象有著有限的聲明周期,當這些對象所要做的事情完成了,我們希望它們會被垃圾回收器回收掉。但是如果有一系列對這個對象的引用存在,那麼在我們期待這個對象生命周期結束時被垃圾回收器回收的時候,它是不會被回收的。它還會占用內存,這就造成了內存洩露。持續累加,內存很快被耗盡。

比如:當Activity的onDestroy()方法被調用後,Activity以及它涉及到的View和相關的Bitmap都應該被回收掉。但是,如果有一個後台線程持有這個Activity的引用,那麼該Activity所占用的內存就不能被回收,這最終將會導致內存耗盡引發OOM而讓應用crash掉。

對戰內存洩露

排查內存洩露是一個全手工的過程,這在Raizlabs的Wrang Dalvik系列文章中有詳細描述。以下幾個關鍵步驟:

通過Bugsnag,Crashlytics或者Developer Console等統計平台,了解OutOfMemoryError情況。重現問題。為了重現問題,機型非常重要,因為一些問題只在特定的設備上出現。為了找到特定的機型,你需要想盡一切辦法,你可能需要去買,去借,甚至去偷。當然,為了確定復現步驟,你需要一遍一遍地去嘗試。一切都是非常原始和粗暴的。在發生內存洩露的時候,把內存Dump出來。具體看這裡。然後,你需要在MAT或者YourKit之類的內存分析工具中反復查看,找到那些原本該被回收掉的對象。計算這個對象到GC Roots的最短強引用。確定應用路徑中的哪個應用是不該有的,然後修復。

很復雜吧?如果有一個類庫能在發生OOM之前把這些事情全部搞定,然後你只要修復這些問題就好了,豈不美哉!!!這就是LeakCanary的由來,接下來我們就要介紹一下LeakCanary的使用。

由於Google已經不再對Eclipse上開發Android做支持,所以就不再Eclipse上演示如何使用LeakCanary了,LeakCanary的官方網址為:https://github.com/square/leakcanary,根據指導文檔,使用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'
}
在build.gradle中添加了LeakCanary的依賴後,點擊\按鈕同步一下工程後就可以使用LeakCanary了。細心的小伙伴可能會注意到我們引入了三種模式的依賴,debugCompile表示在debug打包模式下引入的依賴庫,releaseCompile表示在release打包模式下引入的依賴庫,testCompile表示的是在test打包模式引入的依賴庫。總的來說就是在不同模式下使用不同的庫,在項目打包的時候不會把其他模式的庫打包進去。它們之間的區別稍後會有講解。

引入了LeakCanary的依賴庫後,接下來就是使用它了,根據GitHub上的指導文檔,首先在我們的Application中初始化LeakCanary,如下所示:

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);
    }
}
在初始化之前先是調用LeakCanary的靜態方法isInAnalyserProcess()做過濾,如果該方法返回true就直接返回否則就執行LeakCanary的install()方法進行初始化工作。這樣就完成了LeakCanary的初始化操作,是不是很簡單?接下來我們測試一個例子,看看LeakCanary是如何檢測內存洩露的,在上篇文章Android 源碼系列之<十二>從源碼的角度深入理解LeakCanary的內存洩露檢測機制(上)中講解了Android開發中常見的內存洩露情形(如果你還沒有看過請點擊這裡)。我們根據上篇文章任意舉一例子:從當前MainActivity頁面跳轉到LeakActivity頁面,在LeakActivity頁面中模擬長耗時的任務,然後點擊返回鍵返回到MainActivity頁面,現在編寫LeakActivity,代碼如下:
public class LeakActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.leak_activity);
    }

    public void start(View view) {
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                        Log.e(getPackageName(), "LeakCanary ----->>>>> " + System.currentTimeMillis());
                    } catch (Exception e) {
                    }
                }
            }
        }.start();
    }
}
在LeakActivity中,當點擊了按鈕後就會啟動一個新的匿名內部類線程並在線程中模擬了長耗時的操作,根據上篇文章的講解,匿名內部類會默認持有當前MainActivity的引用,當點擊返回按鈕後LeakActivity本應該由系統進行回收,但是內部啟動的線程還在繼續執行操作就造成了LeakActivity所占用的內存資源得不到釋放,就會造成內存洩露。然後運行一下程序,看看效果:

\
根據運行效果來看,在LeakActivity頁面在點擊按鈕啟動了線程後返回到主頁面後,大約5秒鐘後會彈出一個提示框,提示說在進行內存洩露檢測,稍後你會發現在狀態欄上彈出了一個Notification,我們展開這個Notification看看裡邊是啥東東:

\
彈出的Notification大致說了包名為com.example.leakcanary下的LeakActivity發生了內存洩露,洩露的內存大約是108KB,如果想要查看更為詳細的內存洩露信息,可以點擊查看,然後我們點擊進去看看詳細的內存洩露信息,如下所示:

\

內存洩露引用鏈清晰詳細的列舉了發生內存洩露的原因,根據這種引用關系我們可以很容易的定位到內存洩露點,然後修復這些洩露問題。怎麼樣?是不是很簡單?從此可以對身邊的小伙伴說:用了LeakCanary以後再也不怕內存洩露了(*^__^*) ……檢測到LeakActivity發生了內存洩露,根據上篇文章的修復方法修改一下LeakActivity之後再運行程序就不會有Notification彈出了。

LeakCanary的使用就是這麼簡單,如果你還沒使用過它強烈建議使用一次,相信你用過之後就會離不開它的(*^__^*) ……在我們引入LeakCanary的時候我們引入了三種不同模式的庫,他們之間肯定是有區別的,下載完它的源碼後,其結構圖如下所示:

\

通過閱讀LeakCanary的代碼發現leakcanary-android是真正內存洩露分析庫,而leakcanary-android-no-op只是一個空殼子,裡邊啥也沒做。也就是說在打release包的時候LeakCanary庫根本就不會打進去,所以不不用擔心引入額外的方法,只有在debug或者test模式下LeakCanary庫才會打包進APK。

首先看一下leakcanary-android下的AndroidManifest.xml文件,如下所示:

 



    
    
    

    
        
        

        
            
                
                
            
        
        
    
在配置文件中首先申請讀寫權限是為了存儲堆內存信息到文件中便於在後台分析是否發生了內存洩露。接著是聲明了兩個Service,他們分別是HeapAnalyzerService和DisplayLeakService,HeapAnalyzerService添加了android:process=":leakcanary"屬性,那也就是說HeapAnalyzerService是運行在單獨的進程中的,這樣做的目的是不影響APP進程(比如給APP進程造成卡頓等影響),因此在初始化LeakCanary的時候首先是調用了LeakCanary的靜態方法isInAnalyzerProcess()方法判斷當前進程是否是分析進程,如果是分析進程就不需要在分析進程中做APP進程需要做的初始化操作,所以就直接返回。最後聲明了兩個Activity,DisplayLeakActivity主要是以列表的形式展示出做有發生過的內存洩露點,點擊列表中的每一條信息時會進入內存洩露的詳情頁面,RequestStoragePermissionActivity根據名字就知道它是用來申請存儲權限的,需要注意的是DisplayLeakActivity和RequestStoragePermissionActivity都聲明了android:taskAffinity="com.squareup.leakcanary"屬性,這既是說它們是運行在新的TaskStack中的,如果你不熟悉taskAffinity屬性,請看我之前寫的一篇文章:Android 源碼系列之<九>從源碼的角度深入理解Activity的launchModel特性。

 

了解了LeakCanary的相關配置後我們再看一下它的相關資源文件:

\

資源文件就不詳細說明了,對於這些資源我們能做的就是如果不喜歡這些資源文件,我們可以在項目中把它替換掉。

由於篇幅原因,這裡就不再過多的分析LeakCanary的源碼了,在下篇文章中我將帶領小伙伴們從源碼的角度出發深入分析一下LeakCanary的內存洩露分析機制,敬請期待!!!最後感謝收看(*^__^*) ……

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved