Last reviewed against the current Android and Play Console documentation. Tool labels and metric definitions change; where this article and the official pages disagree, the official pages win.
A slow launch is the most expensive performance problem an app can have, because every user pays it on every cold start — and most of them never tell you. They just leave.
What makes it difficult is that "the app is slow to open" is not a diagnosis. It is a symptom with several possible owners: your `Application` object, a content provider added by an SDK, a database opened on the main thread, or a first frame that waits on a network response.
This guide covers the measurement half of Android app performance testing — the numbers, where they come from, how to make them reproducible — then the diagnostic half: finding which component delays the first frame, and proving a change helped.
Quick answer: what to measure and what to ignore
The short version
- Optimize for the cold start. Google recommends assuming a cold start; improvements there also help warm and hot starts.
- Two metrics, two questions. TTID is when the first frame appears. TTFD is when the app is actually usable.
- TTID is automatic; TTFD is not. You only get a TTFD value if your app calls
reportFullyDrawn(). - Play Console measures TTID. Slow cold start is 5 seconds or more, warm 2 seconds or more, hot 1 second or more.
- Startup is not a core vital. It appears in Android vitals, but Google publishes no bad-behaviour threshold for it.
- One launch proves nothing. Repeat the same start state, on the same build type, several times.
What app startup time actually measures
Google defines two metrics, and confusing them is the most common measurement error:
- Time to initial display (TTID) — the time until the app produces its first frame, including process initialisation on a cold start, activity creation, UI initialisation and the first draw. The framework reports it automatically for every app.
- Time to full display (TTFD) — the time until the app is genuinely usable, including content loaded after that first frame. The system cannot infer this, so the app has to report it.
A fast first frame with a slow content load looks excellent in a TTID-only measurement and feels broken to the user. Both matter: TTID tells the user something is happening, TTFD is when they can do something.
TTID versus TTFD
-
TTID — first frame visible Measured automatically
-
TTFD — app is usable Requires reportFullyDrawn
If your app never calls reportFullyDrawn(), no TTFD is reported at all — which is why some teams believe a slow-loading screen is "fine".
Cold, warm and hot starts
App launch happens in one of three states, and the state changes how much work is required.
| State | What the system does | What your app does | Should you optimise for it? |
|---|---|---|---|
| Cold | Creates the app process and shows a blank starting window. | Creates the app object, starts the main thread, creates and initialises the activity, draws the first frame. | Yes — this is the baseline Google recommends. |
| Warm | The process may still exist; the activity is recreated. | Runs onCreate again and can reuse saved instance state. |
Improves as a side effect of cold-start work. |
| Hot | Brings the existing activity back to the foreground. | Skips re-initialisation when the UI is still resident, unless memory trimming removed it. | Usually the fastest; not a substitute for cold-start testing. |
From tap to first frame on a cold start
- The system launches the app and shows a blank starting window immediately.
- The system creates the app process. Nothing of yours has run yet.
- The app object is created. Your
Application.onCreateruns here, if you override it. - The main thread starts and creates the host activity.
- The activity initialises — constructors,
Activity.onCreate, and in Compose the initial composition. - The first frame is drawn, the starting window is replaced, and TTID is recorded.
Steps 3 and 5 are where most avoidable delay lives — which is why Google's guidance targets app and activity initialisation, not the system steps.
How to measure startup yourself
Filter Logcat for Displayed. The system server writes this line, not your app, so you may need to disable Logcat filters to see it:
ActivityManager: Displayed com.example.app/.MainActivity: +3s534msA total figure appears when the first drawn activity differs from the one that started the process. Google notes that Displayed excludes resources not referenced in the initial composition — asynchronously loaded images or data do not delay it.
For a scriptable measurement, force-stop and launch with the activity manager:
adb shell am start -S -W com.example.app/.MainActivity \
-c android.intent.category.LAUNCHER \
-a android.intent.action.MAINThe output reports ThisTime, TotalTime and WaitTime. The -S flag is what makes the run a cold start rather than a warm one.
For TTFD, call reportFullyDrawn() once the screen is genuinely usable. If the screen waits on background work, register a FullyDrawnReporter and release it when that work finishes. In Compose, ReportDrawn, ReportDrawnWhen and ReportDrawnAfter cover the immediate, predicate and suspending cases.
reportFullyDrawn() before the first frame. Google states that if you call it before the system detects TTID, both values are reported as the same number — a flattering TTFD that measured nothing.
What Play Console reports, and what it does not
Android vitals has a Start-up time page reporting slow starts from cold, warm and hot states. The documented thresholds are 5 seconds or more cold, 2 seconds or more warm and 1 second or more hot, expressed as the percentage of impacted sessions plus 90th and 99th percentile figures.
Three collection details decide whether a reading is comparable: times are recorded only when a user triggers an activity; if an app starts several times a day from the same state, the day's maximum is recorded; and measurement ends at the first frame, so an app that opens with a splash screen records the time to display the splash.
Two limits matter. Startup is reported in Android vitals but is not one of Google's core vitals — the core list is user-perceived crash rate, ANR rate, excessive partial wake locks, and memory usage — and no bad-behaviour threshold is published for startup. And the pre-launch report treats slow startup as a warning, so a clean report is not evidence of a fast launch.
For what else Vitals measures and how the rates are computed, see Android Vitals explained.
Reading a trace to find the delay
A number tells you there is a problem. A trace tells you where. Three tools answer different questions.
- Perfetto — open a system trace and find the Android App Startups derived-metric row. Selecting the slice shows the duration; expanding the app row and zooming into the main thread shows what ran during it. Shortcuts:
mselects,w/szoom,a/dpan. - The Profiler's system trace view — CPU usage, thread activity, CPU cores, and a Display section showing how long each frame took to render on the main thread and
RenderThread. - Inline tracing — wrap suspicious sections in
Tracecalls so they appear as named slices. This catches work with no framework method name to search for.
In Compose, look for main-thread slices beginning with Choreographer#doFrame, then at Compose:recompose, Compose:layout and Compose:draw. They separate composition from measure/place and draw — usually enough to tell whether the first frame is slow because of your state graph or layout depth. This is also where you separate startup delay from later slowness: startup ends at the first frame, so a responsive screen with late content is a TTFD problem, not initialisation.
Common causes of slow startup
Google's guidance names two clusters, and they map onto where the delay appears in a trace.
Heavy app initialisation
- Work in
Application.onCreatethat does not need to happen yet — initialising state for screens the launch intent may never show. - Content providers used as an initialisation mechanism. Each is expensive to instantiate, and Android initialises them in an undetermined order.
- Disk I/O, deserialisation and GC pressure running during initialisation and blocking it.
Heavy activity initialisation
- A large or deeply nested UI, or composables doing heavy calculation on the main thread.
- Blocking disk or network I/O before the first draw.
- Decoding bitmaps or rasterising
VectorDrawables at first use, and SDKs that self-initialise through a merged-manifest provider you never wrote.
That last one is worth checking explicitly: an SDK can add a provider with no code change on your side, which is why startup regressions sometimes follow a dependency bump that "only changed analytics".
Fixing the delay
Google's recommended fix for heavy app initialisation is lazy initialisation: create objects when they are first needed rather than eagerly. A singleton, or a dependency injection framework such as Hilt that constructs dependencies on first injection, moves that work out of the critical path. Where content providers are the initialisation mechanism, the App Startup library replaces many of them with a single InitializationProvider and declares ordering through each initialiser's dependencies().
For the first frame, draw with the data you already have. Move network work off the main thread, render a loading state rather than blocking on a response, and let images arrive after the frame. If the screen genuinely cannot be usable until data arrives, report TTFD honestly instead of pretending the launch ended at the first frame.
For each SDK that initialises at launch, ask what breaks if it waits. Analytics, crash reporting and remote config usually tolerate deferral; a payment or auth SDK on the launch path may not. Where an SDK documents an opt-out from auto-init, use it rather than relying on doing nothing.
Baseline Profiles
A Baseline Profile ships ahead-of-time compilation information for the code paths that matter, so ART does not have to interpret and JIT them on the first run. Google's stated figure is that Baseline Profiles improve code execution speed by about 30% from the first launch.
That is a claim about code execution, not a promise about your launch time. It is also the one fix here that targets the first run after install specifically — the moment a user opens the app once and judges it. Macrobenchmark can generate Baseline Profiles as well as measure startup.
Validating that the change helped
Startup measurements are easy to fool. Google distinguishes a benchmark build type that mirrors release — non-debuggable and minified — because debug builds distort timing.
- Fix the build type. Measure a release-like build, not a debug build.
- Fix the start state. Force-stop before each run so every measurement is cold.
- Fix the device. Same model, version and power state. An emulator is for iteration, not the final word.
- Run it several times. Discard the first run; report a range or a percentile.
- Measure both metrics. TTID and TTFD can move in opposite directions when work shifts into a loading state.
- Use Macrobenchmark in CI. It turns "we think it is faster" into a build-time check.
Then test for correctness, because startup work is unusually good at hiding bugs. Deferred initialisation can leave a null singleton on a path you did not exercise; an asynchronous first frame can show an empty state where a value used to be. Exercise deep links, notification launches and restored processes — not just a tap on the launcher icon. Expect the Console trend to lag too, since a release reaches users gradually.
Troubleshooting by symptom
| Symptom | Likely cause | Where to look |
|---|---|---|
| Slow only on the first launch after install | Interpretation and JIT compilation of startup code paths. | Compare first run with a later cold start; consider a Baseline Profile. |
| Slow on every launch, including warm | Work in Activity.onCreate or the initial composition. |
Main-thread slices; Compose:recompose versus Compose:layout. |
| First frame is fast, but the screen is useless | TTFD problem: content arrives after the first frame. | Add reportFullyDrawn reporting, then trace the data path. |
| Regression after a dependency update | An SDK added a manifest provider or auto-init hook. | Inspect the merged manifest and startup providers. |
| Intermittent stalls, worse on some devices | Main-thread I/O, GC pressure, or low-memory conditions. | StrictMode for disk and network misuse; memory profiling on a low-RAM device. |
| Fast on the emulator, slow in the field | Emulator timings are not representative. | Re-measure on physical devices across your supported range. |
Startup testing checklist
Left column: measure honestly. Right column: prove the fix did not break anything.
Measure
- Build a release-like, non-debuggable variant.
- Force-stop before every cold-start run.
- Record TTID and, if implemented, TTFD.
- Repeat several times; report a range, not one number.
- Check Play Console startup sessions by state and percentile.
- Read the pre-launch report's slow-startup warnings.
Verify
- Trace the fix and confirm the slice actually shrank.
- Exercise deep links, notification launches and process restoration.
- Confirm deferred initialisation cannot produce a null dependency.
- Check that the loading state matters more than the missing content.
- Add a Macrobenchmark startup test to CI.
- Re-check device coverage with a compatibility matrix.
For the device side of that last item, see compatibility testing across devices and the pre-launch report guide.
Frequently asked questions
What is a cold start in Android?
The app starting from scratch: the system creates the process, then the app creates its application object, main thread, activity and first frame. It happens on first launch after boot and after the system kills the process.
How can I measure Android app startup time?
For a quick check, read the Displayed line in Logcat, or run adb shell am start -S -W and read ThisTime and TotalTime. For repeatable numbers, use Macrobenchmark; for production data, use the Start-up time page in Android vitals.
What is the difference between TTID and TTFD?
TTID is when the first frame appears and is measured automatically. TTFD is when the app is actually usable, including asynchronously loaded content, and is only reported if your app calls reportFullyDrawn().
Can SDK initialization slow down app startup?
Yes. SDKs commonly initialise through content providers or an application-level init call, and content providers are expensive to instantiate in an undetermined order. Google documents the App Startup library as a way to consolidate that work and control ordering.
Should network requests block the first screen?
No. Google's guidance is to avoid blocking screen drawing on disk or network I/O. Draw a usable first frame, load remote content afterwards, and report TTFD when the screen is interactive.
What are Android Vitals startup metrics?
The Start-up time page reports slow starts by state: 5 seconds or more cold, 2 seconds or more warm, 1 second or more hot, with impacted sessions and 90th/99th percentiles. Startup is not one of Google's core vitals.
When should I use Baseline Profiles?
When first-run performance matters — especially for new installs, where code has not been compiled ahead of time. Google states Baseline Profiles improve code execution speed by about 30% from the first launch by avoiding interpretation and JIT steps.
Conclusion: measure first, then change one thing
Android app performance testing for startup comes down to a small loop: establish a number you trust, find the slice responsible, change one thing, re-measure, then test the paths your change could have broken. Measuring once, on a debug build, with the app already warm, tells you nothing — and changing Application.onCreate, adding a Baseline Profile and deferring three SDKs at once tells you nothing either.
Taken one step at a time, startup work compounds. The number you want is not a good result on one device; it is the 90th percentile of your cold starts, on the devices your users actually own, staying where you put it.
Official sources
- Android Developers — App startup time
- Android Developers — Performance overview
- Android Studio — Profiler overview
- Android Studio — CPU Profiler and system trace
- Android Developers — Android vitals
- Play Console Help — Monitor your app's technical quality with Android vitals
- Android Developers — Baseline Profiles overview
- Android Developers — Macrobenchmark overview
- Android Developers — App Startup library
- Play Console Help — Understand your pre-launch report