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
- 配置 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'
}
- 在項(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使用方式
監(jiān)控 Activity 泄露(默認(rèn))
由于經(jīng)常把 Activity 當(dāng)作為 Context 對(duì)象使用,在不同場(chǎng)合由各種對(duì)象引用 Activity。所以,Activity 泄漏是一個(gè)重要的需要檢查的內(nèi)存泄漏之一。監(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)的泄漏信息。
- 監(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)存泄漏信息。