【性能篇1】LeakCanary 内存泄漏问题分析思路

本周在处理内存泄漏的问题,网上找了很久未找到系统性的方法,本文是总结的LeakCanary内存泄漏分析方法,并不一定是最优解,部分系统引用链的貌似不适用,如果能帮忙到你那就最好了,如果你有更优的方法,可以互相学习告知我一下,非常感谢。由于本地PC上传图片到简书异常,无法传图,故下面的图示内容都手动用代码打出来。

1 总结

下面部分内容关键字为避免暴露个人信息,这里用*号代替了,这里不影响分析,下面列出LeakCanary上报的内存泄漏问题如下:

应用版本:
4.3.0_28b2c3a_201215

泄漏对象
com.android.*.activities.ContactEditorActivity

引用链
*android.database.ContentObserver$Transport.mContentObserver
**.support.v7.app.*AlertController$1.aym
**.support.v7.app.*AlertController.mContext
*android.view.ContextThemeWrapper.mInflater
*com.android.internal.policy.PhoneLayoutInflater.mPrivateFactory
*com.android.*.activities.ContactEditorActivity

刚拿到这个bug,第一反应是这块代码也没看过,对PhoneLayoutInflater的逻辑并不熟悉,是如何持有ContactEditorActivity的引用的呢?下面先给出方法,总结如下:
1)根据上面的hash 28b2c3a获取到APK和mapping文件
2)将获取到的APK拖入到AS中,根据引用链通过findUsage 找到调用关系,定位原因

话不多说,下面以上述问题作为实例进行分析

2 实例分析

1、根据hash定位APK和mapping,通过hash找到编译出来的apk和mapping文件
2、将APK拖入到AS中,根据引用链进行分析
(1)将APK拖入到AS中如图所示(对应APK拖到AS中的视图)

--assets
--lib
--classes.dex
--classes2.dex
--res
--resources.arsc
--META-INF
--com
--kotlin
--google
--okhttp3
--AndroidManifest.xml
--build-data.properties

(2) 双击classes.dex文件,根据引用链提供的包名和类名定位到具体的类
*android.database.ContentObserver$Transport.mContentObserver
如根据上面路径可以找到ContentObserver类,这里--表示一级一级的目录

如下--表示目录
--android
----database
------ContentObserver
--------<init>(android.os.Handler)
--------void onChange(boolean)

(3)选中ContentObserver对象,右键Find Usages 显示出调用到ContentObserver的地方如下,根据引用链很快就可以定位到引用ColorAlertController$1:

如下……表示引用关系
References to <init>(android.os.Hanlder)
……android.database.ContentObserver:void <init>(android.os.Handler)
…………*.support.v7.app.*AlertController$1:void <init>(*.support.v7.app.*AlertController, android.os.Handler)

同时通过mapping.txt文件可以知道ColorAlertController$1.aym即是ColorAlertController引用

*.support.v7.app.*AlertController$1  ------->*..support.v7.app.*AlertController$1 
------*.support.v7.app.*AlertController this$0 -->aym

(4)上面References 引用链继续点击展开,可以清楚找到持有ContactEditorActivity引用的逻辑如图所示,是通过ContactEditorActivity$6内部类引用传递的,已经定位到一层一层的引用关系和问题点:

如下……表示引用关系
References to <init>(android.os.Hanlder)
…android.database.ContentObserver:void <init>(android.os.Handler)
……*.support.v7.app.*AlertController$1:void <init>(*.support.v7.app.*AlertController, android.os.Handler)
………*support.v7.app.AlertDialog:void <init>(android.content.Context, int)
…………*support.v7.app.AlertDialog$Builder:*.support.v7.app.AlertDialog create()
……………com.android.*.activities.ContactEditorActivity:android.app.Dialog getDeleteContactDialog()
………………com.android.*.activities.ContactEditorActivity$6:void handleMessage(android.os.Message)

