Android內(nèi)存泄漏檢測(cè)工具LeakCanary上手指南

LeakCanary官方Demo介紹

  • 首先可以去github上面downlaod它的官方開(kāi)源Demo
  • 用Android Studio打開(kāi)leakcanary項(xiàng)目后,在leakcanary-sample ->MainAvtivity目錄下可以看到內(nèi)存泄漏示例


  • 注釋說(shuō)明了此例子內(nèi)存泄漏的原因:此AsyncTask是一個(gè)匿名的內(nèi)部類,隱式的持有外部類(MainActivity)的引用,當(dāng)activity被銷毀的時(shí)候,如果AsyncTask(代碼sleep 20秒,模擬了一個(gè)耗時(shí)操作)沒(méi)有執(zhí)行完成,則MainActivity將會(huì)泄漏。
  • 運(yùn)行Demo,點(diǎn)擊啟動(dòng)異步線程的Button,在20秒內(nèi)我們點(diǎn)手機(jī)返回鍵(就是關(guān)閉MainActivity的意思,這會(huì)讓GC回收MainActivity)。
    -大概過(guò)了10秒左右,將會(huì)在通知欄看到一個(gè)內(nèi)存泄漏的提示:
  • 查看提示
    -總結(jié):在這個(gè)例子中,可以看到是由于子線程的引用,導(dǎo)致GC無(wú)法回收MainActivity,引發(fā)了內(nèi)存泄漏了(264KB)。

在項(xiàng)目中引入LeakCanary

  1. 配置 build.gradle文件,添加后我們點(diǎn)Sync Now ,或者build一下
dependencies {
//debugCompile是設(shè)置僅在我們開(kāi)發(fā)Debug的時(shí)候,LeakCanary才會(huì)幫我們?nèi)z測(cè)內(nèi)存泄漏
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
// releaseCompile是打包的時(shí)候LeakCanary不生效
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
 }
  1. 在項(xiàng)目里添加一個(gè)類叫MyApplication 繼承自Application ,并在其中“安裝” LeakCannary:
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        LeakCanary.install(this);
    }
}
  • 記得把它作為 android:name 配到 AndroidManifest.xml 的 Application 節(jié)點(diǎn)下

完成這些步驟后就已經(jīng)把環(huán)境基本上配置好了,不添加任何代碼,LeakCanary會(huì)默認(rèn)幫我們?nèi)z測(cè)Activity的內(nèi)存泄漏。

寫一個(gè)內(nèi)存泄漏的Demo

  • UI布局
  <?xml version="1.0" encoding="utf-8"?>
  <RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"    > 
  <Button        
    android:layout_centerHorizontal="true"
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="開(kāi)啟異步線程"/>
</RelativeLayout>
  • 在MainActivity中添加代碼
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private TextView mTv;
    private Button mButton;

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

        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startAsyncTask();
            }
        });
    }

    public void startAsyncTask(){
        new AsyncTask<Void,Void,Void>(){
            @Override
            protected Void doInBackground(Void... params) {
                Log.v("家俊","doInBackground");
                SystemClock.sleep(10000);
                return null;
            }
        }.execute();
    }
  • 運(yùn)行代碼,點(diǎn)擊button后等待10秒,LeakCanary提示內(nèi)存泄漏。

  • 分析其中的原因:

  • AsyncTask是一個(gè)匿名的內(nèi)部類,隱式的持有外部類(MainActivity)的引用,當(dāng)activity被銷毀的時(shí)候,如果AsyncTask(代碼sleep 20秒,模擬了一個(gè)耗時(shí)操作)沒(méi)有執(zhí)行完成,則MainActivity將會(huì)泄漏

  • 關(guān)于隱式引用----內(nèi)部類可以直接去調(diào)用外部類的成員(屬性和方法),如果沒(méi)有持有外部類的引用,內(nèi)部類是沒(méi)辦法去調(diào)用外部類的成員,但是內(nèi)部類又沒(méi)有顯示的去指定聲明引用,所以稱之為隱式引用。

