Measuring is the first step to optimizing. This exercise walks through capturing and reading a startup trace to identify exactly what your app does — and what slows it down — during cold start.
Setup
You need:
- A physical device or emulator with USB debugging enabled
adbin your PATH- Android Studio (for the built-in Profiler alternative)
Verify connection:
adb devices
# Should list your device as "device" (not "unauthorized")
Option A: Android Studio CPU Profiler (Easiest)
- Run your app in debug or profileable mode
- Open Profiler tab → CPU → click Record before cold start
- Force-stop the app:
adb shell am force-stop com.example.myapp - Launch from home screen while recording
- Stop recording after first frame renders
The flame chart shows:
- Thread activity over time
- Method durations (click any slice to see duration)
- Long slices on the main thread = potential jank
Option B: Perfetto (Production-grade)
# 1. Force-stop the app
adb shell am force-stop com.example.myapp
# 2. Start Perfetto trace
adb shell perfetto \
-c - --txt \
-o /data/misc/perfetto-traces/startup.pftrace \
<<EOF
buffers: { size_kb: 65536 drain_period_ms: 250 }
data_sources: { config { name: "linux.process_stats" } }
data_sources: { config { name: "track_event" } }
data_sources: { config {
name: "android.startupapp"
android_startup_config { startup_type: "cold" }
} }
duration_ms: 10000
EOF &
# 3. Immediately cold-start your app
adb shell am start-activity -W com.example.myapp/.MainActivity
# 4. Pull the trace
adb pull /data/misc/perfetto-traces/startup.pftrace ~/Desktop/
Open startup.pftrace at ui.perfetto.dev.
Reading the Trace
Key slices to find in the trace:
| Slice | What it means | Target |
|---|---|---|
Zygote:ForkAndSpecializeCommon | Process fork from Zygote | < 5ms |
bindApplication | ART init + app class load | < 100ms |
ActivityThread:handleBindApplication | Application.onCreate runs here | Your code |
ContentProvider onCreate calls | Often hidden bottleneck | Each < 20ms |
ActivityThread:performLaunchActivity | Activity.onCreate | Your code |
Choreographer#doFrame (first) | First frame rendered | Target: < 500ms total |
Step-by-Step Analysis
1. Find your Application.onCreate:
Search for bindApplication. Everything between this and the first Choreographer#doFrame is your startup cost.
2. Identify ContentProvider init:
Look for ContentProvider.onCreate calls inside bindApplication. These run synchronously before your Application.onCreate — a common surprise.
3. Find the slowest methods: Sort flame chart by duration. Look for synchronous disk reads, network calls, or heavy object init on the main thread.
4. Measure with am start -W:
adb shell am force-stop com.example.myapp
adb shell am start-activity -W -n com.example.myapp/.MainActivity
# Output:
# WaitTime: 892ms ← total wall time
# ThisTime: 756ms ← your activity only
# TotalTime: 789ms ← from process create to first frame
Run this 5 times and average; first run is always slower due to disk cache effects.
Exercise Tasks
- Record a cold start trace for your app
- Find your
Application.onCreatein the trace — how long does it take? - List all ContentProviders that initialize at startup
- Identify the single slowest operation on the main thread
- Move any synchronous DB/file reads to a background thread and re-measure
- Check: does removing a non-critical SDK init from
Application.onCreatereduce TotalTime?
Expected Findings in a Typical App
- Firebase init in Application.onCreate: ~50–150ms
- Room database creation: ~30–80ms if done synchronously
- ContentProvider from analytics SDK: ~20–50ms per provider
- Glide/Coil init: usually fast (<5ms)
Eliminating 3 synchronous SDK inits from Application.onCreate and moving them to lazy/background initialization typically cuts cold start by 100–300ms.
Key Takeaways
| Tool | Best for |
|---|---|
| Android Studio Profiler | Interactive debugging, easy setup |
| Perfetto | Production-accurate, shareable traces |
am start -W | Quick timing without trace overhead |
bindApplication slice | Where your app's startup cost lives |
| ContentProvider | Check each one — they run before Application.onCreate |