Android App 冷啟動時間檢測

原始文章:https://dev.to/pyricau/android-vitals-when-did-my-app-start-24p4
最近在分析 App 啟動性能,剛好看到這篇文章:Android Vitals – When did my app start? 其中分享了幾個紀錄 App 冷啟時間的方式,有些點也可以幫助理解一下 App 啟動的點。在此翻譯分享一下。
Application.onCreate
最簡單的方式是在 Application create 的時間點紀錄啟動時間,不過這明顯不是一個準確的方法。
class MyApp : Application() {
var applicationOnCreateMs: Long = 0
override fun onCreate() {
super.onCreate()
applicationOnCreateMs = SystemClock.uptimeMillis()
}
}
ContentProvider.onCreate()
比 Application.onCreate 稍微再好一點的方法是透過 ContentProvider.onCreate,這個是比較安全的 library 初始化點,Jetpack 提供的 App Startup 就是透過 content provider 來做各種初始化的管理,因為這個時間點其實是比 Application.onCreate 觸發時間還早。
class StartTimeProvider : ContentProvider() {
var providerOnCreateMs: Long = 0
override fun onCreate(): Boolean {
providerOnCreateMs = SystemClock.uptimeMillis()
return false
}
}
Class 載入時間點
在任何 class 能使用之前都必須透過 class loader 載入,所以我們還可以利用 static 變數來記錄 class 載入的時間點。
class MyApp : Application() {
companion object {
val applicationClassLoadMs = SystemClock.uptimeMillis()
}
}
不過要注意的是在 Andorid P (Api level 28) 之後第一個被載入的 class 是 AppComponentFactory
@RequiresApi(Build.VERSION_CODES.P)
class StartTimeFactory : androidx.core.app.AppComponentFactory() {
companion object {
val factoryClassLoadMs = SystemClock.uptimeMillis()
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example">
<!-- Replace AndroidX appComponentFactory. -->
<application
android:appComponentFactory="com.example.StartTimeFactory"
tools:replace="android:appComponentFactory"
tools:targetApi="p" />
</manifest>
Process 啟動時間
每一個 App 的 Application 在能 bind 之前都要先有一個來自 Zygote 複製出來的 process,在 linux base 的系統中 process 的資訊會記錄在一個系統檔案中:/proc/[pid]/stat。Process 的啟動時間也記錄在這檔案裡。透過以下的 Process utility code 可以讀取 process 啟動時間:
object Processes {
fun readProcessForkRealtimeMillis(): Long {
val myPid = android.os.Process.myPid()
val ticksAtProcessStart = readProcessStartTicks(myPid)
// Min API 21, use reflection before API 21.
// See https://stackoverflow.com/a/42195623/703646
val ticksPerSecond = Os.sysconf(OsConstants._SC_CLK_TCK)
return ticksAtProcessStart * 1000 / ticksPerSecond
}
// Benchmarked (with Jetpack Benchmark) on Pixel 3 running
// Android 10. Median time: 0.13ms
fun readProcessStartTicks(pid: Int): Long {
val path = "/proc/$pid/stat"
val stat = BufferedReader(FileReader(path)).use { reader ->
reader.readLine()
}
val fields = stat.substringAfter(") ")
.split(' ')
return fields[19].toLong()
}
}
雖然我們可以拿到 process 真正的啟動時間,但是以 Android App 冷啟動的分析來說,真正有意義的點是要從 ActivityThread.handleBindApplication() 開始計算,因為以 App 開發的角度,在這之前能做的事情很有限。
另外因為 Android 系統最佳化而使用保留的 process,有可能 App 最後 bind 的 process 是已經 fork 許久的 process。(這個又衍生出了冷啟動的定義究竟是要包含 process create 還是不包含.. )
Bind application time
ActivityThread bind application 時的第一件事情就是設置 process 起始時間:
public class ActivityThread {
private void handleBindApplication(AppBindData data) {
// Note when this process has started.
Process.setStartTimes(
SystemClock.elapsedRealtime(),
SystemClock.uptimeMillis()
);
...
}
}
這麼一來我們就可以直接透過對應的 Process.getStartUptimeMillis() 取得 application bind 的時間點。似乎很棒吧?
在 Android 28 之前的確很棒,以此方式測量的啟動時間中位數大約是 250ms。但在 Android 28+ 上,最長可以到14小時。目前暫時還沒理解原因,猜測是 Android 在 bind application 後將 process 停住,因而導致實際的啟動點更晚了。
結論
總結一下我們目前能相對準確的測量 App 冷啟動的時間點:
- API 25 前:此用 Content Provider class 載入時間
- API 25 – API 28: 透過 Process.getStartUptimeMillis()
- API 28+: 透過 Process.getStartUptimeMillis() 但需要過濾異常數值 (比如超過一分鐘才從 bind application 到 Application.onCreate
Cover images source: https://stacksense.io/krishnan/platforms/on-the-serverless-cold-start-problem/
我是 K,以前幻想著可以做些偉大的事。但發現把一件簡單的事情做到很好就是一件很難又很厲害的事。所以現在正處於一個把簡單的事情做好的狀態。