Cold start optimization
Skill almasumdev/awesome-mobile-performance-agent-skills/.github/skills/startup/cold-start-optimization
Reduce cold start time (TTID and TTFD) on iOS and Android using baseline profiles, main-thread hygiene, and selective pre-warming. Use when launch latency exceeds the budget in Agent.md.From its SKILL.md
npx -y skills add almasumdev/awesome-mobile-performance-agent-skills --skill cold-start-optimizationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.0 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it
Cold Start Optimization
Instructions
Optimize cold start — the scenario where the OS creates a new process and your app draws its first meaningful frame. Cold start is what app-store reviewers and lab devices measure, so it is the number that matters for reviews and rankings.
1. Define the Target Metrics
Two numbers matter:
- TTID (Time To Initial Display) — first frame drawn. On Android this is
Displayedin logcat. On iOS it is the momentUIWindowcommits its first frame. - TTFD (Time To Full Display) — first useful frame with real data. On Android call
Activity.reportFullyDrawn(). On iOS emit anos_signpostevent namedTTFD.
Targets (p50 device, release build):
| Metric | iOS budget | Android budget |
|---|---|---|
| TTID | 1.2 s | 1.5 s |
| TTFD | 2.0 s | 2.5 s |
2. Baseline First
Before changing code, capture a baseline.
Android (Macrobenchmark):
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule val rule = MacrobenchmarkRule()
@Test fun coldStartup() = rule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric()),
iterations = 10,
startupMode = StartupMode.COLD,
) { pressHome(); startActivityAndWait() }
}
iOS (XCTest):
func testLaunchPerformance() {
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
3. Make the Main Thread Boring
Every millisecond before the first frame is paid on the main thread. Audit and remove:
- Disk IO in
Application.onCreate/UIApplicationDelegate.application(_:didFinishLaunchingWithOptions:). - Synchronous network calls, feature-flag fetches, remote-config SDK init.
- Eager DI container construction (Hilt
@EntryPoint, Swinject assembler,get_itregistrations) for features not on the start screen. - JSON parsing of cached payloads. Defer to
Dispatchers.IO/ a backgroundDispatchQueue. - Heavy reflection or annotation scanning (Jackson, Gson with
@SerializedNameat startup).
Android pattern — use Initializer from androidx.startup and disable default init for expensive libs:
class AnalyticsInitializer : Initializer<Analytics> {
override fun create(ctx: Context): Analytics = Analytics.init(ctx)
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}
iOS pattern — replace +[load] with +initialize or lazy static let. Never do work in +[load].
enum FeatureFlags {
static let shared = FeatureFlagsClient() // lazily created on first access
}
4. Ship a Baseline Profile (Android)
Baseline Profiles AOT-compile the hot startup path and typically reduce cold start by 15–30%.
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@get:Rule val rule = BaselineProfileRule()
@Test fun generate() = rule.collect(packageName = "com.example.app") {
pressHome(); startActivityAndWait()
device.findObject(By.res("feed")).scroll(Direction.DOWN, 0.8f)
}
}
Commit the produced baseline-prof.txt into src/main/ and add androidx.profileinstaller.
5. Pre-warm Only What the First Frame Needs
- iOS: prebuild the root
UIView/ SwiftUIViewtree off the critical path usingTask.detachedfor side data only — never for the first frame's view tree. - Android: in
Application.onCreate, post non-critical work withHandler(Looper.getMainLooper()).post {}so it runs after first frame. - Flutter: use
SchedulerBinding.instance.addPostFrameCallbackto defer work until after the first frame; mark shell route withHeroMode(enabled: false)during launch. - React Native: enable Hermes; move bridge-heavy native modules to lazy registration via
TurboModuleon the New Architecture.
6. Splash Screen, Not Blank Window
Use the OS-provided splash APIs — SplashScreen on Android 12+, LaunchScreen.storyboard / .xcassets on iOS. A themed splash hides the process-creation window and improves perceived TTID without adding real time.
7. Measure Again, Record in CI
Add the benchmark to CI. Fail the build on > 5% regression vs a committed baseline JSON. See startup-profiling skill.
Checklist
- TTID and TTFD budgets documented.
- Baseline numbers captured on a p50 device in release/profile mode.
- No disk IO, network, or JSON parsing on the main thread before first frame.
- Expensive SDK initializers moved off the startup path (AndroidX Startup, lazy singletons).
- Android: Baseline Profile generated and installed via
androidx.profileinstaller. - iOS: no work in
+[load];Task.detachedused for side effects only. - Splash theme / launch screen configured to avoid white flash.
- CI benchmark fails on > 5% regression.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.