基於Xamarin.Android的應用程式啟動效能優化

效能優化實踐者發表於2021-11-03

背景

隨著移動應⽤程式開發越來越流⾏,越來越多的應⽤程式浮現於市場。但是,開發移動應⽤程式並不是⼀個簡單的過程,需要花費⼤量時間,尤其是如果想要⼀個可跨Apple、 Android 和 Windows 運⾏的可擴充套件移動應⽤程式。

然⽽,糟糕的效能可能會極⼤地損害⽤戶體驗。⽤戶在任何時候都不希望看到10秒以上的啟動畫⾯。如果等待時間過⻓\他們可能會感到⽣⽓、放棄購物、減少停留時間或完全解除安裝應⽤程式。

隨著開發平臺的普及,我們需要正確的⼯具和⽅法來滿⾜不斷增⻓的需求。

Xamarin就是這樣⼀種框架,它⽀持在 Android、iOS 和 Windows 平臺上共享單個程式碼庫

所以,我們將在 Xamarin.Android 應⽤程式中測試效能, 就像在 Android Studio 中使⽤ Java 開發⼀樣, 我們可以使⽤c#對效能進⾏測試, 從⽽優化啟動時間。

測試總啟動時間

⾸先測試程式在不同裝置的啟動時間, 此處⽤到的⼯具是友盟+推出的U-APM。 從圖中可以看出應⽤程式在啟動時間上還存在著⼀定的優化空間。

在 Android 上,ActivityManager系統程式會顯示⼀條“初始顯示時間”⽇志訊息, 可以更好地瞭解整體啟動時間。在命令⾏使⽤adb logcat快速檢視 Android 裝置⽇志。 或者使⽤Visual Studio中的Android 除錯⽇志。

在 Windows 上,運⾏以下powershell

> adb logcat -d | Select-String Displayed

輸出:

ActivityTaskManager: Displayed com.lgq.wood.expiramentation/.MainActivity:

上述⽇志訊息是在 x86 Android 模擬器上從 Visual Studio 除錯應⽤程式時捕獲的。 啟動/連線偵錯程式會產⽣⼀些額外的開銷,並且缺少Debug編譯時的優化。
如果我們簡單地切換到Release配置並再次部署和運⾏應⽤程式:

ActivityTaskManager: Displayed com.lgq.wood.expiramentation/.MainActivity:

如果我們在 Pixel 3 XL 裝置上測試應⽤程式:

ActivityTaskManager: Displayed com.lgq.wood.expiramentation/.MainActivity:

因為我們最終⽬標是提⾼移動應⽤程式的效能, 那麼第⼀步應該是實際測試卡頓函式的具體位置。 如果盲⽬地進⾏程式碼更改,最終可能會和我們推測的結果產⽣很⼤的分歧, 如果⼀些複雜的效能改進甚⾄會損害程式碼庫的可維護性。這個過程應該是:測試,做出修改,再次測試,並且重複以上步驟。

採⽤U-APM測得卡頓位置主要出現於:

com.lgq.wood.expiramentation.apache.http.impl.exec.readRawTextFile

診斷問題

好, ⽬前應⽤程式由於readRawTextFile很慢。 現在我該怎麼辦?
⾸先我們需要對以下⼏個元件有⼀個系統性的瞭解

安卓ART

Android 運⾏時 (ART) 是 Android 上的應⽤程式和系統服務使⽤的託管運⾏時。 ART 作為運⾏時執⾏ Dalvik 可執⾏⽂件 (.dex ⽂件 - D alvik EX可執⾏⽂件) , 這是⼀種⽤於儲存 Dalvik 位元組碼的緊湊格式。

ART 通過在安裝應⽤程式時將整個應⽤程式編譯為本機程式碼, 引⼊了提前 (AOT) 編譯。 這帶來 了更快的應⽤程式執⾏和改進的記憶體分配。 以及垃圾收集機制、更準確的分析等等。

為了實現這⼀點, ART使⽤dex2oat來建立⼀個ELF(可執⾏和連結格式) 的可執⾏⽂件。缺點是需要額外的時間來編譯。 此外,應⽤程式會佔⽤⼤量磁碟記憶體來儲存已編譯的程式碼。

AOT

Mono 運⾏時提供AOT功能。 Mono 將預編譯程式集以最⼩化 JIT 時間並減少記憶體使⽤ 。Mono可以在⽀持它的平臺(如 Android)上⽣成 ELF .so ⽂件。 然後它在原始程式集旁邊儲存⼀個預編譯的影像。

Mono.Android.dll → libaot-Mono.Android.dll.so

然後, 這些⽂件可以被 Mono 運⾏時使⽤, 並省略 JIT 開銷

啟動跟蹤

