Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 記一次內存洩露優化過程

記一次內存洩露優化過程

編輯:關於Android編程

背景

項目目前存在使用久了或者重復打開關閉某個頁面,內存會一直飙升,居高不下,頻繁發生GC。靜置一段時間後,情況有所改善,但是問題依舊明顯,如圖1-1、1-2。

\
圖1-1.操作時的內存使用情況


圖1-2.靜置時的內存使用情況

如上圖1-1,是通過Android Studio查看內存(灰色)和CPU(紅色)使用情況,可以看出內存有發生抖動並且是處於比較高的狀態,再者,從logcat可以看到一直發生GC,如下圖1-3:

這裡寫圖片描述
圖1-3.<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrP2z9bV4tCpx+m/9qOsysfT0LrctuDS8svY1OyzybXEo6zX7tb30qq1xNSt0vLKx7eiyfrBy8TatObQucK2o7rSs8Pmudix1dauuvOjrLjD0rPD5rbUz/PA7dOmsbu72MrVo6y1q9PJ09rEs9bW1K3S8rW81sLL/MO709Cxu7ywyrG72MrVtfSjrNK71rHVvL7d18XE2rTmo6y1vNbCxNq05r7TuN+yu8/CoaM8L3A+DQo8aDIgaWQ9"目標">目標

本目標就是排查出應用內發生內存洩露的地方,並解決問題,避免組員踩重復的坑,減少應用內存洩露現象,減少內存抖動,減少OOM發生的幾率,提高應用的性能、流暢度,減少卡頓。

內存洩露排查優化過程

內存洩露排查過程,有兩種方法,一種是通過MAT手動分析排查,另一種是通過LeakCanary注入到應用內輔助排查。

使用MAT分析內存洩露

