當前位置:
首頁 > 最新 > 記一次Activity的內存泄漏和分析過程

記一次Activity的內存泄漏和分析過程

今日科技快訊

7月23日消息,近日,全國多家P2P(互聯網金融借貸平台)因出現了逾期、跑路、倒閉的情況而備受關注。因在應用商店中推薦這類應用的下載,手機廠商小米因此躺槍了。對此,小米方面回應稱,目前正全面清查金融類的應用。「接到有用戶反映相關情況,我們第一時間就下線了所有P2P推廣廣告。」小米方面表示。

作者簡介

本篇來自o動感超人o的投稿,分享了記一次Activity的內存泄漏和分析過程,一起來看看!希望大家喜歡。

o動感超人o的博客地址:

https://www.jianshu.com/u/b433b31eadad

正文

發現這個問題的原由是測試提出的一個bug,是某個地圖頁面多次操作以後會出現卡頓甚至會ANR,很明顯肯定是內存的問題,我就用Android Profiler查看了一下內存,發現出現某個圖層操作的時候短時間內會很頻繁的觸發GC操作,然後無意中發現退出這個地圖頁面的時候LeakCanary會說此頁面泄露了10M+的內存,雖然這個LeakCanary不是每次都很準確,不過它報了就得去查看一下,然後在Android Profiler里發現此頁面(以後該頁面稱為MapActivity)在退出後仍然佔用了大量內存,頻繁觸發GC先不管,先把這個問題解決掉,圖片如下:

這個MapActivity已經退出了,看來是有實例在引用著它導致沒辦法釋放內存,然後看一下都有誰引用了此類

好了,再查一下context是在哪裡設置的,不過這個欄位最好用弱引用WeakReference去包一下,而且是public的我覺得不太好吧。。。不過作者可能有他的考慮。。。如果是我的話我會用WeakReference去包一下,不然太容易內存泄漏了,然後我找到了context設置的地方

在MapActivity類里我是這麼調用的

所以這個context是MapActivity,而內存泄露的也是這個MapActivity,然後我們點擊前面的箭頭展開context,看誰引用了ConfigBean

不知道為什麼,其中ConfigBean$3這個類我並沒有找到,但是Tool的3個匿名類我在Tool的位元組碼文件里找到了

可以看到,是Tool類的setListener方法里的代碼,然後我們看源碼里的這個方法

可以看到,這3個類正是dialog設置的3個監聽,是3個匿名類,而這3個匿名類都引用了外部的Dialog和ConfigBean,所以這3個匿名類持有參數傳遞過來的Dialog和ConfigBean兩個實例的強引用,我們先看其中的一個方法dialog.setOnShowListener的源碼

而這個mShowMessage也對應了我們上圖中的mShowMessage引用,我再發一次

其他兩個監聽的設置也是一樣的,分別將3個匿名類作為Message的obj屬性存到了Message里,現在的情況是Message持有匿名類的實例,而匿名類持有Dialog和ConfigBean的實例。然後在我們隱藏Dialog的時候,調用了這個第三方庫的此方法。

然後我們看一下這個方法的源碼

然後StyledDialog類的這個方法又調用了DialogsMaintainer.dismissLoading(activity);我們繼續查看DialogsMaintainer類的此方法

我覺得dialogsOfActivity和loadingDialogs這兩個Map也是用弱引用比較好這個方法我們會找到該Activity里所有的Dialog然後調用dialog的dismiss()方法。而Dialog的dismiss方法做了什麼呢,看代碼

當不在創建Dialog的線程的時候,會調用Dialog線程的mHandler發送mDismissAction這個Runnable,否則就直接在創建Dialog的線程執行dismissDialog()方法,mDismissAction這個Runnable的run方法會執行dismissDialog()方法(這個Runnable只是執行run方法,它並沒有新起一個線程去start),然後看dismissDialog()方法

繼續看sendDismissMessage()方法

可以看到之前將匿名類設置給自己obj屬性的Message將自己發送到了它的targerHandler所在Looper中的MessageQueue中。到現在了我們根據下圖總結一下。

這裡有個套路,每行所在的類被下一行in前面指針所引用,所以下圖就是:MapActivity被ConfigBean的context屬性持有,ConfigBean作為參數bean被Tool$2、Tool$3、Tool$4這三個匿名類持有(val$bean代表方法參數bean),這三個匿名類又被Message的obj屬性所持有,下面以此類推,不過下面就看不太清楚邏輯了,這時候我們需要Eclipse Memory Analyzer,也就是平時所說的MAT軟體,下載過程不贅述,假設現在讀者下載好了MAT,然後用Android Studio點擊下圖中紅框里的按鈕導出剛才我們分析的東東。

導出文件假如命名為leak.hprof,然後打開終端用hprof-conv leak.hprof leak_mat.hprof生成可以給MAT分析的hprof文件,打開後我選擇第一項。

我剛發現直接點Cancel也可以打開文件並分析。。。打開後點擊如圖所示的按鈕。

然後在向右的三個箭頭那裡輸入我們泄露的MapActivity。

然後右鍵選擇空白的那個圖標。

選擇Path to GC Roots和下面的沒什麼區別,這裡我們選擇Merge Shortest Paths to GC Roots,然後排除不需要關心的弱引用軟引用之類的東東。然後結果出來了。

看到這裡,其實我們應該明白,Tool$4這個匿名類持有ConfigBean,而ConfigBean持有的context是我們的MapActivity,這個Tool$4匿名類是這樣的。

方法參數new DialogInterface.OnDismissListener(){。。。}就是Tool$4,這個Tool$4被設置為了dialog的mDismissMessage類屬性的obj屬性,這個Message會在dialog執行dismiss的時候發送到Looper所持有的MessageQueue中,在Looper的loop方法中取到這個Message消費完以後會調用Message.recycleUnchecked方法去回收它所佔用的內存,而這時候肯定因為某種原因無法釋放,然後可以思考一下,這個Message是Dialog中的一個類屬性,然後可以聯想到這個Dialog因為某種原因被某類持有,然後查詢一下這個第三方庫會發現在DialogsMaintainer類中有2個靜態集合,而在調用DialogsMaintainer.dismissLoading之後的流程中並沒有把Dialog移除,好了這次內存泄漏分析之旅到此結束。

總結

提示:如果把ConfigBean中的context設置為弱引用,那麼需要把DialogsMaintainer中的兩個用到Activity的靜態map的key也變為弱引用,因為這兩個靜態map的key和ConfigBean中的context類屬性是同一個值,弱引用的特性是當一個對象僅僅被weak reference指向,而沒有任何其他strong reference指向的時候,如果GC運行,那麼這個對象就會被回收。所以如果只把ConfigBean中的context類屬性改為弱引用,其他地方仍然有這個指針的強引用那麼這樣的改動沒有任何效果。而在這個框架里如果要修復這個bug,應該像上面說明的那樣改動。

其實我們公司的項目只是用到它顯示了一個Dialog,後期我要去掉這個框架自己做一個Dialog來用,這個故事告訴我們用第三方框架要謹慎啊!!!

或者掃一掃關注我的公眾號


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 郭霖 的精彩文章:

Android開發之桌面快捷鍵使用細則
Android Studio項目模板全面解析

TAG:郭霖 |