版權聲明:本文章原創於 RamboPan ,未經允許,請勿轉載。
記錄一次使用 Android Profiler 分析內存
最近手裏開發了一個小應用,功能也不復雜,大致功能是:
-
加載本地的視頻及生成縮略圖,用戶選擇一個點擊按鈕進行觀看,再點擊一次結束觀看。
-
記錄時間差,然後跳轉一個新界面,把時間差動態加載出來。
-
點擊按鈕主動 或者 倒數30秒 後返回主菜單。
也不難,貼一個做完了的效果。
雖然這個應用不大,我們也需要嚴謹是吧,也分析下代碼中有沒有導致內存泄露的情況。
這次做個筆記。
這裏使用 Android Profiler 進行分析,分析內存中的對象是否因爲不合理的寫法,在頁面關閉後未能被回收。
先說說這個小應用,主要分爲 3 個頁面:配置頁面、播放頁面、結果頁面。
主要是把 assets 目錄中的本地文件複製到需要使用的設備上。完成第一次使用配置。
做這種給他人使用的應用,多加一些操作說明方便他人,也給自己節省時間(別人詢問的時間)。
加載視頻以及生成縮略圖,這裏用了 LruCache 做了一層內存緩存(用戶切換視頻時加載更快),也生成了對應圖片文件做了一層硬盤緩存(下一次進入時直接讀取本地文件)。
這個界面就一個倒計時與動態顯示秒數,因爲並沒有要求特別複雜的動態顯示,直接採用間隔時間修改 TextView ,做了一個簡單的任務定時修改 TextView 。返回可以通過點擊按鈕返回,也可以是倒計時到時間返回。
我們開始通過 Android Profiler 啓動這個應用。
然後點擊 Memory 這部分。
先看第一個界面的內存分析。
因爲我點擊複製文件之後,複製了幾個較大的視頻文件。從波浪的圖形中能夠看出內存不斷使用。
每複製完成一個文件後,佔用內存變小再進行下一次增大,結束最後一次複製後和剛開始時內存佔用差不多。
這裏看着沒有問題,我們直接進入第二個界面分析。
才進入的時候,因爲需要生成縮略圖文件以及內存緩存,所以內存佔用迅速增加。
這裏使用 Dunp Java Heap 按鈕,獲取堆信息。
能看到有 PlayActivity 與 PlayActivity$ ,這裏 PlayActivity $ 表明 PlayActivity 的某個內部對象正在引用外部的 PlayActivity 。
從右邊也能看出,有些 view 正在加載圖片,這次是才進入播放界面採集的堆信息,有圖片正在生成與加載肯定是正常的。
先說明下我們需要觀察的幾個參數,見下圖:
- Allocations : 代表總的實例數量。
- Shallow Size : 代表這些實例自身所佔的大小。
- Retained Size : 代表這些實例及其引用所佔的所有大小。
現在對比下上面的圖,我們只進入了一次 ResultActivity ,所以對應 PlayActivity 與 PlayActivity$ 的 Allocations 都是 1 。
後面我們會測試幾次進入,可以再觀察這個值。
我們接着剛纔的說,耐心等上一小會,我們手動 GC 一次進行清理,再獲取堆信息。
一般應用在 GC 之後能發現明顯的內存減少。
這次能看到因爲圖片已經加載完成,所以只留下了一個 PlayActivity 實例。這裏看也是沒有什麼問題的。
(6 個 FrameImageView 是因爲主界面有 6 個場景選擇的 View。)
現在我們點擊觀看視頻,等待一段時間後,結束觀看,此處開始跳轉結果頁面。
第一個上升趨勢是點擊按鈕後視頻開始播放,第二個上升趨勢是跳轉了結果頁面。
然後我們點擊結果頁面的返回按鈕。再過一小段時間,我們點擊 GC 按鈕,再取一次堆信息。
第一個下降趨勢是點擊返回時,部分內存直接收回。第二個下降趨勢是點擊 GC 後再次釋放的內存,
從圖裏能看見,除了 ResultActivity 沒有被回收,還有一個 ResultActivity $ 1,我們點擊之後能看到有一個 mOnClickListener in View$Listener ,那麼這個代碼對應在哪呢 ?
對應在屏幕中央的返回按鈕,我使用了一個匿名的 OnClickListener 。
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
看了 ResultActivity$1 ,我們再來看 ResultActivity 這個實例,點開之後發現都是資源有關的引用。
從這來看,也沒有什麼大的問題,回收肯定會回收,只是時間會等得更久一點。
稍等一會再回來,再次點擊 GC 按鈕,只剩 PlayActivity,也符合之前的判斷。
這次測試是從結果頁面返回播放界面,等待了一會之後點擊 GC 再進行堆信息獲取。
那麼如果點擊返回之後立刻獲取堆信息會怎麼樣 ? 現在來試一試。
怎麼突然多了這麼多 ResultActivity$ !!!
既然出現了這麼多,肯定是有原因的。點開 ResultActivity$2 發現是倒數計時的 View 以及不斷倒計時的 String 。
因爲需要不斷更新 TextView 的文字,肯定需要一個定時的任務或者線程來執行。我這是把 TextView 和線程封裝了一下。
因爲之前已經想到了可能會提前結束,所以在 onDestroy() 方法中加入了對任務的停止,
當然放在點擊按鈕時處理也是可以的。
@Override
protected void onDestroy() {
if (mCountDownTask != null) mCountDownTask.stopTask();
super.onDestroy();
}
所以此處產生了很多 String 對象,也算正常。再點開 ResultActivity$3。
能看到有個 val$alpha ,還有剛剛的 View ,因爲這個 View 結束時會閃動一下,我這是通過修改透明度實現的,所以此處存在很多透明度值也是正常的。
還有個 ResultActivity$1 和之前是一樣的,那個匿名 OnClickListener。
此時我們再點擊 GC 發現因爲倒計時 View 產生的多個對 ResultActivity 的引用已經不在了。
剩下兩個 ResultActivity$ 仍然是之前分析的匿名 OnClickListener 與 Resultactivity 中的一些資源引用。
再次等待一會後 GC ,能看到 ResultActivity 相關的都已經回收了。
我們測試了單次(觀看視頻後跳轉結果再退回主界面) ,現在我們來多測試幾次,再獲取一次堆信息。
能看到 ResultAcitivity 相關的都多了很多,至少都是 4+,點開其中一個發現都是重複的,說明確實是因爲幾次使用增加了相關的對象,然後我們耐心等待後再 GC 一次,發現 ResultActivity 還剩一個,後面就沒再截圖了。
從這些分析暫時看出代碼中沒有內存泄露的跡象。
如果有不妥之處或不同意見,煩請留言,互相提高。
Android Profiler 參考資料:
https://developer.android.com/studio/profile/android-profiler?hl=zh-cn