LeakCanary使用方式

  1. 監(jiān)控 Activity 泄露(默認(rèn))
    由于經(jīng)常把 Activity 當(dāng)作為 Context 對(duì)象使用,在不同場(chǎng)合由各種對(duì)象引用 Activity。所以,Activity 泄漏是一個(gè)重要的需要檢查的內(nèi)存泄漏之一。

  2. 監(jiān)控Fragment泄漏
    -使用RefWatcher監(jiān)控
    此類的對(duì)象的獲取方式:RefWatcher refWatcher=LeakCanary.install(this);
    可以在剛才自定義的全局MyApplication 中去獲取。代碼如下:

public class MyApplication extends Application {
    public static RefWatcher refWatcher;

    @Override
    public void onCreate() {
        super.onCreate();
        refWatcher = LeakCanary.install(this);
    }
}

然后,在需要檢測(cè)Fragment回收的地方,加入refWatcher.watch(this);
-代碼如下

public abstract class BaseFragment extends Fragment {
    @Override 
    public void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = MyApplication.refWatcher;
        refWatcher.watch(this);
    }
}

當(dāng) Fragment.ondestroy()被調(diào)用之后,如果這個(gè) fragment 實(shí)例沒(méi)有被銷毀,那么就會(huì)從通知欄和 logcat 里看到相應(yīng)的泄漏信息。

  1. 監(jiān)控其他泄漏
  RefWatcher refWatcher = MyApplication.refWatcher;
  refWatcher.watch(someObjNeedGced);

someObjNeedGced標(biāo)識(shí)我們需要監(jiān)控的對(duì)象,當(dāng) someObjNeedGced 還在內(nèi)存中時(shí),就會(huì)在 logcat 里看到內(nèi)存泄漏的提示。

原理

  • LeakCanary 流程圖
  • LeakCanary 的機(jī)制如下:
    1.RefWatcher.watch() 會(huì)以監(jiān)控對(duì)象來(lái)創(chuàng)建一個(gè) KeyedWeakReference 弱引用對(duì)象
    2.在 AndroidWatchExecutor 的后臺(tái)線程里,來(lái)檢查弱引用已經(jīng)被清除了,如果沒(méi)被清除,則執(zhí)行一次 GC
    3.如果弱引用對(duì)象仍然沒(méi)有被清除,說(shuō)明內(nèi)存泄漏了,系統(tǒng)就導(dǎo)出 hprof 文件,保存在 app 的文件系統(tǒng)目錄下
    4.HeapAnalyzerService 啟動(dòng)一個(gè)單獨(dú)的進(jìn)程,使用 HeapAnalyzer 來(lái)分析 hprof 文件。它使用另外一個(gè)開(kāi)源庫(kù) HAHA。
    5.HeapAnalyzer 通過(guò)查找 KeyedWeakReference 弱引用對(duì)象來(lái)查找內(nèi)在泄漏
    6.HeapAnalyzer 計(jì)算 KeyedWeakReference 所引用對(duì)象的最短強(qiáng)引用路徑,來(lái)分析內(nèi)存泄漏,并且構(gòu)建出對(duì)象引用鏈出來(lái)。
    7.內(nèi)存泄漏信息送回給 DisplayLeakService,它是運(yùn)行在 app 進(jìn)程里的一個(gè)服務(wù)。然后在設(shè)備通知欄顯示內(nèi)存泄漏信息。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,284評(píng)論 25 708
  • 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講,...
    宇宙只有巴掌大閱讀 2,415評(píng)論 0 12
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏...
    _痞子閱讀 1,654評(píng)論 0 8
  • 有沒(méi)有一本書,要用一生來(lái)品讀。 20170523 忽然想到了這個(gè)問(wèn)題,而思考起來(lái)卻有些費(fèi)解,會(huì)有答案嗎。 自覺(jué)也算...
    疏影0701閱讀 500評(píng)論 3 1
  • 23頁(yè) :“從這句話可以看出來(lái),產(chǎn)品值得運(yùn)營(yíng)的一個(gè)重要基礎(chǔ),就是產(chǎn)品本身要有價(jià)值! ” 語(yǔ)句不通。 24頁(yè) :“這...
    佰釧堂白開(kāi)水閱讀 149評(píng)論 0 0