1. 程式人生 > >Android LeakCanary效能優化工具

Android LeakCanary效能優化工具

效能優化工具(九)-LeakCanary

一、簡介

使用MAT來分析記憶體問題,有一些門檻,會有一些難度,並且效率也不是很高,對於一個記憶體洩漏問題,可能要進行多次排查和對比才能找到問題原因。 為了能夠簡單迅速的發現記憶體洩漏,Square公司基於MAT開源了LeakCanary

二、使用

在app 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 LeakApplication extends Application {
    @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {//1
      // This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process. return; } LeakCanary.install(this); } }

如果當前的程序是用來給LeakCanary 進行堆分析的則return,否則會執行LeakCanary的install方法。這樣我們就可以使用LeakCanary了,如果檢測到某個Activity 有記憶體洩露,LeakCanary 就會給出提示。

例子程式碼只能夠檢測Activity的記憶體洩漏,當然還存在其他類的記憶體洩漏,這時我們就需要使用RefWatcher來進行監控。改寫Application,如下所示:

public class MyApplication extends Application {
    private RefWatcher refWatcher;
    @Override
    public void onCreate() {
        super.onCreate();
        refWatcher= setupLeakCanary();
    }
<span class="hljs-function"><span class="hljs-keyword">private</span> RefWatcher <span class="hljs-title">setupLeakCanary</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">if</span> (LeakCanary.isInAnalyzerProcess(<span class="hljs-keyword">this</span>)) {
        <span class="hljs-keyword">return</span> RefWatcher.DISABLED;
    }
    <span class="hljs-keyword">return</span> LeakCanary.install(<span class="hljs-keyword">this</span>);
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> RefWatcher <span class="hljs-title">getRefWatcher</span><span class="hljs-params">(Context context)</span> </span>{
    MyApplication leakApplication = (MyApplication) context.getApplicationContext();
    <span class="hljs-keyword">return</span> leakApplication.refWatcher;
}

}

install方法會返回RefWatcher用來監控物件,LeakApplication中還要提供getRefWatcher靜態方法來返回全域性RefWatcher。

最後為了舉例,我們在一段存在記憶體洩漏的程式碼中引入LeakCanary監控,如下所示。

public class OtherActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LeakThread leakThread = new LeakThread();
        leakThread.start();
    }
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LeakThread</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Thread</span> </span>{
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">try</span> {
            CommonUtils.getInstance(OtherActivity.<span class="hljs-keyword">this</span>);
            Thread.sleep(<span class="hljs-number">6</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>);
        } <span class="hljs-keyword">catch</span> (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onDestroy</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">super</span>.onDestroy();
    RefWatcher refWatcher = MyApplication.getRefWatcher(<span class="hljs-keyword">this</span>);<span class="hljs-comment">//1</span>
    refWatcher.watch(<span class="hljs-keyword">this</span>);
}

}

MainActivity存在記憶體洩漏,原因就是非靜態內部類LeakThread持有外部類MainActivity的引用,LeakThread中做了耗時操作,導致MainActivity無法被釋放。

它用於自動監控Activity執行onDestroy方法之後是否發生記憶體洩露,當前此例onDestroy加是多餘的,這裡只是為了方便舉例,如果想要監控Fragment,在Fragment中新增如上的onDestroy方法是有用的。

執行程式,這時會在介面生成一個名為Leaks的應用圖示。接下來不斷的切換橫豎屏,這時會閃出一個提示框,提示內容為:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,記憶體洩漏資訊就會通過Notification展示出來。

提示的notification:

解決方法就是將LeakThread改為靜態內部類,再次執行程式LeakThread就不會給出記憶體洩漏的提示了。

使用小結
如果只關注activity的記憶體洩漏,那麼在Application中onCreate加入LeakCanary.install(this);就OK了,

如果還關注fragment的洩漏情況,那麼Application加上RefWatcher,然後在對應fragment頁面中onDestroy中加入:

RefWatcher refWatcher = MyApplication.getRefWatcher(this);
     refWatcher.watch(this);
三、使用踩坑

在剛開始使用LeakCanary的時候,遇到了幾個問題導致有記憶體洩漏發生時LeakCanary不發生通知,這裡和大家分享一下。

1、 你的應用需要有寫SD許可權,因為LeakCanary需要生成hprof檔案,儲存在SD卡里面,因此你的應用要先申請許可權

<!– SDCard中建立與刪除檔案許可權 –>
<uses-permission android:name=“android.permission.MOUNT_UNMOUNT_FILESYSTEMS”/>

<!– 向SDCard寫入資料許可權 –>
<uses-permission android:name=“android.permission.WRITE_EXTERNAL_STORAGE”/>

2、 java.lang.NullPointerException: Attempt to invoke virtual method’boolean java.lang.String.equals(java.lang.Object)’ on a null object reference

atcom.squareup.leakcanary.HeapAnalyzer.findLeakingReference(HeapAnalyzer.java:160)

….

如果遇到這個問題,是LeakCanary的版本過低了,不適合Android6.0及以上的機型,我看網上大部分引用的還是基於1.3的版本,升級到1.5的就沒問題了。

四、原理介紹

4.1 觸發檢測

每次當Activity/Fragment執行完onDestroy生命週期,LeakCanary就會獲取到這個Activity/Fragment,然後初始化RefWatcher對它進行分析,檢視是否存在記憶體洩漏。

4.2 判斷是否存在記憶體洩漏

首先嚐試著從ReferenceQueue佇列中獲取待分析物件(軟引用和弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用或弱引用所引用的物件被垃圾回收器回收,Java虛擬機器就會把這個軟引用或弱引用加入到與之關聯的引用佇列中),如果不為空,那麼說明正在被系統回收,如果直接就返回DONE,說明已經被系統回收了,如果沒有被系統回收,可能存在記憶體洩漏,手動觸發系統GC,然後再嘗試移除待分析物件,如果還存在,說明存在記憶體洩漏。

4.3 分析記憶體洩漏

確定有記憶體洩漏後,呼叫heapDumper.dumpHeap()生成.hprof檔案目錄。HAHA 是一個由 square 開源的 Android 堆分析庫,分析 hprof 檔案生成Snapshot物件。Snapshot用以查詢物件的最短引用鏈。找到最短引用鏈後,定位問題,排查程式碼將會事半功倍。

整體流程如下:

i

總結:

LeakCanary對於記憶體洩漏的檢測非常有效,但也並不是所有的記憶體洩漏都能檢測出來。

<1>無法檢測出Service中的記憶體洩漏問題

<2>如果最底層的MainActivity一直未走onDestroy生命週期(它在Activity棧的最底層),無法檢測出它的呼叫棧的記憶體洩漏。

所以說LeakCanary針對Activity/Fragment的記憶體洩漏檢測非常好用,但是對於以上檢測不到的情況,還得配合Android Monitor + MAT 來分析。