一、什么是內(nèi)存泄漏?
在Android的開(kāi)發(fā)中,經(jīng)常聽(tīng)到“內(nèi)存泄漏”這個(gè)詞?!皟?nèi)存泄漏”就是一個(gè)對(duì)象已經(jīng)不需要再使用了,但是因?yàn)槠渌膶?duì)象持有該對(duì)象的引用,導(dǎo)致它的內(nèi)存不能被回收。那么在android開(kāi)發(fā)過(guò)程中,有哪些比較常見(jiàn)的場(chǎng)景呢?
其實(shí)主要就是activity或者fragment內(nèi)存泄漏了。
那么,泄漏的原因一般有哪些呢?
1、數(shù)據(jù)庫(kù)游標(biāo)忘記關(guān)閉了。
2、bitmap忘記回收
3、不當(dāng)?shù)氖褂胔andler
4、timer等不當(dāng)?shù)氖褂谩?br>
5、地圖等
總之,歸納起來(lái)一句話來(lái)講:
目前開(kāi)發(fā)者遇到的內(nèi)存泄漏普遍就是以上幾種了,這里列舉的僅僅是我遇到的比較多的場(chǎng)景,也不排除還有其他場(chǎng)景。
二、那么遇到內(nèi)存泄漏一般是怎么追蹤并解決問(wèn)題的呢?
通常,開(kāi)發(fā)者會(huì)利用android studio自帶的工具進(jìn)行分析:
1、讓自己的app先跑上一段時(shí)間,在懷疑會(huì)產(chǎn)生內(nèi)存泄漏的幾個(gè)地方來(lái)回的多切換幾次。
然后,打開(kāi)著界面,在點(diǎn)擊這里。

對(duì),通常先GC一下,因?yàn)?,?nèi)存泄漏并不會(huì)因?yàn)镚C而消除,GC只是將一些不必要的干擾因素排除掉而已。
2、然后點(diǎn)GC右邊的那個(gè)按鈕,dump一下內(nèi)存,來(lái)進(jìn)行分析,dump之后,自動(dòng)出現(xiàn)這個(gè)界面

為了方便查看,我們通常會(huì)根據(jù)包名Arrage by package分類(lèi)查看,其實(shí)我們只關(guān)心自己應(yīng)用包名先的activity,fragment之類(lèi)的一些。
3、這里,右邊會(huì)列舉出實(shí)例的個(gè)數(shù),通常出現(xiàn)幾個(gè)實(shí)例的activity其實(shí)就是你該嚴(yán)重懷疑的對(duì)象了,那么

根據(jù)這里的引用樹(shù),最終層層剝根,你就可以輕易發(fā)現(xiàn)最終是誰(shuí)引用到了這個(gè)activity,而導(dǎo)致他泄漏了。
三,恩,回顧一下,這個(gè)過(guò)程,真是太麻煩了,那么,有沒(méi)有一個(gè)神器可以不用這么麻煩,就幫我們發(fā)現(xiàn)內(nèi)存泄漏,以及定位出原因呢?
答案顯然是有的,他就是 LeakCanary

1、leak canary的原理:
實(shí)際上就是有一個(gè)線程專(zhuān)門(mén)去分析內(nèi)存圖譜,然后,有一些列的lifecircler之類(lèi)的鉤子監(jiān)聽(tīng)activity 的destory方法,如果一旦發(fā)現(xiàn)某個(gè)activity執(zhí)行的 destroy發(fā)放,但是,他的實(shí)例還一直存在于內(nèi)存中,那就判定這個(gè)activity泄漏了。
一下就是HeapAnalyzer的一段代碼片段:
/**
* Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,
* and then computes the shortest strong reference path from that instance to the GC roots.
*/
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));
}
}
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
Instance leakingRef) {
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
// False alarm, no strong reference path to GC Roots.
if (result.leakingNode == null) {
return noLeak(since(analysisStartNanoTime));
}
LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
String className = leakingRef.getClassObj().getClassName();
// Side effect: computes retained size.
snapshot.computeDominators();
Instance leakingInstance = result.leakingNode.instance;
long retainedSize = leakingInstance.getTotalRetainedSize();
// TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
if (SDK_INT <= N_MR1) {
retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
}
return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
since(analysisStartNanoTime));
}
其中,這段代碼清晰表明:
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);
如果一個(gè)該銷(xiāo)毀的context還被持有,那么就是泄漏了。
2、leak canary 使用:
如果你使用android studio開(kāi)發(fā),那么相當(dāng)簡(jiǎn)單
在build.gralde中添加
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}
然后:在你的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...
}
}
其中
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
是一個(gè)空操作集合,表示,在生產(chǎn)環(huán)境下,不會(huì)進(jìn)行內(nèi)心泄漏檢測(cè),只是在開(kāi)發(fā)調(diào)試環(huán)境起作用,為什么要這么做呢?很顯然,一個(gè)是為了app流暢,內(nèi)存泄漏分析是需要消耗性能的,第二個(gè),用戶其實(shí)對(duì)你的app內(nèi)存泄漏并不關(guān)心,一般用戶也不明白這是個(gè)什么鬼,你突然彈出一個(gè)提醒說(shuō)某某activity泄漏了,用戶也是一臉懵逼。