Application.onCreate()
最简单的方法是捕获调用 Application.onCreate() 的时间。
class MyApp : Application() {
var applicationOnCreateMs: Long = 0
override fun onCreate() {
super.onCreate()
applicationOnCreateMs = SystemClock.uptimeMillis()
}
}
ContentProvider.onCreate()
对于库开发人员来说,一个安全的早期初始化钩子是 ContentProvider.onCreate():
class StartTimeProvider : ContentProvider() {
var providerOnCreateMs: Long = 0
override fun onCreate(): Boolean {
providerOnCreateMs = SystemClock.uptimeMillis()
return false
}
}
ContentProvider.onCreate() 也适用于应用程序开发人员,它在应用程序生命周期中比 Application.onCreate() 更早被调用。
类加载时间
在使用任何类之前,必须先加载它。 我们可以依靠静态初始化器来存储特定类的加载时间。
我们可以跟踪 Application 类的加载时间:
class MyApp : Application() {
companion object {
val applicationClassLoadMs = SystemClock.uptimeMillis()
}
}
我们了解到在 Android P+ 上加载的第一个类是 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>
要在 Android P 之前跟踪启动时间,库开发人员可以依赖提供程序的类加载时间:
class StartTimeProvider : ContentProvider() {
companion object {
val providerClassLoadMs = SystemClock.uptimeMillis()
}
}
类加载顺序通常是 AppComponentFactory > Application > ContentProvider。 如果 AppComponentFactory 在类加载时加载其他类,这可能会改变。
进程分叉时间
我们知道每个应用进程都是从 Zygote 分叉开始的。 在 Linux 和 Android 上,有一个名为 /proc/[pid]/stat 的文件,该文件可读并包含每个进程的统计信息,包括进程启动时间。
让我们查看 /proc/[pid]/stat 部分下的 man proc:
(2) comm %s
The filename of the executable, in parentheses.
This is visible whether or not the executable is
swapped out.
...
(22) starttime %llu
The time the process started after system boot. In
kernels before Linux 2.6, this value was expressed
in jiffies. Since Linux 2.6, the value is expressed
in clock ticks (divide by sysconf(_SC_CLK_TCK)).
/proc/[pid]/stat 是一个包含一行文本的文件,其中每个 stat 由一个空格分隔。 但是,第二个条目是可执行文件的文件名,其中可能包含空格,因此我们必须通过查找第一个 ) 字符跳过它。 一旦我们这样做了,我们可以用空格分割剩余的字符串并选择索引 19 处的第 20 个条目。
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()
}
}
这为我们提供了进程何时分叉的实时信息。 我们可以将其转换为正常运行时间:
val forkRealtime = Processes.readProcessForkRealtimeMillis()
val nowRealtime = SystemClock.elapsedRealtime()
val nowUptime = SystemClock.uptimeMillis()
val elapsedRealtime = nowRealtime - forkRealtime
val forkUptimeMs = nowUptime - elapsedRealtimeMs
这为我们提供了真正的流程开始时间。 然而我们得出结论,应用冷启动监控应该在 ActivityThread.handleBindApplication() 被调用时开始,因为应用开发者对之前花费的时间几乎没有影响。
预分叉
依赖 fork 时间还有另一个缺点:在应用程序开始专门处理 ActivityThread.handleBindApplication() 之前,进程可能会被 fork。 我在一个生产应用中测量了从 fork 到 Application.onCreate() 的时间:虽然中位时间为 350 毫秒,但最长为 4 天,0.5% 的应用启动间隔大于 1 分钟。 这可能是由于某些系统保留了一个预分叉的 zygotes 池来加速应用程序启动。
绑定 application 时间
ActivityThread.handleBindApplication() 做的第一件事就是调用 Process.setStartTimes():
public class ActivityThread {
private void handleBindApplication(AppBindData data) {
// Note when this process has started.
Process.setStartTimes(
SystemClock.elapsedRealtime(),
SystemClock.uptimeMillis()
);
...
}
}
相应的时间戳可通过 Process.getStartUptimeMillis() 获得:
返回启动此进程的 SystemClock#uptimeMillis()。
API 28 以前,我测量了在生产应用程序中从绑定应用程序到 Application.onCreate() 的时间。 虽然中位数时间为 250 毫秒,但在 API 28+ 上,最长为 14 小时,0.05% 的应用程序启动间隔大于 1 分钟。
我还发现了从 AppComponentFactory 的类加载到 Application.onCreate() 的时间的类似问题:在 API 28+ 上,0.1% 的应用程序启动时间超过 1 分钟。
这不可能是由于设备休眠,因为我们只使用 SystemClock.uptimeMillis() 测量时间间隔。 我一直无法弄清楚这里到底发生了什么,看起来有时绑定应用程序启动然后中途停止,而实际的应用程序启动要晚得多。
结论
以下是我们如何在监控冷启动时最准确地测量应用程序启动时间:
API 24 以下:使用内容提供者的类加载时间。
API 24 - API 28:使用 Process.getStartUptimeMillis()。
API 28 及更高版本:使用 Process.getStartUptimeMillis() 但过滤掉奇怪的值(例如超过 1 分钟才能到达 Application.onCreate()),并回退到调用 ContentProvider.onCreate() 的时间。