編輯:關於Android編程
今天我們來看一下一個內存洩漏檢測神器 leakcanary(https://github.com/square/leakcanary)
首先我們來看一下leakcanary的使用說明
就這麼多,只需要一行代碼,太簡單了,簡單得都有點懷疑它了。
我們來看一下一個簡單的例子,也是它官方源碼中提供的一個例子,這個因為太小了我就截了個圖
從例子中可以看到,AsyncTask執行了sleep操作,但是由於AsyncTask聲明為了一個內部匿名類,此類持有外部類的對象,導致用戶退出此Activity時,此Activity不能被gc回收,安裝此例子到手機,點擊START NEW ASYNCTASK,退出app,觀察手機,會彈出一個內存洩漏通知如下圖
很神奇吧,連洩漏的堆棧調用信息都能查到,比我們在前兩篇用到的工具方便多了
leakcanary很神奇,就像魔術一樣,我們很想知道它背後的運行機制,現在我們就來解析一下leakcanary的源碼。首先從我們應用Application入手,因為leakcanary在使用中只有一行代碼,我們就從這行代碼慢慢跟蹤一下源碼。
public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); LeakCanary.install(this); } }
public static RefWatcher install(Application application) { return install(application, DisplayLeakService.class, AndroidExcludedRefs.createAppDefaults().build()); }
/** * Creates a {@link RefWatcher} that reports results to the provided service, and starts watching * activity references (on ICS+). */ public static RefWatcher install(Application application, Class listenerServiceClass, ExcludedRefs excludedRefs) { if (isInAnalyzerProcess(application)) { return RefWatcher.DISABLED; } enableDisplayLeakActivity(application); //此Listener很重要,在後面會扮演重要角色 HeapDump.Listener heapDumpListener = new ServiceHeapDumpListener(application, listenerServiceClass); //從名字我們就可以看出它是監視內存洩漏對象的 RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs); // ActivityRefWatcher.installOnIcsPlus(application, refWatcher); return refWatcher; }
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) { if (SDK_INT < ICE_CREAM_SANDWICH) { // If you need to support Android < ICS, override onDestroy() in your base activity. return; } ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher); activityRefWatcher.watchActivities(); }ActivityRefWtacher提供了一個方法 watchActivitys()
public void watchActivities() { // Make sure you don't get installed twice. stopWatchingActivities(); application.registerActivityLifecycleCallbacks(lifecycleCallbacks); }
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); } };
void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); }
public void watch(Object watchedReference, String referenceName) { checkNotNull(watchedReference, "watchedReference"); checkNotNull(referenceName, "referenceName"); if (debuggerControl.isDebuggerAttached()) { return; } final long watchStartNanoTime = System.nanoTime(); //首先生成了一個id,此id是用來唯一標識這個檢測對象的 String key = UUID.randomUUID().toString(); //將id存起來 retainedKeys.add(key); //KeyWeakReference集成自 WeakReference(弱引用),WeakReference使用來跟蹤這個對象的, //弱引用大家都明白,它不會影響gc回收,構造WeakReference時,可以傳入一個ReferenceQueue, //這個ReferenceQueue的主要作用是當對象不可達時也就是可以被gc回收時,對象所對應的WeakReference就會被放入 //ReferenceQueue中,只要檢測ReferenceQueue是否有我們的對象的WeakReference,就可以判斷對象是否可能洩漏 final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue); watchExecutor.execute(new Runnable() { @Override public void run() { //此方法就是為了確認對象是否可回收 ensureGone(reference, watchStartNanoTime); } }); }
void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); //此方法是循環ReferenceQueue,如果對象的ReferenceQueue在裡面,就從retainedKeys中移除對象的key, //因為此對象已經可回收,是安全的 removeWeaklyReachableReferences(); //判斷我們要檢測的reference是否還在retainedKeys中,如果不在說明已經被移除了,也就是可以被gc回收了 if (gone(reference) || debuggerControl.isDebuggerAttached()) { return; } //執行垃圾回收,但是只是建議,並不是一定會執行 gcTrigger.runGc(); //再次從retainedKeys移除安全的key removeWeaklyReachableReferences(); //如果此對象的WeakReference還是不能被回收,那麼此對象就有可能洩漏了,只是可能,因為gc在上一步可能沒有運行 if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); //此方法獲得內存Heap的hprof文件,LeakCanary之所以這麼好用,主要是在這裡,它分析了hprof文件,來確認內存洩漏, //我們在上一篇也分析過hprof文件,原來LeakCanary也是分析這個文件,只是不需要人工分析了,LeakCanary用了一個自己 //的開源hprof分析庫haha(https://github.com/square/haha)此庫是基於google的perflib. File heapDumpFile = heapDumper.dumpHeap(); if (heapDumpFile == HeapDumper.NO_DUMP) { // Could not dump the heap, abort. return; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); //heapdumpListener主要就是啟動服務分析hprof文件 heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); } }
@Override public void analyze(HeapDump heapDump) { checkNotNull(heapDump, "heapDump"); HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass); }
public static void runAnalysis(Context context, HeapDump heapDump, Class listenerServiceClass) { Intent intent = new Intent(context, HeapAnalyzerService.class); intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName()); intent.putExtra(HEAPDUMP_EXTRA, heapDump); context.startService(intent); }
@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); //分析hprof的核心類 HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs); //檢查我們的對象是否內存洩漏 AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey); AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result); }進入checkForLeak方法
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); //解析過程,是基於google的perflib庫,根據hprof的格式進行解析,這裡就不展開看了 Snapshot snapshot = parser.parse(); //分析結果進行去重 deduplicateGcRoots(snapshot); //此方法就是根據我們需要檢測的類的key,查詢解析結果中是否有我們的對象,獲取解析結果中我們檢測的對象 Instance leakingRef = findLeakingReference(referenceKey, snapshot); //此對象不存在表示已經被gc清除了,不存在洩露因此返回無洩漏 // False alarm, weak reference was cleared in between key check and heap dump. if (leakingRef == null) { return noLeak(since(analysisStartNanoTime)); } //此對象存在也不能也不能確認它內存洩漏了,要檢測此對象的gc root return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef); } catch (Throwable e) { return failure(e, since(analysisStartNanoTime)); } }
private Instance findLeakingReference(String key, Snapshot snapshot) { //因為需要檢測的類都構造了一個KeyedWeakReference,因此先找到KeyedWeakReference,就可以找到我們的對象 ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName()); List最後一步,也是最核心的方法,確認是否內存洩漏,和我們手動分析hprof的方法幾乎相同keysFound = new ArrayList<>(); //循環所有KeyedWeakReference實例 for (Instance instance : refClass.getInstancesList()) { List values = classInstanceValues(instance); //找到KeyedWeakReference裡面的key值,此值在我們前面傳入的對象唯一標示 String keyCandidate = asString(fieldValue(values, "key")); //當key值相等時就表示是我們的檢測對象 if (keyCandidate.equals(key)) { return fieldValue(values, "referent"); } keysFound.add(keyCandidate); } throw new IllegalStateException( "Could not find weak reference with key " + key + " in " + keysFound); }
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef) { //這兩行代碼是判斷內存洩露的關鍵,我們在上篇中分析hprof文件,判斷內存洩漏 //判斷的依據是展開調用到gc root,所謂gc root,就是不能被gc回收的對象, //gc root有很多類型,我們只要關注兩種類型1.此對象是靜態 2.此對象被其他線程使用,並且其他線程正在運行,沒有結束 //pathFinder.findPath方法中也就是判斷這兩種情況 ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs); ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef); // 找不到引起內存洩漏的gc root,就表示此對象未洩漏 // 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(); retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance); return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize, since(analysisStartNanoTime)); }
Google在Android6.0之後就刪除了HttpClient相關的API,使用HttpUrlConnection代替,在Android開發中,網絡訪問是必不可少的,
最近有空在看《App研發錄》一書,良心之作。書中第一部分第二章節講了不少關於網絡底層封裝的知識,看後覺得學到了不少干貨。索性自己也動手完成了一個非常輕量級的網絡請求框架,
為什麼要使用異步任務?Android 單線程模型,多線程的操作系統耗時操作放在非主線程中運行AsyncTask 為何而生?子線程中更新UI封裝簡化異步操作構建AsyncT
正文 1 Started Service介紹 Started Service,即被啟動的服務。它是2種常見服務之一,另一種是Bo