本周在处理内存泄漏的问题,网上找了很久未找到系统性的方法,本文是总结的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匿名内部类是在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
上述是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中的实例