0x00 內(nèi)存泄漏
測(cè)試安卓新版本的過程中,發(fā)現(xiàn)在操作若干次書籍正文之后,雖然沒有出現(xiàn)crash,但是會(huì)出現(xiàn)頁面卡頓、時(shí)不時(shí)會(huì)有黑屏、ANR無響應(yīng)等異常,十分影響體驗(yàn)。于是,我將產(chǎn)生異常時(shí)進(jìn)行了日志打印,看到如下的問題:

此時(shí)系統(tǒng)進(jìn)行頻繁的GC操作,懷疑是發(fā)生內(nèi)存泄漏了。
此時(shí)用adb打印下app的內(nèi)存信息,看下是否有異常:
命令為:adb shell dumpsys meminfo YourPackageName

我們看到View和Activity的數(shù)量非常高,此時(shí)再將老版本的內(nèi)存信息打印下:

果不其然,新版本的內(nèi)存信息與老版本比較起來,內(nèi)存中有較多的View和Activity,一定是發(fā)生了內(nèi)存泄漏。
0x01 LeakCanary定位
將LeakCanary集成到項(xiàng)目源代碼中,集成方法可參考:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0511/2861.html
安裝集成LeakCanary的項(xiàng)目app,進(jìn)行使用,一段時(shí)間后,LeakCanary捕獲到了內(nèi)存泄漏:

然后將log日志拿到:

這里只是截取了一部分,后面還有很長的日志信息,打印出此時(shí)的調(diào)用鏈對(duì)象中的各個(gè)的變量和對(duì)應(yīng)的值。
單純的看LeakCanary分析的結(jié)果,確認(rèn)了是此次新增加的NewActivity引入的內(nèi)存泄漏問題,也看到com.iflytek.cloud.thirdparty.ae(已經(jīng)過混淆,屬混淆后的類名稱)類對(duì)象保持了對(duì)該Acticity的引用沒有釋放引發(fā)的問題。但是還是比較難定位問題,無法直接進(jìn)行解決。所以使用MAT工具進(jìn)行進(jìn)一步分析。
0x02 MAT分析
使用MAT分析的幾個(gè)關(guān)鍵步驟是
- Android Device Monitor拿到Hprof堆文件
- hprof-conv工具轉(zhuǎn)換成MAT可分析的文件
- 使用MAT工具分析
第一步,Android Device Monitor拿到Hprof堆文件
在Android Studio中可以直接打開Android Device Monitor,不過在AS 3.0版本以上就廢棄Android Device Monitor的工具入口了,需要在本機(jī)android sdk路徑下打開tools目錄下的Monitor。

如下圖所示,在Devices列表中選擇我們的測(cè)試設(shè)備,再選擇其中我們項(xiàng)目的包名,選中后點(diǎn)擊更新堆信息按鈕,繼而操作我們的項(xiàng)目app,一段時(shí)間發(fā)生內(nèi)存泄漏后,點(diǎn)擊導(dǎo)出HPROF文件按鈕將堆信息文件進(jìn)行導(dǎo)出,文件是prof格式。

第二步,hprof-conv工具轉(zhuǎn)換成MAT可分析的文件
hprof-conv工具在系統(tǒng)sdk/platform-tools目錄下,使用方法
hprof-conv 輸入文件 輸出文件
得到MAT可分析的hprof文件后,就可以使用MAT分析啦。
第三步,使用MAT工具分析
下載MAT工具,下載地址:https://www.eclipse.org/mat/
打開MAT

點(diǎn)擊File->Open Heap Dump打開上一步轉(zhuǎn)換好的hprof文件:

可以看到內(nèi)存中的所有對(duì)象,如下圖所示:

考慮內(nèi)存泄漏較多發(fā)生在Activity級(jí)別,所以我們篩選出內(nèi)存的Activity數(shù)據(jù)。點(diǎn)擊圖中的QQL圖標(biāo) 輸入指令
select * from instanceof android.app.Activity
類似于SQL語句,點(diǎn)擊紅色嘆號(hào)執(zhí)行后如下圖所示:

此時(shí)展示出了現(xiàn)在內(nèi)存中存留的所有Activity,操作時(shí)我們已經(jīng)關(guān)閉了所有xxNewActivity,但是內(nèi)存中還是有多個(gè)xxNewActivity的實(shí)例,證明xxNewActivity發(fā)生了內(nèi)存泄漏。接下來我們要針對(duì)這些泄漏的Activity深入分析是哪些實(shí)例沒有釋放導(dǎo)致的泄漏。
在泄漏的Activity上右鍵->Path To GC Roots->with all references:

下圖即為path to gc roots的主要對(duì)象:

其實(shí)從GC上說,除了強(qiáng)引用外,其他的引用在JVM需要的情況下是都可以 被GC掉的,如果一個(gè)對(duì)象始終無法被GC,就是因?yàn)閺?qiáng)引用的存在,從而導(dǎo)致在GC的過程中一直得不到回收,因此就內(nèi)存溢出了。我們可以選擇path to GC roots->exclude all phantom/weak/soft etc.reference:

也就是強(qiáng)引用的對(duì)象主要是ShareListsMenu對(duì)context的引用,和xxNewActivity內(nèi)部對(duì)象的強(qiáng)引用。
0x03 問題解決
查到問題后,剩下的就是解決問題了。我們?cè)赟hareListsMenu類中的clearData方法中新增對(duì)mContexts變量的置空操作,解除對(duì)context的引用。

然后我們將修改后的代碼再跑起來,這時(shí)候不會(huì)發(fā)生內(nèi)存泄漏了。問題得到了解決。
ps:關(guān)于MAT在mac上的安裝,會(huì)出現(xiàn)的問題及解決方法:
https://juejin.im/post/5a81482a6fb9a063331522a6
參考文章:
https://www.cnblogs.com/ldq2016/p/6632338.html
http://www.jb51.net/article/77899.htm
http://wensong.iteye.com/blog/1986449