【性能篇1】LeakCanary 內(nèi)存泄漏問(wèn)題分析思路

本周在處理內(nèi)存泄漏的問(wèn)題,網(wǎng)上找了很久未找到系統(tǒng)性的方法,本文是總結(jié)的LeakCanary內(nèi)存泄漏分析方法,并不一定是最優(yōu)解,部分系統(tǒng)引用鏈的貌似不適用,如果能幫忙到你那就最好了,如果你有更優(yōu)的方法,可以互相學(xué)習(xí)告知我一下,非常感謝。由于本地PC上傳圖片到簡(jiǎn)書(shū)異常,無(wú)法傳圖,故下面的圖示內(nèi)容都手動(dòng)用代碼打出來(lái)。

1 總結(jié)

下面部分內(nèi)容關(guān)鍵字為避免暴露個(gè)人信息,這里用*號(hào)代替了,這里不影響分析,下面列出LeakCanary上報(bào)的內(nèi)存泄漏問(wèn)題如下:

應(yīng)用版本:
4.3.0_28b2c3a_201215

泄漏對(duì)象
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

剛拿到這個(gè)bug,第一反應(yīng)是這塊代碼也沒(méi)看過(guò),對(duì)PhoneLayoutInflater的邏輯并不熟悉,是如何持有ContactEditorActivity的引用的呢?下面先給出方法,總結(jié)如下:
1)根據(jù)上面的hash 28b2c3a獲取到APK和mapping文件
2)將獲取到的APK拖入到AS中,根據(jù)引用鏈通過(guò)findUsage 找到調(diào)用關(guān)系,定位原因

話不多說(shuō),下面以上述問(wèn)題作為實(shí)例進(jìn)行分析

2 實(shí)例分析

1、根據(jù)hash定位APK和mapping,通過(guò)hash找到編譯出來(lái)的apk和mapping文件
2、將APK拖入到AS中,根據(jù)引用鏈進(jìn)行分析
(1)將APK拖入到AS中如圖所示(對(duì)應(yīng)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文件,根據(jù)引用鏈提供的包名和類名定位到具體的類
*android.database.ContentObserver$Transport.mContentObserver
如根據(jù)上面路徑可以找到ContentObserver類,這里--表示一級(jí)一級(jí)的目錄

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

(3)選中ContentObserver對(duì)象,右鍵Find Usages 顯示出調(diào)用到ContentObserver的地方如下,根據(jù)引用鏈很快就可以定位到引用ColorAlertController$1:

如下……表示引用關(guān)系
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)

同時(shí)通過(guò)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 引用鏈繼續(xù)點(diǎn)擊展開(kāi),可以清楚找到持有ContactEditorActivity引用的邏輯如圖所示,是通過(guò)ContactEditorActivity$6內(nèi)部類引用傳遞的,已經(jīng)定位到一層一層的引用關(guān)系和問(wèn)題點(diǎn):

如下……表示引用關(guān)系
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)同樣的根據(jù)包名和類名找到ContactEditorActivity6,這里可以點(diǎn)擊Class進(jìn)行排序,便于快速找到,選擇初始化init<>方法右鍵Show ByteCode,通過(guò)smali字節(jié)碼可知,ContactEditorActivity6匿名內(nèi)部類是在1055行進(jìn)行的初始化

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

(6)找到ContactEditorActivity類1055行可知,這里Handler匿名內(nèi)部類持有了ContactEditorActivity引用導(dǎo)致內(nèi)存泄漏

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

3、問(wèn)題修復(fù)
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改為靜態(tài)內(nèi)部類+弱引用,另外排查mHandler消息操作內(nèi)容,確認(rèn)界面退出時(shí)移除消息。

3、常見(jiàn)的幾個(gè)內(nèi)存泄漏

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

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

4、可以忽略的內(nèi)存泄漏

(1) 上報(bào)示例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)上報(bào)示例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 根據(jù)需求可以忽略的內(nèi)存泄漏,詳見(jiàn)上述文件

5、常見(jiàn)內(nèi)存泄漏問(wèn)題處理

下面列出一些LeakCanary內(nèi)存泄漏的案例,后面有案例會(huì)繼續(xù)補(bǔ)充

【案例1】、關(guān)鍵字"LoadedApk.mServices"

【泄漏對(duì)象】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

【分析】根據(jù)上述堆棧這里比較重要的是需要熟悉bindService的流程,bindService啟動(dòng)流程建議參考這篇文章:http://gityuan.com/2016/05/01/bind-service/
LoadedApk對(duì)象是APK文件在內(nèi)存中的表示,APK文件的相關(guān)信息,諸如APK文件的代碼和資源。
【原因】service未解綁
【解決方法】在onDestroy的時(shí)候unbindService

【案例2】、關(guān)鍵字"ContactPhotoLoader"

【泄漏對(duì)象】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退出時(shí),mPengdingRequests通過(guò)ImageView持有activity的引用
【解決方法】退出時(shí),清掉mPengdingRequests中的實(shí)例

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容