您好,登錄后才能下訂單哦!
這篇文章主要介紹Android中OOM與Leakcanary的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
對于資源性對象不再使用時,應該立即調用它的close()函數,將其關閉,然后再置為null。例如Bitmap等資源未關閉會造成內存泄漏,此時我們應該在Activity銷毀時及時關閉。
例如BraodcastReceiver、EventBus未注銷造成的內存泄漏,我們應該在Activity銷毀時及時注銷。
對象盡量避免使用靜態變量存儲數據,特別是大數據對象,建議使用數據庫存儲。
優先使用Application的Context,如需使用Activity的Context,可以在傳入Context時使用弱引用進行封裝,然后,在使用到的地方從弱引用中獲取Context,如果獲取不到,則直接return即可。
該實例的生命周期和應用一樣長,這就導致該靜態實例一直持有該Activity的引用,Activity的內存資源不能正常回收。此時,我們可以將該內部類設為靜態內部類或將該內部類抽取出來封裝成一個單例,如果需要使用Context,盡量使用Application Context,如果需要使用Activity Context,就記得用完后置空讓GC可以回收,否則還是會內存泄漏。
Message發出之后存儲在MessageQueue中,在Message中存在一個target,它是Handler的一個引用,Message在Queue中存在的時間過長,就會導致Handler無法被回收。如果Handler是非靜態的,則會導致Activity或者Service不會被回收。并且消息隊列是在一個Looper線程中不斷地輪詢處理消息,當這個Activity退出時,消息隊列中還有未處理的消息或者正在處理的消息,并且消息隊列中的Message持有Handler實例的引用,Handler又持有Activity的引用,所以導致該Activity的內存資源無法及時回收,引發內存泄漏。解決方案如下所示:
1. 使用一個靜態Handler內部類,然后對Handler持有的對象(一般是Activity)使用弱引用,這樣在回收時,也可以回收Handler持有的對象。
2. 在Activity的Destroy或者Stop時,應該移除消息隊列中的消息,避免Looper線程的消息隊列中有待處理的消息需要處理。需要注意的是,AsyncTask內部也是Handler機制,同樣存在內存泄漏風險,但其一般是臨時性的。對于類似AsyncTask或是線程造成的內存泄漏,我們也可以將AsyncTask和Runnable類獨立出來或者使用靜態內部類。
在退出程序之前,將集合里的東西clear,然后置為null,再退出程序
WebView都存在內存泄漏的問題,在應用中只要使用一次WebView,內存就不會被釋放掉。我們可以為WebView開啟一個獨立的進程,使用AIDL與應用的主進程進行通信,WebView所在的進程可以根據業務的需要選擇合適的時機進行銷毀,達到正常釋放內存的目的。
在構造Adapter時,使用緩存的convertView。
// leakcanary 添加支持庫即可,只在debug下使用 debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
leakcanary 不需要初始化,用的是 ContentProvider!
ContentProvider.onCreate 方法比 Application.onCreate 更早執行。LeakCanary 源碼的 Manifest.xml 里有聲明ContentProvider,apk打包流程中會把所有的Manifest合并到app 的 Manifest 里,即APP就有了ContentProvider。
// package="com.squareup.leakcanary.leaksentry" <application> <provider android:name="leakcanary.internal.LeakSentryInstaller" android:authorities="${applicationId}.leak-sentry-installer" android:exported="false"/> </application>
下面是初始化的代碼
internal class LeakSentryInstaller : ContentProvider() { override fun onCreate(): Boolean { CanaryLog.logger = DefaultCanaryLog() val application = context!!.applicationContext as Application // 進行初始化工作,核心 InternalLeakSentry.install(application) return true }
監聽實現
fun install(application: Application) { CanaryLog.d("Installing LeakSentry") // 只能在主線程調用,否則會拋出異常 checkMainThread() if (this::application.isInitialized) { return } InternalLeakSentry.application = application val configProvider = { LeakSentry.config } // 監聽 Activity.onDestroy() ActivityDestroyWatcher.install( application, refWatcher, configProvider ) // 監聽 Fragment.onDestroy() FragmentDestroyWatcher.install( application, refWatcher, configProvider ) // Sentry 哨兵 listener.onLeakSentryInstalled(application) }
在了解監聽過程前有必要了解下 ActivityLifecycleCallbacks 與 FragmentLifeCycleCallbacks
// ActivityLifecycleCallbacks 接口 public interface ActivityLifecycleCallbacks { void onActivityCreated(Activity var1, Bundle var2); void onActivityStarted(Activity var1); void onActivityResumed(Activity var1); void onActivityPaused(Activity var1); void onActivityStopped(Activity var1); void onActivitySaveInstanceState(Activity var1, Bundle var2); void onActivityDestroyed(Activity var1); } // FragmentLifecycleCallbacks 接口 public abstract static class FragmentLifecycleCallbacks { public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {} public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {} public void onFragmentDestroyed(FragmentManager fm, Fragment f) {} // 省略其他的生命周期 ... }
Application 類提供了 registerActivityLifecycleCallbacks 和 unregisterActivityLifecycleCallbacks 方法用于注冊和反注冊 Activity 的生命周期監聽類,這樣我們就能在 Application 中對所有的 Activity 生命周期回調中做一些統一處理。同理,FragmentManager 類提供了 registerFragmentLifecycleCallbacks 和 unregisterFragmentLifecycleCallbacks 方法用戶注冊和反注冊 Fragment 的生命周期監聽類,這樣我們對每一個 Activity 進行注冊,就能獲取所有的 Fragment 生命周期回調。
下面是 ActivityDestroyWatcher 的實現,refWatcher 監聽 activity 的 onActivityDestroyed
internal class ActivityDestroyWatcher private constructor( private val refWatcher: RefWatcher, private val configProvider: () -> Config ) { private val lifecycleCallbacks = object : ActivityLifecycleCallbacksAdapter() { override fun onActivityDestroyed(activity: Activity) { if (configProvider().watchActivities) { // 監聽到 onDestroy() 之后,通過 refWatcher 監測 Activity refWatcher.watch(activity) } } } companion object { fun install( application: Application, refWatcher: RefWatcher, configProvider: () -> Config ) { val activityDestroyWatcher = ActivityDestroyWatcher(refWatcher, configProvider) // 注冊 Activity 生命周期監聽 application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks) } } }
如此一來Activity、Fragment在調用onDestroy時我們都能知道。講道理,如果在調用onDestroy時被GC是正常的,如果沒有被回收則是發生了內存泄漏,這是我們要處理的。那 refWatcher.watch(activity) 監聽到銷毀后怎么處理?
在讀這塊代碼前舉個栗子比較好理解:比如我們去科技中心面試
進去的時候會登記個人信息在觀察列表,并標明停留時間30分鐘
30分鐘過后查看是否有登出
如果未登出將信息由觀察列表轉移至懷疑列表
懷疑列表名單超過5個時,找公安人員確定是否是恐怖分子
確定是恐怖分子,警察抓人
RefWatcher 的實現原理跟上面的栗子神似:
Activity調用onDestroy后,以UUID生成key,被KeyedWeakReference包裝,并與ReferenceQueue關聯,并把<key,KeyedWeakReference>存入 watchedReferences 中(watchedReferences 對應觀察隊列)
等待5s時間
調用 moveToRetained 方法,先判斷是否已經釋放,如果未釋放由 watchedReferences (觀察隊列) 轉入 retainedReferences(懷疑隊列)
當 retainedReferences 隊列的長度大于5時,先調用一次GC,用HAHA這個開源庫去分析dump之后的heap內存
確定內存泄漏對象
咱們先看下 refWatcher.watch(activity) 的實現
@Synchronized fun watch( watchedReference: Any, referenceName: String ) { if (!isEnabled()) { return } // 移除隊列中將要被 GC 的引用 removeWeaklyReachableReferences() val key = UUID.randomUUID().toString() val watchUptimeMillis = clock.uptimeMillis() // 構建當前引用的弱引用對象,并關聯引用隊列 queue val reference = KeyedWeakReference(watchedReference, key, referenceName, watchUptimeMillis, queue) if (referenceName != "") { CanaryLog.d( "Watching instance of %s named %s with key %s", reference.className, referenceName, key ) } else { CanaryLog.d( "Watching instance of %s with key %s", reference.className, key ) } // 將引用存入 watchedReferences watchedReferences[key] = reference checkRetainedExecutor.execute { // 如果當前引用未被移除,仍在 watchedReferences 隊列中, // 說明仍未被 GC,移入 retainedReferences 隊列中,暫時標記為泄露 moveToRetained(key) } }
分析上面這段代碼都做了什么:
移除隊列中將要被 GC 的引用,這里的隊列包括 watchedReferences 和 retainedReferences
使用UUID生成唯一key,構建 WeakReference 包裝 activity 并與 ReferenceQueue 關聯
將 reference 放入觀察隊列 watchedReferences 中
線程池調用 moveToRetained 函數,此函數先走一遍gc,依舊沒回收的對象會進入 retainedReferences 懷疑隊列,當隊列大于5時調用HAHA庫走可達性分析確定是否是內存泄漏
下面是細節分析 —》removeWeaklyReachableReferences() 邏輯
private fun removeWeaklyReachableReferences() { // WeakReferences are enqueued as soon as the object to which they point to becomes weakly // reachable. This is before finalization or garbage collection has actually happened. // 弱引用一旦變得弱可達,就會立即入隊。這將在 finalization 或者 GC 之前發生。 var ref: KeyedWeakReference? do { // 隊列 queue 中的對象都是會被 GC 的 ref = queue.poll() as KeyedWeakReference? //說明被釋放了 if (ref != null) { val removedRef = watchedReferences.remove(ref.key)//獲取被釋放的引用的key if (removedRef == null) { retainedReferences.remove(ref.key) } // 移除 watchedReferences 隊列中的會被 GC 的 ref 對象,剩下的就是可能泄露的對象 } } while (ref != null) }
removeWeaklyReachableReferences 函數會根據 ReferenceQueue 出來的 KeyedWeakReference 的 key 移除 watchedReferences(觀察隊列)和 retainedReferences(懷疑隊列)中的引用,即把已經釋放的移出,剩下的是內存泄漏的
moveToRetained(key) 邏輯實現
@Synchronized private fun moveToRetained(key: String) { // 再次調用,防止遺漏 removeWeaklyReachableReferences() val retainedRef = watchedReferences.remove(key) //說明可能存在內存泄漏 if (retainedRef != null) { retainedReferences[key] = retainedRef onReferenceRetained() } }
此函數的作用:
走一遍 removeWeaklyReachableReferences 方法,將已經回收的清除
將 watchedReferences(觀察隊列)中未被回收的引用移到 retainedReferences(懷疑隊列)中
onReferenceRetained() 則是在工作線程中檢測內存泄漏,最后會調用 checkRetainedInstances 函數
下面是 checkRetainedInstances 的具體實現
private fun checkRetainedInstances(reason: String) { CanaryLog.d("Checking retained instances because %s", reason) val config = configProvider() // A tick will be rescheduled when this is turned back on. if (!config.dumpHeap) { return } var retainedKeys = refWatcher.retainedKeys // 當前泄露實例個數小于 5 個,不進行 heap dump if (checkRetainedCount(retainedKeys, config.retainedVisibleThreshold)) return if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) { showRetainedCountWithDebuggerAttached(retainedKeys.size) scheduleRetainedInstanceCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS) CanaryLog.d( "Not checking for leaks while the debugger is attached, will retry in %d ms", WAIT_FOR_DEBUG_MILLIS ) return } // 可能存在被觀察的引用將要變得弱可達,但是還未入隊引用隊列。 // 這時候應該主動調用一次 GC,可能可以避免一次 heap dump gcTrigger.runGc() retainedKeys = refWatcher.retainedKeys if (checkRetainedCount(retainedKeys, config.retainedVisibleThreshold)) return HeapDumpMemoryStore.setRetainedKeysForHeapDump(retainedKeys) CanaryLog.d("Found %d retained references, dumping the heap", retainedKeys.size) HeapDumpMemoryStore.heapDumpUptimeMillis = SystemClock.uptimeMillis() dismissNotification() val heapDumpFile = heapDumper.dumpHeap() // AndroidHeapDumper if (heapDumpFile == null) { CanaryLog.d("Failed to dump heap, will retry in %d ms", WAIT_AFTER_DUMP_FAILED_MILLIS) scheduleRetainedInstanceCheck("failed to dump heap", WAIT_AFTER_DUMP_FAILED_MILLIS) showRetainedCountWithHeapDumpFailed(retainedKeys.size) return } refWatcher.removeRetainedKeys(retainedKeys) // 移除已經 heap dump 的 retainedKeys HeapAnalyzerService.runAnalysis(application, heapDumpFile) // 分析 heap dump 文件 }
以上是“Android中OOM與Leakcanary的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。