면접 질문 목록으로 가기
면접 질문실전 질문꼬리 질문

앱 시작과 콜드 스타트(Cold Start) 최적화

skydovesJaewoong Eum (skydoves)||10분 소요

앱 시작과 콜드 스타트(Cold Start) 최적화

사용자가 앱 아이콘을 탭했을 때 메모리에 아무것도 없는 상태라면, 시스템은 새로운 프로세스를 생성하고, Application 클래스를 로드한 뒤, ContentProvider를 인스턴스화하고, Application.onCreate()를 호출한 후, 대상 Activity를 실행하여 첫 번째 프레임을 그립니다. 이 일련의 과정을 콜드 스타트(cold start)라고 하며, UI가 화면에 표시되기까지 가장 오래 걸리는 경로입니다. 이 구간에서 발생하는 모든 지연은 사용자에게 빈 화면이나 멈춘 스플래시로 직접 체감되므로, 앱의 첫인상을 결정짓는 매우 중요한 최적화 대상입니다.

ContentProvider는 콜드 스타트 시간에 상당한 영향을 미치는 요인 중 하나입니다. Firebase, WorkManager, Lifecycle 등 라이브러리가 각자의 ContentProvider를 등록하면, 개발자가 작성한 애플리케이션 코드가 실행되기도 전에 초기화 작업이 누적됩니다. App Startup 라이브러리는 여러 ContentProvider를 하나로 통합하고, 의존성을 인식하는 초기화 시스템을 제공하여 이 문제를 해결합니다. 면접에서도 콜드 스타트 최적화는 빈번하게 출제되는 주제이므로, 단순히 "느리니까 최적화해야 한다" 수준이 아니라 내부 동작 원리까지 설명할 수 있어야 좋은 인상을 줄 수 있습니다.

이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.

  • 프로세스 생성부터 첫 번째 프레임 렌더링까지의 콜드 스타트 시퀀스를 설명할 수 있습니다.
  • ContentProvider가 앱 시작의 병목이 되는 이유와 App Startup이 이를 어떻게 통합하는지 파악할 수 있습니다.
  • AppInitializer가 초기화 모듈 간 의존성 그래프를 해석하는 과정을 추적할 수 있습니다.
  • 콜드 스타트 시간을 줄이기 위한 실전 전략, 즉 지연 초기화(lazy initialization), Baseline Profile, 트레이싱(tracing) 기법을 식별할 수 있습니다.
  • initializeComponent()를 활용하여 필수적이지 않은 초기화를 필요한 시점까지 지연시키는 방법을 적용할 수 있습니다.

콜드 스타트 시퀀스

콜드 스타트는 앱의 프로세스가 존재하지 않는 상태에서 발생합니다. 개발자가 작성한 코드가 실행되기 전에 시스템은 다음과 같은 여러 단계를 거칩니다.

  1. 시스템이 Zygote로부터 새로운 프로세스를 포크(fork)합니다.
  2. 런타임이 Application 객체를 생성합니다.
  3. 병합된 매니페스트(merged manifest)에 선언된 모든 ContentProvider가 인스턴스화되고, 각각의 onCreate() 메서드가 호출됩니다.
  4. Application.onCreate()가 호출됩니다.
  5. 대상 Activity가 생성되고, 첫 번째 프레임이 측정(measure), 배치(layout), 렌더링(draw)됩니다.

3단계가 많은 앱에서 시간을 소모하는 구간입니다. 각 ContentProviderApplication.onCreate() 이전에 메인 스레드에서 실행됩니다. 5개의 라이브러리가 각자 자동 초기화를 위해 별도의 ContentProvider를 등록하면, 5개의 인스턴스가 생성되고, 5번의 onCreate() 호출이 발생하며, 이 모든 작업이 앱 코드 시작 전에 메인 스레드를 차단합니다. 시스템은 ContentProvider를 병합된 매니페스트에 나타나는 순서대로 순차적으로 인스턴스화하므로 이 과정을 병렬화할 수 없습니다.

웜 스타트(warm start)는 1단계를 건너뜁니다(프로세스는 이미 존재하지만 Activity가 소멸된 상태). 핫 스타트(hot start)는 기존 Activity를 포그라운드로 가져오는 것 외에 모든 단계를 건너뜁니다. 콜드 스타트가 가장 비용이 크고, 사용자의 첫인상을 결정하기 때문에 최적화의 우선순위가 가장 높습니다.

ContentProvider가 비용이 큰 이유

ContentProvider는 원래 프로세스 간 데이터 접근(cross-process data access)을 위해 설계되었습니다. 라이브러리들이 ContentProvider를 초기화 후크(hook)로 채택한 이유는, ContentProvider.onCreate()Application.onCreate() 이전에 자동으로 호출되어 개발자가 Application 클래스에 별도 코드를 추가하지 않아도 라이브러리가 스스로 초기화할 수 있기 때문입니다.

비용은 두 가지 원인에서 발생합니다. 첫째, 각 ContentProvider는 클래스 로딩 오버헤드를 추가합니다. 런타임이 해당 클래스를 찾고, 로드하고, 검증해야 하기 때문입니다. 둘째, onCreate() 메서드가 SharedPreferences 읽기, 싱글톤 인스턴스 설정, 생명주기 옵저버 등록 같은 실제 초기화 작업을 수행하는 경우가 많습니다.

Firebase(여러 Provider), WorkManager(WorkManagerInitializer), Lifecycle(ProcessLifecycleOwnerInitializer) 등 다양한 라이브러리의 의존성을 병합하면, 중급 사양 디바이스에서 콜드 스타트 시간에 50~150ms가 쉽게 추가될 수 있습니다. 이 시간은 개발자가 작성한 코드가 실행되기 전에 발생하므로 대부분의 개발자가 인지하지 못하는 구간입니다. 면접에서 이 부분을 언급하면 단순한 "앱이 느려요" 수준의 답변과 차별화할 수 있습니다.

App Startup: 여러 ContentProvider를 하나로 통합

App Startup 라이브러리는 여러 ContentProvider를 하나의 InitializationProvider로 대체합니다. 각 라이브러리는 초기화 모듈을 매니페스트의 메타데이터 항목으로 선언하고, InitializationProvider가 이를 탐색하여 한 번에 실행합니다.

InitializationProvider 자체는 최소한의 ContentProvider입니다. onCreate() 메서드는 모든 작업을 AppInitializer에 위임합니다.

public class InitializationProvider extends ContentProvider {
    @Override
    public final boolean onCreate() {
        Context context = getContext();
        if (context != null) {
            Context applicationContext = context.getApplicationContext();
            if (applicationContext != null) {
                AppInitializer.getInstance(context)
                    .discoverAndInitialize(getClass());
            }
        }
        return true;
    }
}

이 면접 질문은 구독자 전용입니다

Dove Letter를 구독하시면 안드로이드, 코틀린 개발 관련 독점 면접 질문의 전체 내용을 볼 수 있습니다.

구독하기
면접 질문 목록으로 가기