Cold Start는 애플리케이션 프로세스가 메모리에 존재하지 않고 Activity가 생성되지 않은 제로 상태에서 시작하는 Android 애플리케이션의 전체 시작 주기입니다. 시스템은 새 프로세스를 만들고, 클래스를 로드하고, Application을 초기화하고, Activity를 만들고, 첫 번째 그리기를 수행합니다. Google, 2024에 따르면 중간급 기기에서 콜드 스타트는 1~5초가 소요될 수 있으며, 100ms 지연마다 사용자 유지 확률이 3% 감소합니다.
주요 포인트
Cold Start(콜드 스타트)는 Android 애플리케이션이 가장 초기 상태에서 시작되는 시나리오입니다: 운영 체제가 새 프로세스를 만들고(Zygote에서 fork), 메모리를 할당하고, DEX 코드를 ART에 로드하고, 클래스를 초기화하고, Application 인스턴스를 만든 다음 첫 번째 Activity를 만듭니다. 앱 시작 전에는 Background Dexopt가 사용되는 경우 캐시된 클래스 이미지를 제외하고 기기 메모리에 앱에 대한 데이터가 없습니다.
콜드 스타트는 세 가지 경우에 발생합니다: 앱 설치 후 첫 번째 시작 시, 기기 재부팅 후 시작 시, 그리고 메모리 부족으로 시스템이 프로세스를 제거한 후 시작 시입니다. 2~4GB RAM을 갖춘 기기에서 시스템은 백그라운드 프로세스를 상당히 적극적으로 제거하므로 Cold Start는 사용자가 몇 시간 동안 비활성 상태 후 앱으로 돌아올 때마다 발생할 수 있습니다. Android 12+에서는 시스템이 동결된 프로세스(freeze / cached)를 유지할 수 있지만, 활성 메모리 절약(OOM-killer)으로 프로세스가 종료됩니다.
Google(Find My Device 보고서, 2023)에 따르면 사용자의 65%가 앱이 3초 내에 열리지 않으면 앱을 닫습니다. 사용자가 하루에 수십 번 돌아오는 소셜 네트워크 및 메신저의 경우 Cold Start는 리텐션에 직접적인 영향을 미칩니다. Google Play Console에서 Cold Start 메트릭은 Android Vitals 섹션의 일부이며 ANR 및 성능 지표 중 하나로 표시됩니다. "나쁨" Cold Start 임계값(25% 기기에서 5초 초과)을 초과하는 앱은 콘솔에서 경고를 받고 검색 결과에서 순위가 낮아질 수 있습니다.
Android는 세 가지 유형의 앱 시작을 구분하며, 각각 지속 시간, UX 영향 및 최적화 접근 방식이 다릅니다. 차이점을 이해하는 것은 올바른 프로파일링 전략을 선택하는 데 필수적입니다.
| 시작 유형 | 프로세스 상태 | Application.onCreate | 일반적인 시간 |
|---|---|---|---|
| Cold | 프로세스 없음 | 실행됨 | 1~5초 |
| Warm | 프로세스 있음, Activity 없음 | 실행되지 않음 | 200~600ms |
| Hot | 프로세스 + Activity가 메모리에 있음 | 실행되지 않음 | < 200ms |
Warm Start는 앱 프로세스가 이미 백그라운드에 존재하지만 Activity가 소멸된 경우 발생합니다(예: 사용자가 긴 휴식 후 돌아와 시스템이 Activity 메모리를 해제한 경우). Hot Start는 사용자가 앱을 최소화하고 즉시 다시 열 때 발생합니다: Activity가 일시 중지되어 있으며 복원에 최소 시간이 걸립니다. 사용자에게 Cold Start는 가장 눈에 띄는 시작 유형이며, 그 최적화는 UX에 가장 큰 개선을 가져옵니다.
앱이 한 번 이상 시작된 후 Cold Start는 Warm Start가 될 수 있습니다 — ART는 컴파일된 클래스 이미지(Boot Profile의 Image)를 캐시하고 이후 DEX 로딩이 더 빨라집니다. 따라서 첫 번째 Cold Start 후 두 번째 시작은 일반적으로 20~40% 더 빠릅니다. 앱이 Baseline Profiles를 사용하는 경우 프로필은 첫 번째 시작 시 로드되고 두 번째 시작은 더욱 빨라질 수 있습니다: Baseline Profiles를 게시한 Google Play는 Android 12+ 기기에서 Cold Start를 30% 가속화했습니다.
Cold Start는 엄격하게 정의된 단계로 구성되며, 각 단계는 독립적으로 측정 및 최적화될 수 있습니다. 단계를 이해하면 앱이 어떤 단계에서 시간을 잃고 있는지 파악하는 데 도움이 됩니다. Google은 네 가지 주요 단계를 식별합니다: 프로세스 생성, Application 초기화, Activity 생성, 첫 번째 프레임.
Android 시스템(ActivityManagerService)은 Zygote 프로세스에서 fork하여 새 프로세스를 만듭니다. Zygote는 공통 Android 클래스가 미리 로드된 프로세스입니다. Fork는 30~80ms가 소요됩니다 — 이 시간은 앱의 통제를 벗어납니다. Fork 후 ActivityThread가 시작됩니다 — 애플리케이션의 메인 루프 인스턴스입니다. 이 단계에서 ClassLoader를 통한 클래스 로딩도 발생하며 ART가 첫 번째 바이트코드 해석을 시작합니다. 앱이 많은 정적 초기화자를 사용하는 경우 이 단계가 길어질 수 있습니다.
ActivityThread가 시작된 직후 Application.onCreate가 호출됩니다. 여기서 개발자는 가장 자주 실수를 범합니다 — 모든 것을 한 번에 초기화하는 것입니다: Crashlytics, Firebase, 네트워크 클라이언트, 데이터베이스, Dagger 구성 요소, DI 컨테이너. 이러한 각 초기화는 메인 스레드에서 차단되는 시간입니다. Application.onCreate가 500ms 걸리는 경우 사용자는 0.5초 동안 흰색(또는 검은색) 화면을 봅니다. 중간급 기기에서 이 단계의 최적 지속 시간은 200ms 미만입니다.
Application 초기화 후 Activity 인스턴스가 생성됩니다(MainActivity 또는 Launcher Activity). Activity.onCreate가 호출되며, setContentView, 프래그먼트 초기화, ViewModel 설정, LiveData/Flow 구독이 발생합니다. onCreate가 메인 스레드에서 데이터(SharedPreferences, SQLite, API)를 동기적으로 로드하는 경우 단계가 확장됩니다. 목표는 중간급 기기에서 onCreate를 200~400ms 내로 유지하는 것입니다.
onCreate 완료 후 첫 번째 렌더링이 시작됩니다: 측정, 레이아웃, 그리기. 이 순간을 TTFD(Time To First Draw)라고 합니다. 앱이 스플래시 화면(Android 12+의 SplashScreen API 또는 테마를 통해)을 사용하는 경우 렌더링이 더 빨리 발생할 수 있지만 사용자는 스플래시가 사라질 때까지 기다려야 합니다. Cold Start의 이상적인 TTFD는 1.5초 미만입니다.
Cold Start를 측정하려면 특별한 도구가 필요합니다. 일반 로깅(Log.d)은 Application 생성 후에만 작동하기 시작하고 fork 타이밍과 클래스 로딩에는 접근할 수 없기 때문입니다. Google은 세 가지 방법을 권장합니다: ADB 명령, Android Vitals 및 사용자 정의 성능 매크로.
가장 간단하고 재현 가능한 방법은 adb shell am start -S -W 명령입니다. -S 플래그는 시작 전에 앱을 강제로 중지합니다(Cold Start 보장). 명령은 세 가지 메트릭을 출력합니다: ThisTime(Activity 시작 시간), TotalTime(프로세스 시작 포함 총 시간), WaitTime(Activity Manager의 모든 지연 포함 시간). 깨끗한 측정을 위해 5~7회 측정하고 중앙값을 사용하십시오 — 단일 측정은 노이즈(CPU 스로틀링, 백그라운드 로드)의 영향을 받습니다.
# 측정과 함께 강제 Cold Start
$ adb shell am start -S -W \
com.example.app/.MainActivity
# 명령 출력:
# ThisTime: 1842 ms
# TotalTime: 1842 ms
# WaitTime: 1855 ms
Google Play Console은 앱이 설치된 모든 기기에서 익명 메트릭을 수집합니다. Android Vitals → Launch time 섹션에는 기기 모델 및 Android 버전별 Cold Start의 중앙값 분포가 표시됩니다. 이는 테스트 기기가 아닌 사용자 기기에서 실제 메트릭을 볼 수 있는 유일한 방법입니다. Redmi 9A(2GB RAM)에서 Cold Start가 5초를 초과하고 Pixel 8에서 1.2초인 경우 문제는 메모리 크기와 클래스 수입니다. Google은 25번째 백분위수를 기반으로 한 사용자 인지 지연도 표시합니다.
Google Jetpack Macrobenchmark(androidx.benchmark 라이브러리)를 사용하면 계측된 앱 시작 테스트를 작성할 수 있습니다. 테스트는 앱을 설치하고, 콜드 상태에서 시작하고, 첫 번째 프레임까지의 시간을 측정합니다. Macrobenchmark는 자동으로 20회 실행하고, 이상값을 폐기하고, 안정적인 백분위수를 표시합니다. CI/CD의 경우 기준선과 현재 시작을 비교할 수 있습니다 — 시간이 증가하면 CI 파이프라인이 실패할 수 있습니다.
Cold Start 최적화는 코드, 리소스, 빌드 구성 및 초기화 아키텍처 등 앱의 여러 수준에 영향을 미치는 체계적인 작업입니다. Google은 가장 비용이 많이 드는 부분 — Application.onCreate — 부터 시작하여 세부 사항으로 진행할 것을 권장합니다.
시작 시 필요하지 않은 모든 초기화를 Application.onCreate에서 첫 사용 지점으로 이동합니다. Firebase, Crashlytics, 분석 SDK, 푸시 알림, DI 구성 요소 — 모든 것은 첫 번째 화면이 렌더링된 후 초기화될 수 있습니다. Kotlin에서 Lazy(by lazy)를 사용하거나 명시적 initialize(context) 호출로 ContentProvider 초기화를 사용합니다. Google(Android Performance, 2023)에 따르면 지연 초기화는 5개 이상의 SDK를 사용하는 앱의 Cold Start를 40~60% 감소시킵니다.
Baseline Profiles는 앱 시작 시 사용되는 중요한 클래스와 메서드의 AOT 컴파일입니다. Baseline Profiles가 없으면 ART가 DEX 코드를 해석하거나 JIT를 통해 컴파일하여 시간이 소요됩니다. 프로필을 사용하면 ART가 앱 설치 중에 지정된 메서드를 네이티브 코드(AOT)로 컴파일합니다. Google은 Baseline Profiles가 Android 9+에서 Cold Start를 15~40%, Android 12+ ART 최적화로 최대 60%까지 가속화한다고 밝히고 있습니다. 프로필을 만들려면 androidx.benchmark:benchmark-baseline-profile-gradle-plugin 플러그인을 사용합니다.
androidx.startup 라이브러리를 사용하면 구성 요소 초기화를 정렬하고 단일 ContentProvider에서 실행할 수 있습니다. 여러 라이브러리의 여러 ContentProvider(각각 콜드 스타트에 1~2ms 추가) 대신 App Startup이 이를 종속성 그래프로 병합하고 필요에 따라 엄격히 초기화합니다. 시작 시 첫 번째 화면에 필요한 @Initializer로 표시된 구성 요소만 실행됩니다. 나머지는 needEarlyInit = false 플래그가 설정되어 첫 번째 렌더링 후 시작됩니다.
// App Startup Initializer — 시작 후 초기화
class AnalyticsInitializer : Initializer<Unit> {
override fun create(context: Context) {
Analytics.init(context)
}
override fun dependencies() = listOf<Class<out Initializer<*>>>()
}
// AndroidManifest.xml에서 선택 사항으로 표시
// <meta-data android:name="AnalyticsInitializer"
// android:value="false" />
DEX 파일 크기는 ART 로딩 시간에 직접적인 영향을 미칩니다. 난독화 및 데드 코드 제거를 위해 R8/ProGuard를 사용합니다(MinifyEnabled = true). 매니페스트에서 android:extractNativeLibs="false"를 활성화하여 APK가 설치 시 .so 파일을 압축 해제하지 않도록 합니다. 10개 이상의 참조 추적이 있는 프로젝트의 경우 첫 번째 화면에만 startup-priority를 추가합니다. DEX의 추가 메서드마다 로딩에 0.5~2ms가 추가되며, 50k 이상의 메서드가 있는 앱(primary dex가 있는 multidex)의 경우 최대 300ms까지 추가됩니다.
Google Play Console의 Android Vitals(Launch time 섹션)는 사용자가 익명 진단에 동의한 경우 앱이 설치된 모든 기기에서 데이터를 수집합니다. 메트릭은 Cold Start 시간에 따라 세 가지 범주로 나뉩니다: "좋음", "보통", "나쁨".
Google은 "나쁜" Cold Start를 모든 기기에서 5초를 초과하는 시간으로 정의합니다. 그러나 실제로 플래그십 기기(Snapdragon 8 Gen)의 경우 좋은 시간은 1.5초 미만, 중간급은 2.5초 미만, 보급형은 4초 미만입니다. Android Vitals는 각 기기 모델의 중앙값을 표시하여 어떤 기기에서 앱이 느리게 시작되는지 파악할 수 있습니다. Samsung A-series 또는 Xiaomi Redmi 기기에서 Cold Start가 나쁜 경우 원인은 대부분 느린 플래시 메모리와 적은 RAM입니다(Baseline Profiles를 통한 가속은 이러한 기기에서 가장 큰 효과를 냅니다).
콘솔에 표시되는 것 외에도 Cold Start 메트릭은 Google Play Search에서 앱 품질 평가에 영향을 미칩니다. "나쁜" 시작 비율이 높은 앱은 설치 페이지에 "성능 경고" 레이블이 표시되어 전환율이 낮아집니다. Google(Android Performance Playbook, 2024)에 따르면 Cold Start 문제를 해결한 앱은 설치 전환율이 평균 5% 증가하고 리텐션(D1)이 3~7% 개선되었습니다.
보다 자세한 모니터링을 위해 Firebase Performance Monitoring을 사용합니다. 세션 수준에서 Cold Start를 추적하고 앱 버전 및 Android 버전별로 분류합니다. Android Vitals와 달리 Firebase는 단계별 소요 시간의 추적 다이어그램을 표시합니다. 예를 들어 버전 3.2.0에서 Application.onCreate가 800ms(새로운 푸시 알림 라이브러리로 인해)가 걸렸지만 버전 3.2.1에서는 200ms(수정 후)가 걸렸음을 확인할 수 있습니다.
아래는 Cold Start를 직접 가속화하는 두 가지 실용적인 예제입니다: 시작 후 SDK 초기화 이동 및 SplashScreen API 사용.
일반적인 실수는 모든 SDK를 Application.onCreate에서 초기화하는 것입니다. 아래는 중요하지 않은 초기화를 첫 번째 프레임 그리기 후 시작되는 코루틴으로 이동하는 방법을 보여줍니다. 중요: Firebase, Crashlytics 및 Crash Reporting SDK는 시작 시 초기화되어야 합니다 — 다른 구성 요소 초기화 중 충돌을 잡기 때문에 연기할 수 없습니다. 나머지는 첫 번째 Activity에서 lifecycleScope를 사용합니다.
// ❌ 나쁨 — 모든 초기화를 Application.onCreate에서
class App : Application() {
override fun onCreate() {
super.onCreate()
Firebase.init(this) // 중요
Analytics.init(this) // 나중에 가능
Database.init(this) // 나중에 가능
ImageLoader.init(this) // 나중에 가능
}
}
// ✅ 좋음 — Firebase는 시작 시, 나머지는 after inflate
class App : Application() {
override fun onCreate() {
super.onCreate()
Firebase.init(this)
}
}
// 첫 번째 프레임 후 MainActivity에서:
lifecycleScope.launchWhenResumed {
initializeNonCriticalSdks()
}
Android 12+에서는 프로세스 시작 시 즉시 시스템 스플래시(어두운/밝은 배경의 앱 아이콘)를 표시하는 공식 SplashScreen API를 사용합니다. 이는 사용자로부터 초기화 시간을 숨깁니다 — 흰색 화면 대신 스플래시를 볼 수 있습니다. 이전 기기의 경우 theme-based splash(스타일의 Theme.SplashScreen)를 사용합니다. 중요: 스플래시는 300ms 이상 지속되어서는 안 됩니다 — 그때까지 앱이 준비되지 않은 경우 "영구" 스켈레톤(shimmer)을 그리고 로딩 진행률을 표시합니다.
// SplashScreen API — Android 12+
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition {
isReady.value == false
}
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
// 테마 기반 스플래시(Android 5-11)
// themes.xml에서:
// <style name="Theme.App.Starting" parent="Theme.SplashScreen">
// <item name="windowSplashScreenBackground">@color/white</item>
// <item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
// </style>
자주 묻는 질문
에뮬레이터는 강력한 호스트 컴퓨터를 사용하고 하드웨어 가속(HAXM / WHPX)으로 프로세서를 에뮬레이션합니다. 물리적 기기, 특히 보급형 기기(UFS 대신 eMMC 스토리지)는 I/O가 훨씬 느립니다. 현실적인 데이터를 얻으려면 중간급 물리적 기기에서 Cold Start를 측정하는 것이 좋습니다.
Google 권장 사항에 따르면 중간급 기기에서 중앙값 Cold Start는 2초 미만이어야 합니다. 플래그십의 경우 1.5초 미만입니다. 보급형 기기(2GB RAM)의 경우 최대 4초까지 허용되지만 3초로 최적화하는 것이 좋습니다. 5초를 초과하는 값은 중요한 것으로 간주됩니다.
간접적으로 — 네. 매니페스트에 벡터 아이콘(AdaptiveIcon)이 포함된 경우 시작 시 drawable로 컴파일되어야 합니다. 아이콘에 복잡한 경로(수십 개의 곡선이 있는 pathData)가 포함된 경우 컴파일에 10~30ms가 소요됩니다. 최적화된 pathData(SVGOMG 또는 Android Studio Vector Asset을 통해)가 있는 VectorDrawable을 사용하세요.
네, Feature Module(Android App Bundle)이 온디맨드로 로드되는 경우 Cold Start는 기능을 탭한 순간부터 첫 번째 프레임까지 측정됩니다. 온디맨드 모듈은 Play Core Library를 통해 로드되며 설치 시 시작 시간에 500~3000ms가 추가됩니다. 메인 모듈과 동일하게 기능 코드를 최적화하세요.
64k 이상의 메서드가 있는 앱은 Multidex가 필요합니다. 이는 ART가 여러 DEX 파일을 로드해야 함을 의미하며, classes.dex 파일 수에 따라 Cold Start 시간이 200~800ms 증가합니다. minSdk 21+(네이티브 multidex 지원 ART)를 사용하고 중요한 클래스를 첫 번째 DEX 파일에 유지하기 위해 --main-dex-list를 통해 primary dex를 구성하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.