CN

How to Calculate App Startup Time in Android?

Word count: 2kReading time: 12 min
2015/12/31
loading

Someone recently asked on Zhihu: “How to calculate APK startup time?”

“How can I use Python or direct ADB commands to calculate APK startup time? I want to measure the time from clicking the icon to the APK being fully started. For example, in a game, this would be from the icon tap to entering the login screen. Existing methods like adb shell am start -W provide ThisTime and TotalTime, but I’m unsure of the difference and they don’t seem to match visual reality.”

My colleague Guo Qifa and I answered different parts of the question. With his permission, I put our answers together here: partly so I could find them again, and partly to make them easier for other developers to find outside Zhihu.

Version note (reviewed 2026-10-13): The original three-column output is explained against AOSP android-5.1.1_r1. The command in android-4.4.4_r2 did not print WaitTime, while android-16.0.0_r1 no longer prints ThisTime. Interpret results using the OS version, launch state, and measurement boundaries. The historical example remains, with TTID, TTFD, and current guidance added; its numbers are not a new device measurement.

1. Startup Scenarios

Startup can be measured once its boundaries are defined: a system launch request to initial display differs from an icon tap to an interactive login screen. Both games and ordinary apps can keep loading after the first frame, so the distinction is not simply “game versus app.”

1.1 App Startup

The launcher normally resolves a MAIN/LAUNCHER entry, which need not be named MainActivity and may route through an existing task. Creating a new instance involves onCreate/onStart/onResume; reusing an instance need not repeat every callback.

Returning from onResume() does not guarantee drawing or presentation. The View path may perform later traversals with measurement, layout, and drawing as needed; there is no fixed requirement for two traversals. The first frame may contain only a background, placeholder, or loading state, not all usable business content.

Distinguish time to initial display (TTID) from time to full display (TTFD), which depends on the app declaring its usable state. Timing lifecycle callbacks alone does not cover the whole launch path.

adb shell am start -W issues a shell launch request. The option is uppercase -W; it does not automatically include the physical tap, launcher processing, or all host-side adb startup overhead.

1.2 Cold Startup: Target Process Not Running

A cold start means the target process is not running, not necessarily the first launch after installation or reboot. Reentering after process death can be cold again. Installation/login state, data caches, and code compilation state are separate variables to record.

The original output is retained below, in milliseconds. Its 355/365 values are historical, not targets or new device results:

1
2
3
4
5
6
7
8
➜ adb shell am start -W com.media.painter/com.media.painter.PainterMainActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.media.painter/.PainterMainActivity }
Status: ok
Activity: com.media.painter/.PainterMainActivity
ThisTime: 355
TotalTime: 355
WaitTime: 365
Complete

There is no universal rule to choose WaitTime, and the three numbers must not be added. They have different boundaries in android-5.1.1_r1.

The waiting branch in frameworks/base/cmds/am/src/com/android/commands/am/Am.java calls AMS startActivityAndWait(). The unreadable first source image is replaced by the relevant timing excerpt:

1
2
3
4
5
6
7
8
9
10
final long startTime = SystemClock.uptimeMillis();
if (mWaitOption) {
result = mAm.startActivityAndWait(null, null, intent, mimeType,
null, null, 0, mStartFlags, profilerInfo, null, mUserId);
res = result.result;
} else {
res = mAm.startActivityAsUser(null, null, intent, mimeType,
null, null, 0, mStartFlags, profilerInfo, null, mUserId);
}
final long endTime = SystemClock.uptimeMillis();

The command later prints WaitTime = endTime - startTime. This is the command’s internal waiting interval, covering the launch call and its return wait, not time since a physical tap. It usually covers an outer interval, but its difference from the other values is not exclusively the previous app’s pause time.

ActivityRecord.reportLaunchTimeLocked(curTime) computes the other two values:

1
2
3
4
final ActivityStack stack = task.stack;
final long thisTime = curTime - displayStartTime;
final long totalTime = stack.mLaunchStartTime != 0
? (curTime - stack.mLaunchStartTime) : thisTime;

Historical ActivityRecord timing formula

  • curTime: SystemClock.uptimeMillis() supplied here by windowsDrawn().
  • displayStartTime: the current Activity’s display-launch accounting start, maintained through paths such as ActivityStack.setLaunchTime(), not necessarily the start of onCreate().
  • stack.mLaunchStartTime: the beginning of this stack’s still-open launch accounting interval. If zero, TotalTime falls back to ThisTime.

The values match when a single Activity has identical start points. They can differ when an earlier Activity has not reported drawing complete and a later Activity is part of the same still-open interval. This is not a universal first-to-last-Activity stopwatch: a completed report clears mLaunchStartTime, and an intermediate visible splash Activity may already have ended an earlier interval.

Historical launch accounting intervals

The old diagram misspells TotalTime as TotolTime. Its aligned endpoints do not mean that the window-drawn report and command return happen simultaneously. The numbered segments are a schematic of one old launch path:

  1. ① can include task selection, pausing the foreground Activity, and scheduling/preparation.
  2. ② can include process creation, an earlier Activity’s initialization, and a transition.
  3. ③ extends beyond onCreate/onResume to the window-drawn report; it does not guarantee that business content such as a login screen is ready.

ThisTime and TotalTime overlap, and WaitTime is not another stage to add. TotalTime can describe the application’s window-launch interval; WaitTime describes the command’s wait. Neither directly measures tap-to-business-readiness end to end.

In this tag, am_activity_launch_time and the Displayed log are written by the same reportLaunchTimeLocked() method. They can correspond to the same successful wait result, but field names and meanings should not be forced across separate launches, timeout paths, or OS versions. Bringing an existing task forward also has paths returning 0 or waiting for visibility, rather than using the first-draw formula above.