Mono 引⼊了⼀項功能,允許在應⽤程式上使⽤內建的 AOT 分析器來⽣成 AOT 配置⽂件。 分析器進⾏記憶體分析、執⾏時間分析,甚⾄是基於統計的抽樣分析。這會⽣成⼀個 AOT 配置⽂件,當使⽤帶有配置⽂件的 Mono 的 AOT 功能時,該配置⽂件可⽤於優化應⽤程式。

啟動跟蹤可⽤於Visual Studio 2019 版本 16.2或Visual Studio for Mac 2019 版本 8.2。

可以通過編輯 Android 項⽬的 .csproj ⽂件並在 Release <PropertyGroup> 中新增以下屬性來 開始使⽤啟動跟蹤:

<PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Release|Any

也可以在項⽬設定的Android 選項中進⾏設定,Mono 的 AOT 編譯器啟⽤使⽤會預設配置⽂件 的啟動跟蹤, 並在部署時加快 Android 應⽤程式的啟動時間。

實際分析

我們需要實際分析我們的程式碼並需要改進。切換回Debug配置, 並通過運⾏以下命令啟⽤Mono 分析器:

$ adb shell setprop debug.mono.profile log:calls

adb shell在 Android 裝置或模擬器上運⾏單個 shell 命令。 setprop設定Android系統屬性, 類似於其他平臺上的環境變數。

然後只需強制退出並重新啟動應⽤程式。 下次啟動時, Mono 會在 Android 應⽤程式的本地⽬ 錄中儲存⼀個⽂件。 profile.mlpd

注意這⾥存在⼀個問題,該⽂件只能由應⽤程式本身訪問,因此我們必須使⽤命令來定位⽂件: run-as

$ adb shell run-as com.lgq.wood.expiramentation ls -l files/.__override__
-rw-rw-r-- 1 u0_a411 u0_a411 515303 2020-07-27 09:29 profile.mlpd

為了從裝置上獲取⽂件, 我使⽤了⼀個已知的可寫⽬錄, 例如: /sdcard/Download/

$ adb shell run-as com.lgq.wood.expiramentation cp files/.__override__/prof

複製⽂件後,您可以使⽤adb pull將⽂件獲取到您的臺式計算機:profile.mlpd

$ adb pull /sdcard/Download/profile.mlpd
/sdcard/Download/profile.mlpd: 1 file pulled, 0 skipped. 162.7 MB/s (515303

profile.mlpd是⼀個⼆進位制⽂件

Windows ⽤戶需要在⽤於 Linux 的 Windows ⼦系統中安裝 Mono才能運⾏。

有了上⾯的⼀系列程式碼, 就會出現⼀些有趣的數字。

解決方案

我們通過前⽂的調⽤, 可以發現以下⼏個函式可能需要相當⻓的時間:

還可以看到記憶體分配, 例如:

請注意,如果您需要檢視這些分配來⾃哪些⽅法,您可以傳遞到。--tracesmprof-report

我們做出了多種嘗試,也都收到了⼀定成效。但是我們最意想不到的是,下⾯這個簡單的改動。 我們嘗試將string直接從stream中讀取,⽽不是使⽤響應的內容建立,然後使⽤新的System.Text.Json庫來進⾏更⾼效的 JSON 解析:

// At the top
using System.Text.Json;
//...
async Task<Response> GetSlides()
{
var response = await httpClient.GetAsync("https://httpbin.org/json");
response.EnsureSuccessStatusCode();
using (var stream = await response.Content.ReadAsStreamAsync())
{
return await JsonSerializer.DeserializeAsync<Response>(stream);
}
}

檢視⽅法調⽤的差異, 我們可以看到⼀個明顯的時間優化:

這⼀點, 和我們在U-APM中測試得到的瓶頸函式相吻合,瓶頸確實是處在readRawTextFile 函式中,我們嘗試了以下⼏種⽅法,也⼀定程度上緩解了啟動問題但收益並沒有U-APM中的 readRawTextFile 那麼⼤。在此列出,僅供參考:

  1. 我們可以快取 Web 請求的結果
  2. 我們可以從磁碟上的⽂件載入之前的調⽤結果, ⽐如設定24 ⼩時內有效。
  3. 由於調⽤不是互相依賴, 我們可以同時進⾏非同步調⽤
  4. 在伺服器端, 我們可以進⾏⼀個新的 API 調⽤, 在⼀個請求中返回所有調⽤的資料

優化效能很難,⽅向也很多。關於程式碼慢的定位部分,改動後可能會發現這⼀部分根本不會產⽣ 效果, 對程式碼產⽣影響的最佳⽅法是測試、測試,然後再次測試。改變後再次測試。⽽通過測試去提升效能,往往能針對問題做預先準備。也往往能更核⼼地提升核⼼效能瓶頸,從⽽帶來⽅⽅⾯⾯的全⽅位提升。

相關文章