(5)同样的根据包名和类名找到ContactEditorActivity6,这里可以点击Class进行排序,便于快速找到,选择初始化init<>方法右键Show ByteCode,通过smali字节码可知,ContactEditorActivity6匿名内部类是在1055行进行的初始化

.line 1055
input-object p1, p0, Lcom/android/*/activities/ContactEditorActivity$6;->aLR:

(6)找到ContactEditorActivity类1055行可知,这里Handler匿名内部类持有了ContactEditorActivity引用导致内存泄漏

private Handler mHandler = new Handler() {
    @override
    public void handleMessage(Message msg) {
            *******内容忽略******
    }
}

3、问题修复
Handler reference leaks
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected.If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.
Issue id: HandlerLeak

这里可以按照google推荐的将Handler改为静态内部类+弱引用,另外排查mHandler消息操作内容,确认界面退出时移除消息。

3、常见的几个内存泄漏

https://android.jlelse.eu/9-ways-to-avoid-memory-leaks-in-android-b6d81648e35e

https://medium.com/bumble-tech/android-handler-memory-leaks-7291c5be6101

4、可以忽略的内存泄漏

(1) 上报示例1

Tap here to lean more
android.os.HandlerThread.<Java Local>
android.os.Message.obj(excluded)
com.alan.test.activities.BlockedCallLogAndSmsListActivity$7.this$0
com.alan.test.activities.BlockedCallLogAndSmsListActivity

(2)上报示例2

Tap here to lean more
android.os.HandlerThread.<Java Local>
android.os.Message.obj(excluded)
com.alan.test.activities.-$$Lambda$BlockedCallLogAndSmsListActivity$vkmvLDKpSCgJ3bP-j-y9zEUgxRM.f$0
com.alan.test.activities.BlockedCallLogAndSmsListActivity

https://github.com/square/leakcanary/blob/v1.3.1/library/leakcanary-android/src/main/java/com/squareup/leakcanary/AndroidExcludedRefs.java#L112

上述是LeakCanary 根据需求可以忽略的内存泄漏,详见上述文件

5、常见内存泄漏问题处理

下面列出一些LeakCanary内存泄漏的案例,后面有案例会继续补充

【案例1】、关键字"LoadedApk.mServices"

【泄漏对象】com.alan.test.activities.MainActivity

【引用链】
*thread java.lang.Thread.(named ModemAsyncTask #1)
*com.alan.test.MyApplication.mLoadedApk
*android.app.LoadedApk.mServices
*android.util.ArrayMap.mArray
*array java.lang.Objec[].[2]
*com.alan.test.activities.MainActivity

【分析】根据上述堆栈这里比较重要的是需要熟悉bindService的流程,bindService启动流程建议参考这篇文章:http://gityuan.com/2016/05/01/bind-service/
LoadedApk对象是APK文件在内存中的表示,APK文件的相关信息,诸如APK文件的代码和资源。
【原因】service未解绑
【解决方法】在onDestroy的时候unbindService

【案例2】、关键字"ContactPhotoLoader"

【泄漏对象】com.android.*.activities.ContactEditorActivity

【引用链】
*thread com.android.*.e$b.aFv(named ContactPhotoLoader)
*com.android.*.e.aFn
*java.util.concurrent.ConcurrentHashMap.table
*array java.util.concurrent.ConcurrentHashMap$Node[].[6]
*java.util.concurrent.ConcurrentHashMap$Node.key
*com.*.support.widget.*RoundImageView.mContext
*com.android.*.activities.ContactEditorActivity

【分析】引用链如下:com.android.contacts.ContactPhotoManagerImpl -> com.android.contacts.e:
java.util.concurrent.ConcurrentHashMap mPendingRequests -> aFn
【原因】Activity退出时,mPengdingRequests通过ImageView持有activity的引用
【解决方法】退出时,清掉mPengdingRequests中的实例

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容