天天看點

應用性能監控:使用Android Profiler和友盟+U-APM解決Android卡頓問題

本⽂出⾃于“ 「2021 友盟+ 移動應⽤性能挑戰賽」 ” 中的參賽作品, 該⽂章表述了作者如何借助 友盟+ U-APM

⼯具進⾏了性能優化。

作為⼀款倒計時⽇曆 APP, 我們需要對每個⽇期實時顯示倒計時并精确到秒。但是我們的 app 在滑動重新整理資料時,會出現卡頓。卡頓在很⼤程度上取決于裝置的 CPU 和其他消耗 CPU 時間的程序。于是我們嘗試使⽤了

友盟 + U-APM 記憶體分析對 APP 進⾏分析:
應用性能監控:使用Android Profiler和友盟+U-APM解決Android卡頓問題

通過觀察記憶體的分布, ⼤部分程式的運⾏都處于可預測的範圍内, 我們需要更加細粒度地進⾏測 試。

啟動 Android Profiler Tool Window , 打開CPU Profiler并選擇正确的時間線。連接配接我們的測試 裝置并再次進⾏滑動重新整理, 可以看到Profile線程被添加到應⽤程序并消耗了額外的 CPU 時間。 看看 Logcat:

I/Choreographer: Skipped 147 frames! The application may be doing too much

檢視 CPU Profiler 時間線:

應用性能監控:使用Android Profiler和友盟+U-APM解決Android卡頓問題

圖表上⽅有⼀個視圖表示⽤戶與應⽤程式的互動。所有⽤戶輸⼊事件在此處顯示為紫⾊圓圈。可以看到⼀個圓圈,代表我們為重新整理資料⽽執⾏的滑動。在事件下⽅,有⼀個 CPU 時間線,它以圖形⽅式顯示了與可⽤ CPU 總時間相關的應⽤程式和 其他程序的 CPU 使⽤率。還可以檢視應⽤程式正在使⽤的線程數。

底部可以看到屬于應⽤程序的線程活動時間線 。每個線程處于由顔⾊訓示的三種狀态之⼀: 活動(綠⾊) 、等待(⻩⾊) 或睡眠(灰⾊) 。

在清單頂部, 可以找到應⽤程式的主線程。在我的裝置 (Nexus 5X) 上,它使⽤的 CPU 時間⼤ 約 5 秒。我們可以記錄⼀個⽅法跟蹤來檢視。

在滑動之前單擊“記錄”按鈕以重新整理操作并在資料重新整理完成後⽴即停⽌記錄⏹:

應用性能監控:使用Android Profiler和友盟+U-APM解決Android卡頓問題

我們将從第⼀個頁籤中顯示的圖表開始分析。橫軸代表時間的流逝。

調⽤者及其被調⽤者 (從上到下)  顯示在垂直軸上。⽅法調⽤也通過顔⾊區分,具體取決于是調 ⽤系統 API、第三⽅ API 還是我們本地函數。每個⽅法調⽤的總時間是⽅法⾃身時間及其被調⽤者時間的總和:

應用性能監控:使用Android Profiler和友盟+U-APM解決Android卡頓問題

從這張圖表中,可以推斷出性能問題出在generateItems⽅法内部。為了證明我們的推斷,我們⼜使⽤了

進⾏了線上測試,測試結果和前⽂相同,下圖是

的卡頓分析測試,圖中展示了主要是哪些⽅法造成了CPU的⾼耗時,導緻了卡頓情況的發⽣:

應用性能監控:使用Android Profiler和友盟+U-APM解決Android卡頓問題

⽕焰圖揭示了哪些⽅法占⽤了寶貴的 CPU 時間,并聚合了相同的調⽤堆棧:

應用性能監控:使用Android Profiler和友盟+U-APM解決Android卡頓問題

發現了兩個可疑的地⽅, getRemainingTime 整個⽅法執⾏時間達到2 秒以上,LocalDateTime.format 占⽤ CPU 時間 1 秒以上:

