앱 시작 시간

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

사용자는 앱이 응답하고 빠르게 로드되기를 기대합니다. 시작 시간이 느린 앱은 이 기대를 충족하지 못하여 사용자가 실망할 수 있습니다. 이러한 종류의 좋지 못한 경험으로 인해 사용자가 Play 스토어에서 앱 평점을 낮게 주거나 사용을 중단할 수도 있습니다.

이 문서에서는 앱 시작 시간을 최적화하는 데 도움이 되는 정보를 제공합니다. 먼저 시작 프로세스의 내부 기능을 설명합니다. 그런 다음 시작 성능을 프로파일링하는 방법을 설명합니다. 마지막으로 일반적인 시작 시간 문제를 설명하고 문제를 해결하는 방법에 관한 힌트를 제공합니다.

다양한 앱 시작 상태 이해

앱은 콜드 스타트, 웜 스타트, 핫 스타트라는 세 가지 상태 중 하나에서 시작하며 각 상태는 앱이 사용자에게 표시되는 데 걸리는 시간에 영향을 미칩니다. 콜드 스타트에서는 앱이 처음부터 시작됩니다. 다른 상태에서는 시스템이 실행 중인 앱을 백그라운드에서 포그라운드로 가져와야 합니다. 항상 콜드 스타트를 가정하여 최적화하는 것이 좋습니다. 이렇게 하면 웜 스타트와 핫 스타트의 성능도 개선될 수 있습니다.

앱이 빠르게 시작하도록 최적화하려면 상태별로 시스템 수준 및 앱 수준에서 발생하는 일, 시스템과 앱이 상호작용하는 방식을 이해하는 것이 좋습니다.

콜드 스타트

콜드 스타트는 앱이 처음부터 시작하는 것을 말합니다. 콜드 스타트 이전에는 시스템의 프로세스에서 앱의 프로세스를 만들지 않습니다. 기기가 부팅되거나 시스템에서 앱이 종료되고 난 후 앱이 처음으로 시작되는 경우 등에서 콜드 스타트가 발생합니다. 이 같은 시작 유형에서는 시스템과 앱이 다른 시작 상태보다 더 많은 작업을 실행해야 하므로 시작 시간을 최소화하는 데 가장 큰 어려움이 있습니다.

콜드 스타트를 시작할 때 시스템은 다음과 같은 세 가지 작업을 실행합니다.

  1. 앱 로드 및 시작
  2. 시작 직후 앱의 빈 시작 창 표시
  3. 프로세스 만들기

시스템에서 앱 프로세스를 만들면 곧바로 앱 프로세스에서는 다음 단계를 실행합니다.

  1. 앱 개체 만들기
  2. 기본 스레드 시작
  3. 기본 활동 만들기
  4. 뷰 확장
  5. 화면 배치
  6. 처음 그리기 실행

앱 프로세스에서 첫 번째 그리기를 완료하고 나면 시스템 프로세스에서 현재 표시된 백그라운드 창을 전환하고 기본 활동으로 바꿉니다. 이 시점에서 사용자는 앱 사용을 시작할 수 있습니다.

그림 1은 시스템 프로세스와 앱 프로세스가 서로 간에 작업을 전달하는 방법을 보여줍니다.

그림 1: 콜드 애플리케이션 시작의 중요한 부분을 시각적으로 표현

앱을 만들고 활동을 만드는 동안 성능 문제가 발생할 수 있습니다.

애플리케이션 만들기

애플리케이션이 시작되면 시스템에서 처음으로 앱 그리기를 완료할 때까지 빈 시작 창이 화면에 남아 있습니다. 이 시점에서 시스템 프로세스가 앱의 시작 창을 전환하여 사용자가 앱과 상호작용을 시작할 수 있도록 합니다.

자체 앱에서 Application.onCreate()가 재정의되면 시스템은 앱 객체에서 onCreate() 메서드를 호출합니다. 그런 다음 앱에서 기본 스레드(UI 스레드라고도 함)를 생성하고 이 스레드를 통해 기본 활동을 만듭니다.

이 시점부터 앱 수명 주기 단계에 따라 시스템 및 앱 수준 프로세스가 진행됩니다.

활동 만들기

