一沈贝、什么是內(nèi)存泄漏?
在Android的開發(fā)中踩晶,經(jīng)常聽到“內(nèi)存泄漏”這個詞执泰。“內(nèi)存泄漏”就是一個對象已經(jīng)不需要再使用了渡蜻,但是因為其它的對象持有該對象的引用术吝,導(dǎo)致它的內(nèi)存不能被回收。那么在android開發(fā)過程中茸苇,有哪些比較常見的場景呢排苍?
其實主要就是activity或者fragment內(nèi)存泄漏了。
那么学密,泄漏的原因一般有哪些呢淘衙?
1、數(shù)據(jù)庫游標(biāo)忘記關(guān)閉了腻暮。
2幔翰、bitmap忘記回收
3漩氨、不當(dāng)?shù)氖褂胔andler
4、timer等不當(dāng)?shù)氖褂谩?br>
5遗增、地圖等
總之,歸納起來一句話來講:
目前開發(fā)者遇到的內(nèi)存泄漏普遍就是以上幾種了款青,這里列舉的僅僅是我遇到的比較多的場景做修,也不排除還有其他場景。
二抡草、那么遇到內(nèi)存泄漏一般是怎么追蹤并解決問題的呢饰及?
通常,開發(fā)者會利用android studio自帶的工具進(jìn)行分析:
1康震、讓自己的app先跑上一段時間燎含,在懷疑會產(chǎn)生內(nèi)存泄漏的幾個地方來回的多切換幾次。
然后腿短,打開著界面,在點擊這里。
對材蹬,通常先GC一下赡鲜,因為,內(nèi)存泄漏并不會因為GC而消除钝诚,GC只是將一些不必要的干擾因素排除掉而已颖御。
2、然后點GC右邊的那個按鈕凝颇,dump一下內(nèi)存潘拱,來進(jìn)行分析,dump之后拧略,自動出現(xiàn)這個界面
為了方便查看芦岂,我們通常會根據(jù)包名Arrage by package分類查看,其實我們只關(guān)心自己應(yīng)用包名先的activity辑鲤,fragment之類的一些盔腔。
3、這里月褥,右邊會列舉出實例的個數(shù)弛随,通常出現(xiàn)幾個實例的activity其實就是你該嚴(yán)重懷疑的對象了,那么
根據(jù)這里的引用樹宁赤,最終層層剝根舀透,你就可以輕易發(fā)現(xiàn)最終是誰引用到了這個activity,而導(dǎo)致他泄漏了决左。
三愕够,恩走贪,回顧一下,這個過程惑芭,真是太麻煩了坠狡,那么,有沒有一個神器可以不用這么麻煩遂跟,就幫我們發(fā)現(xiàn)內(nèi)存泄漏逃沿,以及定位出原因呢?
答案顯然是有的幻锁,他就是 LeakCanary
1凯亮、leak canary的原理:
實際上就是有一個線程專門去分析內(nèi)存圖譜,然后哄尔,有一些列的lifecircler之類的鉤子監(jiān)聽activity 的destory方法假消,如果一旦發(fā)現(xiàn)某個activity執(zhí)行的 destroy發(fā)放,但是岭接,他的實例還一直存在于內(nèi)存中富拗,那就判定這個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);
如果一個該銷毀的context還被持有媒峡,那么就是泄漏了。
2葵擎、leak canary 使用:
如果你使用android studio開發(fā)谅阿,那么相當(dāng)簡單
在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'
是一個空操作集合酬滤,表示签餐,在生產(chǎn)環(huán)境下,不會進(jìn)行內(nèi)心泄漏檢測盯串,只是在開發(fā)調(diào)試環(huán)境起作用氯檐,為什么要這么做呢?很顯然体捏,一個是為了app流暢冠摄,內(nèi)存泄漏分析是需要消耗性能的,第二個几缭,用戶其實對你的app內(nèi)存泄漏并不關(guān)心河泳,一般用戶也不明白這是個什么鬼,你突然彈出一個提醒說某某activity泄漏了年栓,用戶也是一臉懵逼拆挥。