The end point is the report in ActivityRecord.windowsDrawn(). WMS calculates drawn and visible states separately and excludes the StartingWindow from application-window drawing counts. This is a system window-drawing signal, not an optical timestamp proving visible pixels, and not proof that asynchronous content is fully loaded.

1.3 Warm and Hot Startup

Reusing the process while recreating an Activity is normally a warm start. Bringing an existing Activity forward while its process and instance remain alive is the usual hot-start case. “Opening it a second time” or “pressing Back earlier” is not sufficient classification: Back behavior, task state, and OS version affect whether the instance survives.

Android 16 WaitResult also distinguishes internal RELAUNCH (process reused but an existing Activity must be destroyed and recreated, for example after an unhandled configuration change) and UNKNOWN states. Do not silently mix all results into one cold/warm/hot sample group. Define the preparation steps and record the observed state.

2. Game Startup

Command-line tools can launch a game Activity and observe system window-startup accounting. They cannot automatically know when game content becomes usable. Ordinary apps with network content, session restoration, or database loading have the same limitation.

2.1 System Phase

A game commonly has an Activity hosting its window. System traces can cover process creation, the Activity, and initial window drawing. A shell-originated launch should not be labelled as starting at the user’s launcher tap.

2.2 Game Phase

After the Activity appears, the game may load assets, update data, read configuration, or initialize its engine. The first Surface/window frame can be a loading screen. The game must define a verifiable condition for an interactive login screen or menu.

Logs are not the only option. Combine monotonic-clock markers, Trace events, and reportFullyDrawn() when the app reaches its fully usable state. A game engine must distinguish resource readiness from rendered, usable content; download completion alone is insufficient. Use the same clock basis at both ends. Timing from onCreate cannot recover earlier tap or process-creation time.

3. Conclusion

What fascinates me about computers is their precision. A launch may take a different amount of time on each run, but each measurement records that particular run.

Companies have different priorities. When I wrote this, ROM makers wanted their built-in apps to open quickly, while many Internet apps put ads or feature introductions before the main screen. I can understand the different needs. As a user, though, I would rather reach the main screen sooner. Startup time deserves a place among an app’s performance metrics.

As they say:

“In the world of Kung Fu, speed defines the winner!”

4. 2026 Update: Making Measurements Comparable

Fix the launch entry and state first. For a local rough check, replace the package/component below with the app’s real launch entry. -S force-stops the target package before starting its Activity. It does not reset installation, login, or disk caches to a first-install state, nor does it simulate a physical tap.

1
2
3
4
adb shell am start -W -S \
-n com.example.app/.MainActivity \
-a android.intent.action.MAIN \
-c android.intent.category.LAUNCHER

Check Status, the returned Activity, timeouts, and messages about bringing an existing task forward before interpreting timing. The checked Android 16 ActivityManagerShellCommand prints LaunchState, nonnegative TotalTime, and WaitTime, but no ThisTime. Do not replace a missing column with zero. Its force-stop and following wait occur before startTime; the host-side wall-clock duration of the entire adb command is not WaitTime either.

Use Macrobenchmark to compare versions. In a separate benchmark module, use StartupTimingMetric (since 1.1.0) and explicitly select StartupMode.COLD/WARM/HOT, CompilationMode, data/login state, and repetition count. Use a release-like non-debuggable target, a physical device, and consistent conditions; configure profileable as required by the version in use. Inspect the median and distribution plus the generated traces, not just one fastest run. Do not mix uncompiled and Baseline-Profile-precompiled results.

Keep the metrics separate:

  • timeToInitialDisplayMs measures from the system receiving the launch Intent to the destination Activity’s first-frame rendering. It does not mean all business content is ready.
  • timeToFullDisplayMs depends on the fully-drawn signal and ends when the frame containing or following that signal finishes rendering. Missing app reporting, an early-ended recording, or unavailability before API 29 must not become a zero or a passing result. The benchmark measureBlock must wait for the real ready condition to observe asynchronous content.
  • To measure a physical tap through an interactive login screen, define the input event, ready condition, and clock, then correlate system/display evidence. Adding command-output columns does not produce that interval.

Start from the official Macrobenchmark examples and configuration guide. Android 12 / API 31 onward supports correlating app, GPU, and SurfaceFlinger work with Perfetto FrameTimeline; a DrawFrame CPU slice ending does not prove presentation. Field metrics from Android Vitals have their own sampling and definitions and should be interpreted separately from application-specific readiness.

Sources

About Me && Blog

Below are my personal details and links. I look forward to connecting and sharing knowledge with fellow developers!

  1. About Me: Includes my WeChat and WeChat group links.
  2. Blog Navigation: A guide to the content on this blog.
  3. Curated Android Performance Articles: A collection of must-read performance optimization articles. Self-nominations/recommendations are welcome!
  4. Android Performance Knowledge Planet: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.

“If you want to go fast, go alone. If you want to go far, go together.”

WeChat QR Code

CATALOG
  1. 1. 1. Startup Scenarios
    1. 1.1. 1.1 App Startup
    2. 1.2. 1.2 Cold Startup: Target Process Not Running
    3. 1.3. 1.3 Warm and Hot Startup
  2. 2. 2. Game Startup
    1. 2.1. 2.1 System Phase
    2. 2.2. 2.2 Game Phase
  3. 3. 3. Conclusion
  4. 4. 4. 2026 Update: Making Measurements Comparable
    1. 4.1. Sources
  5. 5. About Me && Blog