首先,需要下載、安裝內存分析工具MAT(http://www.eclipse.org/mat/)。接著,需要dump取內存,並進行分析。打開應用,先進行幾個簡單的操作:進入首頁->進入社區首頁->進入一條問答詳情界面->迅速關閉頁面->再次進入問答詳情界面->再關閉,以此重復幾次。然後通過Android Studio的Android Monitor將此時的內存dump下來,dump下來的hprof文件,存放在項目目錄下的captures文件夾下,如圖1-4、1-5。

\
圖1-4.dump內存


圖1-5. hprof文件

dump下來的hprof文件還不能用MAT打開,還需要進一步處理。需要借助Android SDK的一個轉換工具,如圖1-6。


圖1-6.hprof-conv轉換工具

通過命令行執行命令:hprof-conv.exe ,是指源文件,是指目標文件,如下圖1-7。

圖1-7. hprof-conv命令

然後使用MAT打開轉換後的1.hprof文件,如下圖1-8。


圖1-8.內存概覽

上圖顯示的是,當前內存使用情況。打開紅色箭頭按鈕,這是OQL,類似於SQL,可以通過查詢語句,查詢對象。
為了排查出哪些界面(Activity)發生了內存洩露,可以使用下面的OQL語句查詢,如圖1-9。


圖1-9.

查詢結果顯示有HomeActivity、QaDetailActivity,是符合之前的操作(進入首頁->進入問答詳情,詳情重復進出)。這裡可以看出QaDetailActivity肯定是發生了內存洩露,因為查詢結果出現了兩個QaDetailActivity,說明內存中存在了兩個QaDetailActivity對象,但是當QaDetailActivity被關閉時,是應該被回收的。接著,執行圖1-10操作,進一步排查是什麼導致了內存洩露。


圖1-10.執行Path To GC Roots


圖1-11.GC Roots 執行結果

由於JAVA的垃圾回收機制是,當一個對象被持有引用時,如果發生GC時,是不會被回收的。如圖1-11所示,當除去弱引用和軟引用,執行GC後,this$0(QaDetailActivity)對象還是被mErrorListener所引用,mErrorListener是項目中Volley網絡請求庫中的一個錯誤回調接口。可以看下,項目中代碼是如何實現的,如圖1-12。


圖1-12.JsonGet的使用

圖1-12是獲取問答詳情信息發起的JsonGet請求,而Response.Listener和Response.ErrorListener都是匿名對象,一個異步回調,回調之後處理相關邏輯。試想一下,當JsonGet發起請求後,或因網絡阻塞,不能及時處理回調,這時候關閉了QaDetailActivity,但是由於Response回調未執行完,還持有QaDetailActivity對象,所以此時QaDetailActivity並不能被回收。所以,目前項目中所有使用該方式請求數據理論上都存在著內存洩露的風險。那麼如何解決此問題引起的內存洩露呢?這個和Android常見的會引起內存洩露的Handler一樣,都是沒有適時的移除回調導致的。優化後的代碼如圖1-13。


圖1-13.

就是將JsonGet等網絡請求通過觀察者模式進行封裝, 在Activity onCreate addObserver,在onDestory時removeObserver,這樣就可以避免上述所遇到的內存洩露問題了。

上面是一個完整的內存洩露排查過程,但是,你會發現,這是在一個黑箱檢測,你不知道什麼時候該去dump下內存進行分析,有可能你操作了很多界面之後dump下的內存並沒有內存洩露問題。如果進行黑箱測試的話,這是一個耗時、耗力的過程,那麼接下來介紹LeakCanary的使用,這是一個內存洩露檢測神器。

使用LeakCanary檢測內存洩露

LeakCanary是square開源的一個項目https://github.com/square/leakcanary,它是一個Android和Java的內存洩露檢測庫,可以大幅度減少了開發中遇到的OOM問題。在項目中,專門開了一個代碼分支dev_LeakCanary,用來檢測內存洩露。關於LeakCanary的工作原理,可以從github開源項目上的wiki中獲知https://github.com/square/leakcanary/wiki/FAQ :

RefWatcher.watch()創建一個KeyedWeakReference去檢測對象; 接著,在後台線程,它將會檢查是否有引用在不是GC觸發的情況下需要被清除的; 如果引用引用仍然沒有被清除,將會轉儲堆到.hprof文件到系統文件中(it them dumps the heap into a .hprof file stored on the app file system.); HeapAnalyzerService是在一個分離的進程中開始的,HeapAnalyzer通過使用HAHA(https://github.com/square/haha )解析heap dump; 由於一個特殊的引用key和定位的洩露引用,HeapAnalyzer可以在heap dump中找到KeyedWeakReference; 如果有一個洩露,HeapAnalyzer計算到GC Roots的最短的強引用路徑,然後創建造成洩露的引用鏈; 結果在app的進程中傳回到DisplayLeakService,並展示洩露的通知消息;

那麼如何使用它呢?
首先,添加相關依賴,如圖1-14。

\
圖1-14.LeakCanary依賴

然後在Application初始化,並獲取一個RefWatcher對象,如圖1-15。


圖1-15.初始化LeakCanary

最後注冊要監聽的對象,比如,要監聽Activity有沒有內存洩露,那麼,在BaseActivity$onDestory注冊監聽,如圖1-16。


圖1-16.監聽Activity

這樣所有繼承BaseActivity都會被檢測。同樣進行開始的那樣操作界面(進入首頁->問答詳情->重復進出)。這時候,會發現通知欄有消息,點開消息,如下圖1-17。


圖1-17.LeakCanary內存洩露分析界面

LeakCanary的分析結果顯示,還是因為JsonGet的網絡請求,異步回調引起的內存洩露,當然,如果要通過MAT查看更多信息,自己分析,可以在手機SD卡下Download/leakcanary文件夾下找到hprof文件,導出來之後根據之前的操作一樣,先通過轉換工具進行轉換,再用MAT打開查看。

顯而易見,通過LeakCanary進行內存洩露檢測簡單了不少,省時省力,但是如何解決內存洩露問題,還是要靠程序員自身的技能了,它只是告訴你哪裡發生了內存洩露。

優化後對比

通過上面的內存洩露排查優化之後,使用相同包名(dev),相同環境(local環境下),相同租戶和用戶,盡可能的進行相同操作。

\
圖1-18.優化後,操作時內存使用情況

與優化前圖1-1相比,優化前內存的使用大小一直處於12M以上,優化後圖1-18,內存使用在峰值在10M左右。靜置一段時間後(系統發生GC,回收內存),如圖1-19。


圖1-19.優化後,靜置時內存使用情況

靜置後與圖1-2相比,可以看出,兩者內存差不多都穩定在9M左右,但優化後內存抖動情況明顯減少。
說明:可能鑒於測試手段不足,不夠嚴謹,測試結果存在偏差在所難免,但是,還是可以看出優化後的效果還是不錯的。

總結

內存洩露,會導致應用在使用過程中,內存不斷攀升,導致內存不足,應用不流暢,卡頓,甚至導致發生OOM,程序崩潰。所以,在開發過程中,應該避免書寫一些會致使內存洩露的代碼。這裡總結一些常見的內存洩露情景,有則改之無則加勉。

1、 Handler。在使用Handler時應該通過傳入一個runnable,來處理消息,然後在適當的時機移除該runnable,比如Activity onDestory時。
2、 網絡請求異步回調。同Handler一樣,應該在適當的時機移除異步回調操作,比如Activity onDestory時。
3、 匿名內部類。由於匿名內部類會隱式持有當前類的引用,所以應盡可能聲明為static
4、 盡可能少使用靜態成員變量。之前項目的一個bug就是這個情況引起的,當時的代碼是這樣的。
\
5、 在傳遞Context時,需要特別注意,如果可以的話,盡量使用Application Context。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved