Android內(nèi)存泄漏一直是困擾我們Android開發(fā)者的一個(gè)心病治力,由于開發(fā)人員對(duì)于知識(shí)掌握的不夠深入或者代碼的不夠規(guī)范牡整,稍不注意就會(huì)導(dǎo)致代碼出現(xiàn)內(nèi)存泄漏短条。那么怎么解決內(nèi)存泄漏呢?
1流礁、自查渠欺,在AndroidStudio中打開Monitors妹蔽,Dump Java Heap,然后通過MAT分析工具分析hprof文件挠将,查找出內(nèi)存泄漏的地方胳岂。
2、集成Square的LeakCanary舔稀,讓LeakCanary這個(gè)第三方庫幫我們查找內(nèi)存泄漏的地方乳丰。
本文集成LeakCanary并且分析LeakCanary的源碼,看完之后就能明白LeakCanary檢測(cè)內(nèi)存泄漏的原理内贮,做到知其然并知其所以然产园。
使用:
LeakCanary使用起來很簡(jiǎn)單,首先在gradle中添加依賴:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
leakcanary-android-no-op這個(gè)module是用于release版本的夜郁,因?yàn)?LeakCanary 會(huì)不斷 GC 導(dǎo)致stop the world什燕, 影響到 app 的運(yùn)行,這在release版本是不允許的竞端,因此我們只能用在debug階段秋冰。該module只有一個(gè)LeakCanary,RefWatcher婶熬,BuildConfig這三個(gè)基礎(chǔ)類剑勾,當(dāng)然也是什么都不做的埃撵。添加完依賴后在我們的application里添加如下代碼即可。
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);
// Normal app init code...
}
}
源碼分析:
先看入口LeakCanary.java中的install(this)
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
這個(gè)代碼的引用鏈比較長(zhǎng)虽另,一層一層看暂刘,先看結(jié)果,該方法返回一個(gè)RefWatcher捂刺,RefWatcher用來監(jiān)視一個(gè)引用的可達(dá)情況谣拣。
/** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
refWatcher()方法返回一個(gè)AndroidRefWatcherBuilder,該對(duì)象繼承自RefWatcherBuilder族展,用來通過建造者模式構(gòu)建一個(gè)RefWatcher森缠。
AndroidRefWatcherBuilder.java
/**
* Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This
* overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.
*/
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
注意listenerServiceClass()方法的參數(shù)AbstractAnalysisResultService,這個(gè)類是一個(gè)繼承自IntentService的抽象類仪缸,用來處理HeapDump結(jié)果贵涵,在上面的代碼中傳入了一個(gè)DisplayLeakService,DisplayLeakService繼承自AbstractAnalysisResultService恰画。記住這個(gè)類宾茂,后面會(huì)說到。
有時(shí)候我們需要忽略一些特殊情況下的內(nèi)存泄漏比如SDK或者制造商導(dǎo)致的內(nèi)存泄漏拴还,這些內(nèi)存泄漏我們并不需要顯示出來跨晴,那么剛好AndroidExcludedRefs這個(gè)枚舉就派上用場(chǎng)了。
public static ExcludedRefs.Builder createAppDefaults() {
return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
}
public static ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
ExcludedRefs.Builder excluded = ExcludedRefs.builder();
for (AndroidExcludedRefs ref : refs) {
if (ref.applies) {
ref.add(excluded);
((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
}
}
return excluded;
}
當(dāng)我們需要忽略一些我們自己已知的內(nèi)存泄漏時(shí)片林,可以創(chuàng)建一個(gè)EnumSet并且調(diào)用createBuilder方法端盆,這樣LeakCanary就不會(huì)處理這些內(nèi)存泄漏。
最后到了關(guān)鍵一個(gè)方法:
/**
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
首先通過build模式構(gòu)造一個(gè)RefWatcher费封,然后調(diào)用LeakCanary.enableDisplayLeakActivity(context)這句啟用DisplayLeakActivity焕妙,代碼如下:
public static void enableDisplayLeakActivity(Context context) {
setEnabled(context, DisplayLeakActivity.class, true);
}
public static void setEnabled(Context context, final Class<?> componentClass,
final boolean enabled) {
final Context appContext = context.getApplicationContext();
executeOnFileIoThread(new Runnable() {
@Override public void run() {
setEnabledBlocking(appContext, componentClass, enabled);
}
});
}
public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
注意DisplayLeakActivity是LeakCanary這個(gè)庫自己的Activity,用來創(chuàng)建PendingIntent以便發(fā)送通知并且在這個(gè)Activity里展示內(nèi)存泄漏信息孝偎。
接著上面繼續(xù)分析访敌,之后調(diào)用ActivityRefWatcher.install((Application) context, refWatcher)。
public final class ActivityRefWatcher {
/** @deprecated Use {@link #install(Application, RefWatcher)}. */
@Deprecated
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
install(application, refWatcher);
}
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
private final Application application;
private final RefWatcher refWatcher;
/**
* Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking
* after they have been destroyed.
*/
public ActivityRefWatcher(Application application, RefWatcher refWatcher) {
this.application = checkNotNull(application, "application");
this.refWatcher = checkNotNull(refWatcher, "refWatcher");
}
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
}
代碼其實(shí)很簡(jiǎn)單衣盾,通過application注冊(cè)ActivityLifecycleCallbacks寺旺,監(jiān)聽每個(gè)Activity的生命周期方法并在onDestroy里開始檢測(cè)activity的引用。
接著我們看看他是怎么watch的势决。
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
在分析之前先說一下WeakReference和ReferenceQueue阻塑。WeakReference表示弱引用,當(dāng)一個(gè)對(duì)象只被弱引用所指向而沒有被強(qiáng)引用或者軟引用所引用果复,那么當(dāng)GC運(yùn)行的時(shí)候陈莽,該對(duì)象就會(huì)被GC回收。并且同時(shí)系統(tǒng)會(huì)把該引用加入到與該WeakReference關(guān)聯(lián)的一個(gè)引用隊(duì)列中去,也就是這個(gè)ReferenceQueue中走搁。當(dāng)然前提是你創(chuàng)建該WeakReference的時(shí)候給其關(guān)聯(lián)了ReferenceQueue独柑。
回到代碼中去,上面代碼首先通過UUID.randomUUID生成一個(gè)唯一標(biāo)識(shí)符用來表示這個(gè)KeyedWeakReference的key私植,并把其加入一個(gè)Set集合中忌栅,然后再創(chuàng)建一個(gè)KeyedWeakReference并把需要監(jiān)控的object和引用隊(duì)列關(guān)聯(lián)到這個(gè)KeyedWeakReference,KeyedWeakReference繼承自WeakReference曲稼。最后調(diào)用ensureGoneAsync()方法索绪。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
上面代碼執(zhí)行WatchExecutor的execute()方法,真正實(shí)現(xiàn)WatchExecutor接口的是AndroidWatchExecutor類贫悄,在這個(gè)類里面給主線程的消息隊(duì)列添加一個(gè)IdleHandler瑞驱,當(dāng)主線程空閑沒有消息的時(shí)候就執(zhí)行execute()方法,而在上面的execute()方法里面則調(diào)用ensureGone()方法窄坦。
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
在這個(gè)方法中首先調(diào)用removeWeaklyReachableReferences()方法檢查引用隊(duì)列ReferenceQueue中是否已經(jīng)包含了該object引用對(duì)象唤反,因?yàn)槿绻鸕eferenceQueue中有了這個(gè)對(duì)象的引用則說明這個(gè)對(duì)象已經(jīng)被GC回收了,那么就不存在內(nèi)存溢出這一現(xiàn)象嫡丙,調(diào)用一次這個(gè)方法之后再調(diào)用gcTrigger.runGc()執(zhí)行一次GC拴袭,然后再次調(diào)用removeWeaklyReachableReferences這個(gè)方法檢查ReferenceQueue中是否有這個(gè)對(duì)象读第,這樣兩次檢查引用隊(duì)列中的對(duì)象之后如果發(fā)現(xiàn)引用隊(duì)列中還沒有這個(gè)引用對(duì)象則說明泄漏了曙博。內(nèi)存泄漏了怎么辦呢?執(zhí)行heapDumper.dumpHeap()方法讓其生成一個(gè)文件怜瞒。同樣實(shí)現(xiàn)heapDumper接口的是AndroidHeapDumper父泳,然后看看他的dumpHeap()方法。
@Override
public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
showToast(waitingForToast);
if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
Toast toast = waitingForToast.get();
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
上面代碼中首先通過LeakDirectoryProvider生成一個(gè)文件吴汪,實(shí)現(xiàn)LeakDirectoryProvider接口的是DefaultLeakDirectoryProvider惠窄,來看看其的newHeapDumpFile()方法:
@Override
public File newHeapDumpFile() {
List<File> pendingHeapDumps = listFiles(new FilenameFilter() {
@Override public boolean accept(File dir, String filename) {
return filename.endsWith(PENDING_HEAPDUMP_SUFFIX);
}
});
for (File file : pendingHeapDumps) {
if (System.currentTimeMillis() - file.lastModified() < ANALYSIS_MAX_DURATION_MS) {
CanaryLog.d("Could not dump heap, previous analysis still is in progress.");
return RETRY_LATER;
}
}
cleanupOldHeapDumps();
File storageDirectory = externalStorageDirectory();
if (!directoryWritableAfterMkdirs(storageDirectory)) {
if (!hasStoragePermission()) {
CanaryLog.d("WRITE_EXTERNAL_STORAGE permission not granted");
requestWritePermissionNotification();
} else {
String state = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(state)) {
CanaryLog.d("External storage not mounted, state: %s", state);
} else {
CanaryLog.d("Could not create heap dump directory in external storage: [%s]",
storageDirectory.getAbsolutePath());
}
}
// Fallback to app storage.
storageDirectory = appStorageDirectory();
if (!directoryWritableAfterMkdirs(storageDirectory)) {
CanaryLog.d("Could not create heap dump directory in app storage: [%s]",
storageDirectory.getAbsolutePath());
return RETRY_LATER;
}
}
return new File(storageDirectory, UUID.randomUUID().toString() + PENDING_HEAPDUMP_SUFFIX);
}
我們可以看到都是一些對(duì)文件的操作,創(chuàng)建一個(gè)以.hprof結(jié)尾的文件并返回漾橙。
繼續(xù)接著分析上面的dumpHeap()方法杆融,創(chuàng)建完文件后調(diào)用Debug.dumpHprofData()方法把heap信息寫入文件中。
再回到上面的ensureGone()方法霜运,當(dāng)把heap信息寫入文件之后脾歇,調(diào)用heapdumpListener.analyze()方法進(jìn)行分析。而實(shí)現(xiàn)了heapdumpListener接口的是ServiceHeapDumpListener類淘捡,來看看其的analyze()方法:
@Override
public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
執(zhí)行HeapAnalyzerService.runAnalysis方法藕各,這個(gè)方法第三個(gè)參數(shù)listenerServiceClass就是我們之前在上面創(chuàng)建AndroidRefWatcherBuilder時(shí)提到的DisplayLeakService,而HeapAnalyzerService是一個(gè)運(yùn)行在單獨(dú)進(jìn)程中的IntentService:
public final class HeapAnalyzerService extends IntentService {
private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
private static final String HEAPDUMP_EXTRA = "heapdump_extra";
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
context.startService(intent);
}
public HeapAnalyzerService() {
super(HeapAnalyzerService.class.getSimpleName());
}
@Override protected void onHandleIntent(Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
}
在runAnalysis()方法里啟動(dòng)自己焦除,然后在onHandleIntent方法里調(diào)用heapAnalyzer.checkForLeak()方法進(jìn)行分析激况。
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
deduplicateGcRoots(snapshot);
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
這個(gè)方法主要就是依靠Square的另一個(gè)庫haha來解析heap數(shù)據(jù),HprofParser是haha庫的解析器對(duì)象,解析hprof文件并生成Snapshot快照乌逐,然后找到該實(shí)例到GCroot的最短強(qiáng)引用路徑竭讳,并生成AnalysisResult對(duì)象≌闾撸回到上面的代碼代咸,生成AnalysisResult對(duì)象后回調(diào)到AbstractAnalysisResultService。我們知道AbstractAnalysisResultService是一個(gè)繼承自IntentService的抽象類成黄,在他的onHandleIntent()方法里調(diào)用抽象方法onHeapAnalyzed()并刪除了hprof文件呐芥,子類DisplayLeakService重寫了onHeapAnalyzed()方法,
@Override
protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = leakInfo(this, heapDump, result, true);
CanaryLog.d("%s", leakInfo);
boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if (shouldSaveResult) {
heapDump = renameHeapdump(heapDump);
resultSaved = saveResult(heapDump, result);
}
PendingIntent pendingIntent;
String contentTitle;
String contentText;
if (!shouldSaveResult) {
contentTitle = getString(R.string.leak_canary_no_leak_title);
contentText = getString(R.string.leak_canary_no_leak_text);
pendingIntent = null;
} else if (resultSaved) {
pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
if (result.failure == null) {
String size = formatShortFileSize(this, result.retainedHeapSize);
String className = classSimpleName(result.className);
if (result.excludedLeak) {
contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
} else {
contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
}
} else {
contentTitle = getString(R.string.leak_canary_analysis_failed);
}
contentText = getString(R.string.leak_canary_notification_message);
} else {
contentTitle = getString(R.string.leak_canary_could_not_save_title);
contentText = getString(R.string.leak_canary_could_not_save_text);
pendingIntent = null;
}
// New notification id every second.
int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
afterDefaultHandling(heapDump, result, leakInfo);
}
我們看到該方法通過DisplayLeakActivity生成了一個(gè)PendingIntent奋岁,構(gòu)建一個(gè)Notification發(fā)出來思瘟。最后調(diào)用afterDefaultHandling()方法,這是一個(gè)空方法闻伶,我們可以重寫這個(gè)方法進(jìn)行自己的一些操作滨攻,比如把泄漏信息上傳到我們自己的服務(wù)器等等。
整個(gè)LeakCanary總算是分析完了蓝翰,我們來總結(jié)一下光绕,首先設(shè)置lifecycleCallbacks監(jiān)聽Activity的onDestroy()方法,然后創(chuàng)建KeyedWeakReference并啟動(dòng)GC畜份,利用WeakReference和ReferenceQueue的機(jī)制判斷Activity是否有泄漏诞帐,如果已經(jīng)泄漏則dump heap并生成一個(gè)hprof文件并解析這個(gè)文件,得到泄漏對(duì)象到GC根的最短路徑爆雹,得到這個(gè)路徑后發(fā)一個(gè)Notification展示出來即可停蕉。