在Android的性能優(yōu)化中,內(nèi)存優(yōu)化是必不可少的點(diǎn),而內(nèi)存優(yōu)化最重要的一點(diǎn)就是解決內(nèi)存泄漏的問(wèn)題,在Android的內(nèi)存泄漏分析工具也不少,比如PC端的有:AndroidStudio自帶的Android Profiler、MAT等工具;手機(jī)端也有,就是我們今天要介紹的LeakCanary
LeakCanary簡(jiǎn)介
LeakCanary是Square公司為Android開(kāi)發(fā)者提供的一個(gè)自動(dòng)檢測(cè)內(nèi)存泄漏的工具,
LeakCanary本質(zhì)上是一個(gè)基于MAT進(jìn)行Android應(yīng)用程序內(nèi)存泄漏自動(dòng)化檢測(cè)的的開(kāi)源工具,我們可以通過(guò)集成LeakCanary提供的jar包到自己的工程中,一旦檢測(cè)到內(nèi)存泄漏,LeakCanary就好dump Memory信息,并通過(guò)另一個(gè)進(jìn)程分析內(nèi)存泄漏的信息并展示出來(lái),隨時(shí)發(fā)現(xiàn)和定位內(nèi)存泄漏問(wèn)題,而不用每次在開(kāi)發(fā)流程中都抽出專(zhuān)人來(lái)進(jìn)行內(nèi)存泄漏問(wèn)題檢測(cè),極大地方便了Android應(yīng)用程序的開(kāi)發(fā)。
LeakCanary顯示內(nèi)存泄漏的頁(yè)面:
LeakCanary優(yōu)點(diǎn):
1、針對(duì)Android Activity組件完全自動(dòng)化的內(nèi)存泄漏檢查。
2、可定制一些行為(dump文件和leaktrace對(duì)象的數(shù)量、自定義例外、分析結(jié)果的自定義處理等)。
3、集成到自己工程并使用的成本很低。
4、友好的界面展示和通知。
LeakCanary工作機(jī)制:
引用LeakCanary中文使用說(shuō)明,它的基本工作機(jī)制如下:
1、RefWatcher.watch() 創(chuàng)建一個(gè) KeyedWeakReference 到要被監(jiān)控的對(duì)象。
2、然后在后臺(tái)線程檢查引用是否被清除,如果沒(méi)有,調(diào)用GC。
3、如果引用還是未被清除,把 heap 內(nèi)存 dump 到 APP 對(duì)應(yīng)的文件系統(tǒng)中的一個(gè) .hprof 文件中。
4、在另外一個(gè)進(jìn)程中的 HeapAnalyzerService 有一個(gè) HeapAnalyzer 使用HAHA 解析這個(gè)文件。
5、得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位內(nèi)存泄露。
6、HeapAnalyzer 計(jì)算 到 GC roots 的最短強(qiáng)引用路徑,并確定是否是泄露。如果是的話,建立導(dǎo)致泄露的引用鏈。
7、引用鏈傳遞到 APP 進(jìn)程中的 DisplayLeakService, 并以通知的形式展示出來(lái)。
LeakCanary的Android Studio集成
一、 在build.gradle中添加LeakCanary的依賴(lài)包,截止目前l(fā)eakcanary的最新版本是1.6.1:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
}
在開(kāi)發(fā)中一般同時(shí)集成debug和release版本,其中:
com.squareup.leakcanary:leakcanary-android:1.6.1 是debug版本,在你的app編譯的是debug版本,加載的是該jar包,一旦出現(xiàn)內(nèi)存泄漏會(huì)在通知欄中通知開(kāi)發(fā)者產(chǎn)生了內(nèi)存泄漏;
com.squareup.leakcanary:leakcanary-android-no-op:1.6.1 是release版本,如果你的app編譯的是release版本時(shí),加載的是該jar包,no-op是指No Operation Performed,代表不會(huì)做任何操作,不會(huì)干擾正式用戶(hù)的使用;
二、 在我們自定義Application的onCreate方法中注冊(cè)LeakCanary
@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.isInAnalyzerProcess(this)
在注冊(cè)之前先判斷LeakCanary是否已經(jīng)運(yùn)行在手機(jī)上,比如你同時(shí)有多個(gè)APP集成了LeakCanary,其他app已經(jīng)運(yùn)行了LeakCanary則不需要重新install
我們看一下isInAnalyzerProcess方法的源碼:
/**
* Whether the current process is the process running the {@link HeapAnalyzerService}, which is
* a different process than the normal app process.
*/
public static boolean isInAnalyzerProcess(Context context) {
Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
// This only needs to be computed once per process.
if (isInAnalyzerProcess == null) {
isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
}
return isInAnalyzerProcess;
}
從源碼中可以看到真正調(diào)用的是isInServiceProcess方法,在來(lái)看下isInServiceProcess的源碼:
public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
} catch (Exception e) {
CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());
return false;
}
String mainProcess = packageInfo.applicationInfo.processName;
ComponentName component = new ComponentName(context, serviceClass);
ServiceInfo serviceInfo;
try {
serviceInfo = packageManager.getServiceInfo(component, 0);
} catch (PackageManager.NameNotFoundException ignored) {
// Service is disabled.
return false;
}
if (serviceInfo.processName.equals(mainProcess)) {
CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);
// Technically we are in the service process, but we're not in the service dedicated process.
return false;
}
int myPid = android.os.Process.myPid();
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.RunningAppProcessInfo myProcess = null;
List<ActivityManager.RunningAppProcessInfo> runningProcesses;
try {
runningProcesses = activityManager.getRunningAppProcesses();
} catch (SecurityException exception) {
// https://github.com/square/leakcanary/issues/948
CanaryLog.d("Could not get running app processes %d", exception);
return false;
}
if (runningProcesses != null) {
for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
if (process.pid == myPid) {
myProcess = process;
break;
}
}
}
if (myProcess == null) {
CanaryLog.d("Could not find running process for %d", myPid);
return false;
}
return myProcess.processName.equals(serviceInfo.processName);
}
我們可以看到ComponentName component = new ComponentName(context, serviceClass);
而這個(gè)serviceClass參數(shù)是isInAnalyzerProcess方法中調(diào)用的HeapAnalyzerService,HeapAnalyzerService正是用來(lái)分析內(nèi)存泄漏的單獨(dú)進(jìn)程,說(shuō)以說(shuō)LeakCanary在同一個(gè)手機(jī)只需要執(zhí)行一次install就可以了,當(dāng)然執(zhí)行多次也是可以的;
- 回到正題,正常情況下Application調(diào)用LeakCanary.install(this)后就可以正常監(jiān)聽(tīng)該app程序的內(nèi)存泄漏;
LeakCanary監(jiān)聽(tīng)指定對(duì)象的內(nèi)存泄漏
如果想讓LeakCanary監(jiān)聽(tīng)指定對(duì)象的內(nèi)存泄漏,我們就需要使用到RefWatcher的watch功能,使用方式如下:
- 在Application的onCreate中調(diào)用install方法,并獲取RefWatcher對(duì)象:
private static RefWatcher sRefWatcher;
@Override
public void onCreate() {
super.onCreate();
sRefWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher() {
return sRefWatcher;
}
注意:因?yàn)檫@時(shí)候需要獲取sRefWatcher對(duì)象,所以sRefWatcher = LeakCanary.install(this)一定需要執(zhí)行,不需要判斷LeakCanary.isInAnalyzerProcess(this)
- 為了方便演示使用LeakCanary獲取和解決內(nèi)存泄漏的問(wèn)題,我們先寫(xiě)一個(gè)內(nèi)存泄漏的場(chǎng)景,我們知道最常見(jiàn)的內(nèi)存泄漏是單列模式使用Activity的Context場(chǎng)景,所以我們也用單列模式來(lái)演示:
public class Singleton {
private static Singleton singleton;
private Context context;
private Singleton(Context context) {
this.context = context;
}
public static Singleton newInstance(Context context) {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null){//雙重檢查鎖定
singleton = new Singleton(context);
}
}
}
return singleton;
}
}
- 在需要監(jiān)聽(tīng)的對(duì)象中調(diào)用RefWatcher的watch方法進(jìn)行監(jiān)聽(tīng),比如我想監(jiān)聽(tīng)一個(gè)Activity,我們可以在該Acitivity中onCreate方法中添加DemoApp.getRefWatcher().watch(this);:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DemoApp.getRefWatcher().watch(this);
setContentView(R.layout.activity_second);
Singleton singleton = Singleton.newInstance(this);
}
LeakCanary內(nèi)存泄漏展示頁(yè)面
同時(shí)上面的步驟,當(dāng)我們?cè)谶\(yùn)行app程序的時(shí)候,出現(xiàn)內(nèi)存泄漏后,過(guò)一小段時(shí)間后就會(huì)在通知欄中通知出現(xiàn)內(nèi)存泄漏的情況:
同時(shí)會(huì)在桌面上生成一個(gè)Leaks的圖標(biāo),這個(gè)就是展示內(nèi)存泄漏列表的,內(nèi)存泄漏列表頁(yè)面如下:
這是一個(gè)內(nèi)存泄漏的列表,我們可以通過(guò)點(diǎn)擊進(jìn)入查看泄漏的內(nèi)容
還可以通過(guò)點(diǎn)擊右邊的“+”號(hào)查看更詳細(xì)的信息,內(nèi)容太長(zhǎng)就不截圖了,內(nèi)部有詳細(xì)介紹調(diào)用的流程;
內(nèi)存泄漏解決
上面的演示列子我們使用的是單列模式來(lái)產(chǎn)生內(nèi)存泄漏,我們當(dāng)然知道怎樣正確的解決這個(gè)內(nèi)存泄漏,
就是Singleton singleton = Singleton.newInstance(this)的調(diào)用傳入Contxt時(shí)使用ApplicationContext;
這里總結(jié)一下產(chǎn)生內(nèi)存泄漏的常見(jiàn)場(chǎng)景和常用的解決方案
- 常見(jiàn)的內(nèi)存泄漏場(chǎng)景:
1、單例設(shè)計(jì)模式造成的內(nèi)存泄漏
2、非靜態(tài)內(nèi)部類(lèi)創(chuàng)建的靜態(tài)實(shí)例造成的內(nèi)存泄漏
3、Handler造成的內(nèi)存泄漏
4、線程造成的內(nèi)存泄漏
5、資源未關(guān)閉造成的內(nèi)存泄漏
- 常見(jiàn)的解決方案(思路)
1、盡量使用Application的Context而不是Activity的漏
2、使用弱引用或者軟引用漏
3、手動(dòng)設(shè)置null,解除引用關(guān)系漏
4、將內(nèi)部類(lèi)設(shè)置為static,不隱式持有外部的實(shí)例漏
5、注冊(cè)與反注冊(cè)成對(duì)出現(xiàn),在對(duì)象合適的生命周期進(jìn)行反注冊(cè)操作。漏
6、如果沒(méi)有修改的權(quán)限,比如系統(tǒng)或者第三方SDK,可以使用反射進(jìn)行解決持有關(guān)系
7、在使用完BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源時(shí),一定要在Activity中的OnDestry中及時(shí)的關(guān)閉、注銷(xiāo)或者釋放內(nèi)存,
由于篇幅過(guò)長(zhǎng)這里就不對(duì)此展開(kāi)介紹。
參考鏈接: