앱 시작 시간

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

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

앱 시작 내부 기능 이해

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

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

콜드 스타트

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

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

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

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

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

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

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


그림 1. 애플리케이션 콜드 스타트의 중요한 부분을 보여주는 시각적 표현

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

애플리케이션 만들기

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

앱에서 Application.onCreate()를 오버로드한 경우 시스템이 앱 개체에서 onCreate() 메서드를 호출합니다. 그런 다음 앱에서 기본 스레드(UI 스레드라고도 함)를 생성하고 이 스레드를 통해 기본 활동을 만듭니다.

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

활동 만들기

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

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

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

핫 스타트

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

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

핫 스타트에서는 콜드 스타트 시나리오와 동일한 화면 동작을 표시합니다. 즉, 시스템 프로세스가 앱에서 활동 렌더링을 완료할 때까지 빈 화면을 표시합니다.

웜 스타트

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

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

문제 감지 및 진단

Android에서는 여러 가지 방법으로 앱 문제를 알리고 문제 진단을 도와 줍니다. Android vitals를 통해 문제 발생에 관한 알림을 받고 진단 도구를 사용하여 문제를 진단할 수 있습니다.

Android vitals

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

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

일일 세션이란 앱이 사용된 1일을 의미합니다.

Android vitals는 스타트의 데이터를 보고하지 않습니다. Google Play에서 Android vitals 데이터를 수집하는 방법에 관한 자세한 내용은 Play Console 문서를 참조하세요.

느린 시작 시간 진단

시작 시간 성능을 제대로 진단하기 위해 애플리케이션이 시작하는 데 걸리는 시간을 보여주는 측정항목을 추적할 수 있습니다.

처음 표시하는 데 걸린 시간

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

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

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

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

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

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


그림 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>을 지정할 수 있습니다.

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

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이거나 관계없이 해결 방법은 개체를 지연 초기화(즉시 필요한 개체만 초기화)하는 것입니다. 예를 들어 전역 정적 개체를 만드는 대신 싱글턴 패턴으로 이동하여 앱이 개체에 처음 액세스할 때만 개체를 초기화하세요. 또한 개체를 만드는 Dagger와 같은 종속성 삽입 프레임워크를 사용해보세요. 개체가 처음으로 삽입되는 때가 종속성입니다.

과도한 활동 초기화

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

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

문제 진단

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

메서드 추적

CPU 프로파일러를 사용하는 경우 앱의 Application 서브클래스 생성자와 com.example.customApplication.onCreate() 메서드에 주의를 기울이세요.

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

인라인 추적

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

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

문제해결 방법

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

  • 뷰 계층구조가 클수록 앱이 확장하는 데 더 많은 시간이 걸립니다. 이 문제를 해결하기 위해 취할 수 있는 두 가지 단계는 다음과 같습니다.
    • 중복되거나 중첩된 레이아웃을 줄여 뷰 계층구조를 평평하게 합니다.
    • UI 중 시작할 때 표시할 필요가 없는 부분은 확장하지 않습니다. 대신 ViewStub 개체를 앱이 더 적절한 시간에 확장할 수 있는 하위 계층구조의 자리표시자로 사용합니다.
  • 기본 스레드에서 모든 리소스 초기화를 실행하면 시작 속도가 느려질 수 있습니다. 이 문제는 다음과 같이 해결할 수 있습니다.
    • 앱이 다른 스레드에서 느리게 실행할 수 있도록 모든 리소스 초기화를 이동합니다.
    • 앱이 뷰를 로드 및 표시한 다음 나중에 비트맵 또는 다른 리소스에 종속되는 시각적 속성을 업데이트할 수 있도록 허용합니다.

테마가 설정된 시작 화면

앱의 로드 환경을 테마로 설정하여 앱의 시작 화면이 시스템 테마가 아닌 앱의 나머지 부분의 주제와 일치하도록 할 수 있습니다. 이렇게 하면 느린 활동 시작이 숨겨질 수 있습니다.

테마가 설정된 시작 화면을 구현하는 일반적인 방법은 windowDisablePreview 테마 속성을 사용하여 시스템 프로세스에서 앱을 시작할 때 그리는 초기 빈 화면을 끄는 것입니다. 하지만 이 방법을 사용하면 미리보기 창을 표시하지 않는 앱보다 시작 시간이 길어질 수 있습니다. 또한 활동이 시작되는 동안 아무런 피드백 없이 사용자를 기다리게 하여 앱이 제대로 작동하는지 사용자가 궁금해하도록 만듭니다.

문제 진단

종종 사용자가 앱을 시작할 때 느린 응답을 관측하여 이 문제를 진단할 수 있습니다. 이 경우 화면이 정지되거나 입력에 대한 응답이 중지된 것으로 보일 수도 있습니다.

문제해결 방법

미리보기 창을 사용 중지하는 대신 일반적인 머티리얼 디자인 패턴을 따르는 것이 좋습니다. 활동의 windowBackground 테마 속성을 사용하여 시작 활동에 간단한 맞춤 드로어블을 제공할 수 있습니다.

예를 들어 새로운 드로어블 파일을 만들고 다음과 같이 레이아웃 XML 및 앱 manifest 파일에서 참조할 수 있습니다.

레이아웃 XML 파일:

    <layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
      <!-- The background color, preferably the same as your normal theme -->
      <item android:drawable="@android:color/white"/>
      <!-- Your product logo - 144dp color version of your app icon -->
      <item>
        <bitmap
          android:src="@drawable/product_logo_144dp"
          android:gravity="center"/>
      </item>
    </layer-list>
    

manifest 파일:

    <activity ...
    android:theme="@style/AppTheme.Launcher" />
    

일반 테마로 다시 전환하는 가장 쉬운 방법은 setTheme(R.style.AppTheme)super.onCreate()setContentView()를 호출하기 전에 호출하는 것입니다.

Kotlin

    class MyMainActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            // Make sure this is before calling super.onCreate
            setTheme(R.style.Theme_MyApp)
            super.onCreate(savedInstanceState)
            // ...
        }
    }
    

자바

    public class MyMainActivity extends AppCompatActivity {
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        // Make sure this is before calling super.onCreate
        setTheme(R.style.Theme_MyApp);
        super.onCreate(savedInstanceState);
        // ...
      }
    }