앱 프로세스에서 활동을 만든 후 활동에서는 다음 작업을 실행합니다.

  1. 값을 초기화합니다.
  2. 생성자를 호출합니다.
  3. Activity.onCreate()와 같이 활동의 현재 수명 주기 상태에 적합한 콜백 메서드를 호출합니다.

일반적으로 onCreate() 메서드는 오버헤드가 가장 높은 작업(예: 뷰 로드 및 확장, 활동 실행에 필요한 객체 초기화)을 실행하므로 로드 시간에 가장 큰 영향을 미칩니다.

웜 스타트

웜 스타트는 콜드 스타트 시 발생하는 작업의 일부 하위 집합을 포함하며 동시에 핫 스타트보다 더 많은 오버헤드를 나타냅니다. 웜 스타트로 간주될 수 있는 상태로는 여러 가지가 있습니다. 예:

  • 사용자가 앱을 종료한 다음 다시 시작합니다. 프로세스가 계속 실행되었을 수도 있지만 앱이 onCreate() 호출을 통해 활동을 처음부터 다시 만들어야 합니다.

  • 시스템이 메모리에서 앱을 제거한 다음 사용자가 앱을 다시 시작합니다. 프로세스와 활동을 다시 시작해야 하지만 onCreate()에 전달된 저장된 인스턴스 상태 번들이 작업을 실행하는 데 어느 정도 도움이 됩니다.

핫 스타트

애플리케이션의 핫 스타트는 콜드 스타트보다 훨씬 더 간단하고 오버헤드가 낮습니다. 핫 스타트에서는 시스템이 활동을 포그라운드로 가져오기만 하면 됩니다. 애플리케이션의 모든 활동이 아직 메모리에 있으면 앱에서 객체 초기화, 레이아웃 확장, 렌더링을 반복하지 않아도 됩니다.

하지만 onTrimMemory() 등 메모리 자르기 이벤트로 인해 일부 메모리가 삭제된 경우 핫 스타트 이벤트에 대한 대응으로 객체를 다시 만들어야 합니다.

핫 스타트는 콜드 스타트 시나리오와 동일한 화면 동작을 표시합니다.

시스템 프로세스는 앱이 활동 렌더링을 완료할 때까지 빈 화면을 표시합니다.

그림 2: 이 다이어그램은 다양한 시작 상태와 각 프로세스를 보여주며 각 상태는 그려진 첫 번째 프레임부터 시작됩니다.

측정항목을 사용한 문제 감지 및 진단

시작 시간 성능을 제대로 진단하기 위해 애플리케이션이 시작하는 데 걸리는 시간을 보여주는 측정항목을 추적할 수 있습니다. Android에서는 여러 가지 방법으로 앱 문제를 알리고 문제 진단을 도와 줍니다. Android vitals를 통해 문제 발생에 관한 알림을 받고 진단 도구를 사용하여 문제를 진단할 수 있습니다.

시작 측정항목 활용의 이점

Android는 처음 표시하는 데 걸린 시간완전히 표시하는 데 걸린 시간 측정항목을 사용하여 콜드 및 웜 애플리케이션 시작을 최적화합니다. Android 런타임(ART)은 이러한 측정항목의 데이터를 사용하여 향후 시작을 최적화하기 위한 코드를 효율적으로 사전 컴파일합니다.

시작 속도가 빠르면 앱과의 사용자 상호작용이 더 지속적으로 유지되므로 조기 종료, 인스턴스 다시 시작, 다른 앱으로의 이동이 줄어듭니다.

Android vitals

Android vitals를 사용하면 앱 시작 시간이 너무 긴 경우 Play Console을 통해 알림을 받음으로써 앱 성능을 개선할 수 있습니다. Android vitals에서는 다음과 같은 경우 앱의 시작 시간이 너무 긴 것으로 간주합니다.

  • 콜드 스타트가 5초 이상 걸림
  • 스타트가 2초 이상 걸림
  • 스타트가 1.5초 이상 걸림

Android vitals는 처음 표시하는 데 걸린 시간 측정항목을 사용하며 핫 스타트의 데이터를 보고하지 않습니다. Google Play에서 Android vitals 데이터를 수집하는 방법에 관한 자세한 내용은 Play Console 문서를 참고하세요.