應用性能監控:使用Android Profiler和友盟+U-APM解決Android卡頓問題

此時間還包括線程未處于活動狀态的時間段。另外, 可以切換要線上程時間中顯示的計時資訊。最後⼀個頁籤中的圖表顯示按 CPU 時間消耗降序排列的⽅法調⽤清單。該圖表提供詳細的計 時資訊 (以微秒為機關) :

應用性能監控:使用Android Profiler和友盟+U-APM解決Android卡頓問題

從圖表中擷取消耗過多 CPU 時間的⽅法的計時資訊。将它們與調⽤堆棧中的兩個⽅法相關聯:

class  method total time (in ms) percentage of recorded duration

Sample1Activity refreshData 3027   89.28

Sample1Activity generateItems  3025   89.22

Sample1Activity getRemainingTime   1723   50.83

LocalDateTime  format 1003   29.59

可以看到 getRemainingTime 和 LocalDateTime.format 消耗了超過 80% 的時間。為了解決 卡頓問題, 我們需要從這⾥⼊⼿。那麼該怎麼辦? 聰明的讀者可能已經提出了⼏種解決⽅案。 由于我們執⾏了⼤量計算, 是以我們 将隻為少數目前顯示和準備顯示的項⽬調⽤getRemainingTime 和 LocalDateTime.format ⽅法。

為了實作它, 我們需要更新 Item 屬性, 儲存必要的資料以便稍後執⾏格式化:

data class Item(val now: LocalDateTime, val offset: Int)

 這需要在 generateItems 和 bindItem 函數中應⽤以下更改:

private fun generateItems(): List<Item> {

val now = LocalDateTime.now()

return List(1_000) { Item(now, it + 1) }

}

private fun bindItem(holder: ViewHolderBinder<Item>, item: Item) = with(

val date = item.now.plusDays(item.offset.toLong()).toLocalDate().atS

val remainingTime = getRemainingTime(item.now, date)

應用性能監控:使用Android Profiler和友盟+U-APM解決Android卡頓問題

我們内聯了 createItem 函數, 現在所有函數都在 bindItem ⽅法内部。在我們的代碼修改⽣效之後, 重新啟動 CPU Profiler 并記錄⽅法的運⾏情況。

為了檢查我們的優化是否成功, 我們需要檢視 Call Chart :

應用性能監控:使用Android Profiler和友盟+U-APM解決Android卡頓問題

将⿏标移到 generateItems 函數上,會發現現在耗時約為0.3 秒。⽐優化前減少了 13 倍以上的 CPU 時間 。為了確定我們的更改不會對 bindItem ⽅法的耗時 造成負⾯增益,我們切換到⽕焰圖示來檢查bindItem的總耗時。如圖所示,它最多消耗 0.1 秒:

應用性能監控:使用Android Profiler和友盟+U-APM解決Android卡頓問題

此外, 我們可以進⾏滾動測試, 以確定我們的代碼優化不會影響整體應⽤程式的性能, 并且在滾 動過程中加以記錄。測試發現滾動之後不會再出現卡頓和掉幀的情況了。成功!代碼已優化!Android Profiler 和

友盟+U-APM

都是很好的測試⼯具。如果我們追求流暢的⽤戶體驗, 那麼 使⽤這些優秀的 debug ⼯具是⼗分必要的。在本⽂中, 我主要關注性能調優。

但是, 本⽂中未涵蓋的 Android Profiler 的 Memory Profiler 和 Network Profiler,

友盟+U-A PM

的記憶體分析、OOF 異常和記憶體占⽤也同樣值得研究。記錄記憶體配置設定對查找記憶體洩漏有很⼤幫助, 例如代碼中有沒有回收 bitmap。⽆論如何, 使⽤恰當的分析⼯具可以帶來多項優化成果, 期待讀者們的⾃⾏探索。

作者:查宇

繼續閱讀