LeakCanary检测到Activity因mLoadedApk内存泄漏,无法定位问题原因
Android Activity 内存泄漏问题解决
问题背景
开发中遇到InstagramActivity内存泄漏问题,已接入LeakCanary检测,无法识别日志中提到的系统变量mLoadedApk、mReceivers,也无法定位泄漏根因。LeakCanary堆分析日志如下:
D/LeakCanary: ==================================== HEAP ANALYSIS RESULT ==================================== 1 APPLICATION LEAKS References underlined with "~~~" are likely causes. Learn more at https://squ.re/leaks. 968964 bytes retained by leaking objects Signature: a29bf580df87c6f6ad305a8d9f7b3bc3a01ea992 ┬─── │ GC Root: System class │ ├─ android.provider.FontsContract class │ Leaking: NO (GetDataFromServer↓ is not leaking and a class is never leaking) │ ↓ static FontsContract.sContext ├─ instant.saver.for_instagram.api.GetDataFromServer instance │ Leaking: NO (Application is a singleton) │ mBase instance of android.app.ContextImpl │ ↓ Application.mLoadedApk │ ~~~~~~~~~~ ├─ android.app.LoadedApk instance │ Leaking: UNKNOWN │ Retaining 1.8 kB in 31 objects │ mApplication instance of instant.saver.for_instagram.api.GetDataFromServer │ Receivers │ ..InstagramActivity@316939088 │ ....InstagramActivity$2@322490448 │ ..GetDataFromServer@316729128 │ ....VisibilityTracker@322489032 │ ↓ LoadedApk.mReceivers │ ~~~~~~~~~~ ├─ android.util.ArrayMap instance │ Leaking: UNKNOWN │ Retaining 1.3 kB in 18 objects │ ↓ ArrayMap.mArray │ ~~~~~~ ├─ java.lang.Object[] array │ Leaking: UNKNOWN │ Retaining 1.3 kB in 16 objects │ ↓ Object[].[0] │ ~~~ ╰→ instant.saver.for_instagram.InstagramActivity instance Leaking: YES (ObjectWatcher was watching this because instant.saver.for_instagram.InstagramActivity received Activity#onDestroy() callback and Activity#mDestroyed is true) Retaining 969.0 kB in 17175 objects key = 20591e0c-1b12-4810-bc0c-50d74a008298 watchDurationMillis = 14793 retainedDurationMillis = 9769 mApplication instance of instant.saver.for_instagram.api.GetDataFromServer mBase instance of androidx.appcompat.view.ContextThemeWrapper ==================================== 0 LIBRARY LEAKS A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over. See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks ==================================== 0 UNREACHABLE OBJECTS An unreachable object is still in memory but LeakCanary could not find a strong reference path from GC roots. ==================================== METADATA Please include this in bug reports and Stack Overflow questions. Build.VERSION.SDK_INT: 30 Build.MANUFACTURER: OnePlus LeakCanary version: 2.7 App process name: instant.saver.for_instagram Stats: LruCache[maxSize=3000,hits=3479,misses=88288,hitRate=3%] RandomAccess[bytes=4242256,reads=88288,travel=46629354274,range=25009860,size=31821126] Heap dump reason: 4 retained objects, app is not visible Analysis duration: 5231 ms Heap dump file path: /storage/emulated/0/Download/leakcanary-instant.saver.for_instagram/2021-11-10_11-02-28_887.hprof Heap dump timestamp: 1636522400108 Heap dump duration: 45952 ms ====================================
核心变量说明
你业务代码中没有声明的mLoadedApk和mReceivers都是Android Framework层的内置变量,作用如下:
mLoadedApk:Application/ContextImpl的成员变量,是应用进程全局唯一的实例,存储当前APK加载的所有上下文信息、资源、组件注册记录等mReceivers:LoadedApk的成员变量,类型为ArrayMap,系统用来存储整个应用所有动态注册的广播接收器的注册记录
泄漏根因定位
从日志的引用链可以明确泄漏路径:
系统FontsContract静态类 -> 静态变量sContext -> GetDataFromServer网络请求单例 -> mLoadedApk -> mReceivers广播注册列表 -> InstagramActivity实例
泄漏由两个问题共同导致:
- 网络请求单例
GetDataFromServer错误传入了InstagramActivity的上下文,被系统静态类间接持有,单例生命周期和应用进程一致,不会被GC回收 - InstagramActivity内部动态注册了匿名广播接收器(日志中的
InstagramActivity$2就是该匿名内部类实例),Activity销毁前没有调用unregisterReceiver()反注册,系统的mReceivers列表会一直持有该接收器的引用,而匿名内部类默认持有外部Activity的引用,最终导致Activity销毁后无法被回收
修复方案
- 修正单例上下文传入:
GetDataFromServer单例初始化时必须传入Application的全局上下文,禁止传入Activity、Service等短生命周期组件的上下文 - 补充广播反注册逻辑:在InstagramActivity的
onDestroy()(对应onCreate中注册)或onStop()(对应onStart中注册)中调用反注册方法,移除系统对广播接收器的持有 - 补充回调清理逻辑:如果InstagramActivity给
GetDataFromServer设置了请求回调,需要在Activity销毁前将回调置空,避免匿名回调持有Activity引用
内容的提问来源于stack exchange,提问作者Roshan Kumar Nayak.
相关产品推荐
相关产品推荐