처음 표시하는 데 걸린 시간

처음 표시하는 데 걸린 시간(TTID) 측정항목은 프로세스 초기화(콜드 스타트인 경우), 활동 생성(콜드/웜 스타트인 경우), 첫 번째 프레임 표시 등 애플리케이션이 첫 프레임을 생성하는 데 걸리는 시간을 측정합니다.

TTID를 검색하는 방법

Android 4.4(API 수준 19) 이상에서는 logcat에 Displayed라는 값이 포함된 출력 행이 있습니다. 이 값은 프로세스를 시작한 후 활동을 화면에 그릴 때까지 경과된 시간을 나타냅니다. 경과된 시간에는 다음과 같은 순서의 이벤트가 포함됩니다.

  • 프로세스를 시작합니다.
  • 개체를 초기화합니다.
  • 활동을 만들고 초기화합니다.
  • 레이아웃을 확장합니다.
  • 애플리케이션을 처음으로 그립니다.

다음 예와 비슷한 로그 라인이 보고됩니다.

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms

명령줄이나 터미널에서 logcat 출력을 추적하는 경우 경과된 시간을 쉽게 찾을 수 있습니다. Android 스튜디오에서 경과된 시간을 찾으려면 logcat 뷰에서 필터를 사용 중지해야 합니다. 앱 자체가 아니라 시스템 서버에서 이 로그를 제공하므로 필터를 사용 중지해야 합니다.

적절한 설정을 완료한 후 쉽게 용어를 검색하여 시간을 확인할 수 있습니다. 그림 2에는 필터를 끄는 방법과 밑에서 두 번째 출력 행에 logcat의 Displayed 시간 출력 예가 나와 있습니다.

그림 2: 필터를 사용 중지하고 logcat에서 'Displayed' 값을 찾음

logcat 출력의 Displayed 측정항목은 모든 리소스가 로드되고 표시될 때까지 시간을 캡처할 필요가 없습니다. 레이아웃 파일에서 참조되지 않거나 앱에서 객체 초기화의 일부로 생성된 리소스는 제외됩니다. 이러한 리소스는 인라인 프로세스로 로드되고 앱의 초기 표시를 차단하지 않으므로 제외됩니다.

경우에 따라 logcat 출력의 Displayed 행에 전체 시간에 관한 추가 필드가 포함되어 있습니다. 예:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

이 경우 처음으로 그린 활동의 경우에만 첫 번째 시간 측정이 실행됩니다. total 시간 측정은 앱 프로세스가 시작되면 시작되고, 처음에 시작되었지만 화면에 아무것도 표시하지 않은 다른 활동도 포함할 수 있습니다. total 시간 측정은 단일 활동과 총 시작 시간 사이에 차이가 있을 때만 표시됩니다.

ADB 셸 활동 관리자 명령어로 앱을 실행하여 처음 표시하는 데 걸린 시간을 측정할 수도 있습니다. 다음 예를 참고하세요.

adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN

Displayed 측정항목은 전과 같이 logcat 출력에 표시됩니다. 또한 터미널 창에는 다음과 같이 표시됩니다.

Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete

-c-a 인수는 선택사항이며 이를 사용하면 <category><action>을 지정할 수 있습니다.

완전히 표시하는 데 걸린 시간

완전히 표시하는 데 걸린 시간(TTFD) 측정항목은 애플리케이션이 첫 번째 프레임 이후에 비동기식으로 로드된 콘텐츠를 비롯하여 전체 콘텐츠가 포함된 첫 프레임을 생성하는 데 걸린 시간을 측정합니다. 일반적으로 앱에서 보고한 것과 같이 네트워크에서 로드된 기본 목록 콘텐츠입니다.

TTFD를 검색하는 방법

reportFullyDrawn() 메서드를 사용하여 애플리케이션이 시작된 후 모든 리소스와 뷰 계층 구조를 완전히 표시할 때까지 걸린 시간을 측정할 수 있습니다. 이 방법은 앱이 지연 로드를 실행하는 경우 유용할 수 있습니다. 지연 로드에서 앱은 창의 초기 그리기를 차단하지 않지만 대신 비동기식으로 리소스를 로드하고 뷰 계층 구조를 업데이트합니다.

