跪拜 Guibai
← All articles
Frontend · Android

Cutting Android Cold Start from 3s to 800ms with Measurement, Not Magic

By hunterandroid ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Cold-start latency is the first performance signal users feel, and most teams treat it with guesswork. This walkthrough gives a repeatable measurement-first method and identifies the three changes that actually move the needle — Application sync reduction, layout flattening, and theme splash — while calling out popular optimizations that no longer matter on modern minSdk levels.

Summary

Cold start on Android — from process creation through first-frame render — was measured at 3.1 seconds on a production app. The optimization path relied on `adb shell am start -W` as the single source of truth, plus in-code instrumentation at Application and Activity boundaries. Application.onCreate dropped from 1.4s to 380ms by splitting SDK initialization into synchronous, IdleHandler-deferred, and background-thread tiers, pushing non-critical modules past onResume, and removing unnecessary ContentProvider auto-init from third-party SDKs — the provider cleanup alone saved 220ms. The first-screen Activity then became the bottleneck at 900ms. Flattening a deeply nested ConstraintLayout saved 90ms, replacing a mostly-gone header include with ViewStub saved 60ms, and swapping a dedicated SplashActivity for a theme-level windowBackground removed 150ms of Activity-creation overhead. Async data loading with a skeleton screen kept the first frame visible while data arrived. Regression prevention uses a CI baseline (P90 of 20 `am start -W` runs, failing above 15% drift) and online monitoring of attachBaseContext-to-onWindowFocusChanged duration, broken down by device model with emphasis on low-end-device P90.

Takeaways
Use `adb shell am start -W` as the authoritative cold-start measurement; layer in `SystemClock.elapsedRealtime()` instrumentation at Application.onCreate and first-frame callbacks.
Split Application initialization into synchronous-required, IdleHandler-deferred (main-thread-tolerant only), and background-thread work.
Push non-first-screen SDK init past Activity.onResume via `window.decorView.post` or the App Startup library.
Audit third-party ContentProviders with `dumpsys package` and remove unnecessary auto-init providers using `tools:node="remove"` — this alone saved 220ms.
Flatten deeply nested layout hierarchies; a 5-layer LinearLayout inside a ConstraintLayout cost 90ms until flattened.
Replace large include layouts full of gone views with ViewStub lazy loading to avoid wasted measure/layout passes.
Swap a dedicated SplashActivity for a theme-level `windowBackground` drawable and switch back to AppTheme after super.onCreate — saves roughly 150ms.
Load first-screen data asynchronously with a skeleton screen so the first frame renders immediately; user-perceived completion is first-frame visibility, not data arrival.
Add a CI startup baseline: run `am start -W` 20 times, take P90, and fail the build if it exceeds the baseline by 15%.
Monitor attachBaseContext-to-onWindowFocusChanged duration in production, segmented by device model, and track low-end-device P90 instead of averages.
Skip MultiDex.install pre-initialization on minSdk ≥ 21, keep StrictMode on in debug builds, and avoid reflection-based init skipping or class preloading — the gains are negligible or carry high compatibility cost.
Conclusions

The three highest-impact changes — Application sync reduction, layout flattening, theme splash — are mechanical and low-risk, which means most teams can adopt them without architectural rewrites.

Removing unnecessary ContentProviders is an underused lever; many SDKs register providers that run at startup but don't need to, and a single `tools:node="remove"` can reclaim hundreds of milliseconds.

IdleHandler is a double-edged tool: stuffing heavy work into it just shifts the jank from launch to the first user interaction, so it must be reserved for genuinely main-thread-tolerant tasks.

The advice to skip MultiDex.install optimization and class preloading on modern minSdk levels is a useful correction to outdated performance folklore that still circulates.

Regression prevention is treated as a first-class step — CI baselines and online P90 monitoring by device tier — which is rare in optimization write-ups and the reason most gains erode within months.

Concepts & terms
Cold start (Android)
The full launch path when an app's process does not exist: system starts the process, loads Application, creates the first Activity, and completes first-frame rendering. Measured as T2 (onWindowFocusChanged) minus T0 (am_proc_start).
adb shell am start -W
An Android Debug Bridge command that launches an Activity and reports ThisTime, TotalTime, and WaitTime — the recommended single source of truth for cold-start measurement before adding in-code instrumentation.
IdleHandler
A MessageQueue callback that runs when the main thread's message queue becomes idle. Used to defer initialization work, but only for tasks the main thread can tolerate without causing jank on first interaction.
ContentProvider auto-initialization
Third-party SDKs often declare a <provider> in the manifest that runs initialization code automatically during app startup, before Application.onCreate. These can be disabled with tools:node="remove" and replaced with manual lazy loading.
Theme splash (windowBackground)
A cold-start splash technique that sets a drawable as the Activity theme's windowBackground, avoiding the overhead of a separate SplashActivity. The theme is swapped to the normal app theme immediately after super.onCreate.
ViewStub
A lightweight, zero-size View that lazily inflates a layout resource when made visible or explicitly inflated. Used to defer inflation of layout sections that are not needed for the first frame.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