지연 로드로 인해 앱의 초기 표시에 모든 리소스가 포함되지 않는 경우 로드 완료 및 모든 리소스와 뷰의 표시를 별도의 측정항목으로 간주할 수 있습니다. 예를 들어 UI가 완전히 로드되고 일부 텍스트가 그려지지만 앱이 네트워크에서 가져와야 하는 이미지는 아직 표시되지 않을 수 있습니다.

reportFullyDrawn()을 수동으로 호출하여 활동이 지연 로드로 완료되었음을 시스템에 알리는 방식으로 이 문제를 해결할 수 있습니다. 이 메서드를 사용하는 경우 logcat에 표시되는 값은 애플리케이션 객체를 만든 후 reportFullyDrawn()이 호출될 때까지 경과한 시간입니다. 다음은 logcat 출력의 예입니다.

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms

경우에 따라 처음 표시하는 데 걸린 시간에 설명된 대로 logcat 출력에 total 시간이 포함되어 있습니다.

표시 시간이 원하는 것보다 느린 경우 시작 프로세스에서 병목 현상을 식별해 문제 해결을 시도해 볼 수 있습니다.

병목 현상 식별

병목 현상을 찾는 좋은 방법은 Android 스튜디오 CPU 프로파일러를 사용하는 것입니다. 자세한 내용은 CPU 프로파일러를 사용하여 CPU 활동 검사를 참고하세요.

앱과 활동의 onCreate() 메서드 내부에서 인라인 추적을 통해 잠재적 병목 현상에 관한 유용한 정보를 얻을 수도 있습니다. 인라인 추적에 관해 자세히 알아보려면 Trace 함수 문서 및 시스템 추적 개요를 참고하세요.

일반적인 문제 파악

이 섹션에서는 앱의 시작 성능에 종종 영향을 미치는 여러 가지 문제를 설명합니다. 이러한 문제는 주로 앱과 활동 객체의 초기화 및 화면의 로드와 관련이 있습니다.

과도한 앱 초기화

코드가 Application 객체를 재정의하거나, 객체를 초기화할 때 과도한 작업 또는 복잡한 로직을 실행하면 시작 성능에 문제가 발생할 수 있습니다. Application 서브클래스에서 아직 실행할 필요가 없는 초기화를 실행하는 경우 앱이 시작하는 동안 시간을 허비할 수 있습니다. 일부 초기화는 완전히 불필요합니다. 예를 들어 앱이 인텐트에 따라 실제로 시작할 때 기본 활동의 상태 정보를 초기화하는 경우가 이에 해당합니다. 인텐트에 따라 앱에서는 이전에 초기화된 상태 데이터의 하위 집합만을 사용합니다.

앱을 초기화하는 동안 발생하는 다른 문제로는 영향력이 있거나 다양한 가비지 컬렉션 이벤트, 초기화와 동시에 발생하여 초기화 프로세스를 추가로 차단하는 디스크 I/O 등이 있습니다. Dalvik 런타임의 경우 특히 가비지 컬렉션을 고려해야 합니다. Art 런타임은 가비지 컬렉션을 동시에 실행하여 작업의 영향을 최소화합니다.

문제 진단

메서드 추적 또는 인라인 추적을 사용하여 문제를 진단할 수 있습니다.

메서드 추적

CPU 프로파일러를 실행하면 callApplicationOnCreate() 메서드가 궁극적으로 com.example.customApplication.onCreate 메서드를 호출하는 것을 알 수 있습니다. 도구를 통해 이러한 메서드가 실행을 완료하는 데 오랜 시간이 걸리는 것으로 확인되면 어떤 일이 발생하고 있는지 추가로 확인해야 합니다.

인라인 추적

다음을 포함하여 가능성이 큰 원인을 조사하려면 인라인 추적을 사용하세요.

  • 앱의 초기 onCreate() 함수
  • 앱에서 초기화하는 모든 전역 싱글톤 객체
  • 병목 현상 시 발생할 수 있는 모든 디스크 I/O, 역직렬화 또는 타이트 루프

문제 해결 방법

문제의 원인이 불필요한 초기화에 있거나 디스크 I/O에 있거나 상관없이 해결 방법은 지연 초기화입니다. 즉, 즉시 필요한 객체만 초기화해야 합니다. 전역 정적 객체를 만드는 대신 앱에서 처음 필요할 때만 객체를 초기화하는 싱글톤 패턴으로 이동합니다.

또한 처음 삽입될 때 객체와 종속 항목을 만드는 Hilt와 같은 종속 항목 삽입 프레임워크를 사용해 보세요.

앱이 콘텐츠 제공자를 사용하여 시작 시 앱 구성요소를 초기화하는 경우 대신 앱 시작 라이브러리를 사용하는 것이 좋습니다.

과도한 활동 초기화

활동을 만드는 데는 오버헤드가 높은 많은 작업이 수반됩니다. 종종 이 작업을 최적화하여 성능을 향상할 수 있습니다. 일반적인 문제는 다음과 같습니다.

  • 크거나 복잡한 레이아웃 확장
  • 디스크 또는 네트워크 I/O 시 화면 그리기 차단
  • 비트맵 로드 및 디코딩
  • VectorDrawable 객체 래스터화
  • 활동의 다른 하위 시스템 초기화

문제 진단

이 경우에도 메서드 추적과 인라인 추적 모두 유용합니다.

메서드 추적

CPU 프로파일러를 사용할 경우 앱의 Application 서브클래스 생성자와 com.example.customApplication.onCreate() 메서드에 유의합니다.

도구를 통해 이러한 메서드가 실행을 완료하는 데 오랜 시간이 걸리는 것으로 확인되면 어떤 일이 발생하고 있는지 추가로 확인해야 합니다.

인라인 추적

다음을 포함하여 가능성이 큰 원인을 조사하려면 인라인 추적을 사용하세요.

  • 앱의 초기 onCreate() 함수
  • 앱에서 초기화하는 모든 전역 싱글톤 객체
  • 병목 현상 시 발생할 수 있는 모든 디스크 I/O, 역직렬화 또는 타이트 루프

문제 해결 방법

병목 현상의 종류는 다양하지만 두 가지 일반적인 문제와 해결 방법은 다음과 같습니다.

  • 뷰 계층 구조가 클수록 앱이 확장하는 데 더 많은 시간이 걸립니다. 이 문제를 해결하기 위해 취할 수 있는 두 가지 단계는 다음과 같습니다.

    • 중복되거나 중첩된 레이아웃을 줄여 뷰 계층 구조를 평평하게 합니다.

    • 시작하는 동안 표시되지 않아도 되는 UI의 부분을 확장하지 않습니다. 대신 ViewStub 객체를 앱이 더 적절한 시간에 확장할 수 있는 하위 계층 구조의 자리표시자로 사용합니다.

  • 기본 스레드에서 리소스 초기화를 모두 실행해도 시작 속도가 느려질 수 있습니다. 이 문제는 다음과 같이 해결할 수 있습니다.

    • 앱이 다른 스레드에서 느리게 실행할 수 있도록 모든 리소스 초기화를 이동합니다.
    • 앱이 뷰를 로드 및 표시한 다음 나중에 비트맵 또는 다른 리소스에 종속되는 시각적 속성을 업데이트할 수 있도록 허용합니다.

맞춤 스플래시 화면

이전에 다음 메서드 중 하나를 사용하여 Android 11(API 수준 30) 이하에서 맞춤 스플래시 화면을 구현한 경우 시작 시 추가 시간이 더해질 수 있습니다.

  • windowDisablePreview 테마 속성을 사용하여 시작 시 시스템에서 그린 초기 빈 화면을 끕니다.

  • 전용 활동을 사용합니다.

Android 12부터 SplashScreen API로 이전해야 합니다. 이 API를 사용하면 시작 시간이 단축되며 다음과 같은 방법으로 스플래시 화면을 조정할 수도 있습니다.

또한 compat 라이브러리는 SplashScreen API를 백포팅하여 이전 버전과의 호환성을 사용 설정하고 모든 Android 버전에서 스플래시 화면 표시를 위한 일관된 디자인을 만듭니다.

자세한 내용은 스플래시 화면 이전 가이드를 참고하세요.