앱 시작 시간

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

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

다양한 앱 시작 상태 이해

앱은 콜드 스타트, 웜 스타트, 핫 스타트라는 세 가지 상태 중 하나에서 시작합니다. 각 상태는 앱이 사용자에게 표시되는 데 걸리는 시간에 영향을 미칩니다. 콜드 스타트에서는 앱이 처음부터 시작됩니다. 다른 두 상태에서는 시스템이 실행 중인 앱을 백그라운드에서 포그라운드로 가져와야 합니다.

항상 콜드 스타트를 가정하여 최적화하는 것이 좋습니다. 이렇게 하면 웜 스타트와 핫 스타트의 성능도 개선될 수 있습니다.

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

앱 시작을 결정하는 두 가지 중요한 측정항목은 처음 표시하는 데 걸린 시간(TTID)완전히 표시하는 데 걸린 시간 (TTFD)입니다. TTID는 첫 번째 프레임을 표시하는 데 걸리는 시간이고 TTFD는 앱이 완전히 상호작용할 수 있게 되는 데 걸리는 시간입니다. TTID는 사용자에게 앱이 로드되고 있음을 알려주고 TTFD는 앱을 실제로 사용할 수 있음을 의미하기 때문에 둘 다 중요합니다. 둘 중 하나라도 너무 길면 앱이 완전히 로드되기 전에 사용자가 앱을 종료할 수 있습니다.

콜드 스타트

콜드 스타트는 앱이 처음부터 시작하는 것을 의미합니다. 콜드 스타트 이전에는 시스템의 프로세스에서 앱의 프로세스를 만들지 않습니다. 기기가 부팅되거나 시스템에서 앱이 종료되고 난 후 앱이 처음으로 시작되는 경우 등에서 콜드 스타트가 발생합니다.

이 같은 시작 유형에서는 시스템과 앱이 다른 시작 상태보다 더 많은 작업을 실행해야 하므로 시작 시간을 최소화하는 데 가장 큰 어려움이 있습니다.

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

  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. 이 다이어그램은 다양한 시작 상태와 각 프로세스를 보여주며 각 상태는 그려진 첫 번째 프레임부터 시작됩니다.

Perfetto에서 앱 시작을 식별하는 방법

앱 시작 문제를 디버그하려면 앱 시작 단계에 정확히 무엇이 포함되어 있는지 확인하는 것이 도움이 됩니다. Perfetto에서 전체 앱 시작 단계를 식별하려면 다음 단계를 따르세요.

  1. Perfetto에서 Android App Startups 파생 측정항목이 있는 행을 찾습니다. 표시되지 않으면 온디바이스 시스템 추적 앱을 사용하여 트레이스를 캡처해 보세요.

    그림 3.Perfetto의 Android App Startups 파생 측정항목 슬라이스
  2. 연결된 슬라이스를 클릭하고 m 키를 눌러 슬라이스를 선택합니다. 슬라이스 주위에 대괄호가 표시되고 소요 시간이 표시됩니다. 현재 선택 탭에도 시간이 표시됩니다.

  3. 행 위에 포인터를 올려놓으면 표시되는 고정 아이콘을 클릭하여 Android App Startups 행을 고정합니다.

  4. 문제의 앱이 있는 행으로 스크롤하고 첫 번째 셀을 클릭하여 행을 펼칩니다.

  5. 일반적으로 맨 위에 있는 w를 눌러 기본 스레드를 확대합니다(s, a, d를 눌러 각각 축소, 왼쪽으로 이동, 오른쪽으로 이동).

    그림 4. 앱의 기본 스레드 옆에 있는 Android App Startups 측정항목 슬라이스
  6. 파생 측정항목 슬라이스를 사용하면 앱 시작에 정확히 무엇이 포함되어 있는지 더 쉽게 확인할 수 있으므로 계속해서 더 자세히 디버깅할 수 있습니다.

측정항목을 사용하여 스타트업 검사 및 개선

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

시작 측정항목 활용의 이점

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

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

Android vitals

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

Android vitals에서는 다음과 같은 경우 앱의 시작 시간이 너무 긴 것으로 간주합니다.

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

Android vitals는 처음 표시하는 데 걸린 시간(TTID) 측정항목을 사용합니다. Google Play에서 Android vitals 데이터를 수집하는 방법에 관한 자세한 내용은 Play Console 문서를 참고하세요.

처음 표시하는 데 걸린 시간

처음 표시하는 데 걸린 시간 (TTID)은 앱 UI의 첫 번째 프레임을 표시하는 데 걸리는 시간입니다. 이 측정항목은 콜드 스타트 중 프로세스 초기화, 콜드 또는 웜 스타트 중 활동 생성, 첫 번째 프레임 표시 등 앱이 첫 프레임을 생성하는 데 걸리는 시간을 측정합니다. 앱의 TTID를 낮게 유지하면 사용자가 앱 실행을 빠르게 확인할 수 있어 사용자 환경을 개선하는 데 도움이 됩니다. TTID는 Android 프레임워크에 의해 모든 앱에 대해 자동으로 보고됩니다. 앱 시작에 최적화할 때는 최대 TTFD의 정보를 가져오도록 reportFullyDrawn를 구현하는 것이 좋습니다.

TTID는 다음과 같은 이벤트 시퀀스가 포함된 총 경과 시간을 나타내는 시간 값으로 측정됩니다.

  • 프로세스 시작
  • 객체 초기화
  • 활동 만들기 및 초기화
  • 레이아웃 확장
  • 앱을 처음으로 그리는 경우

TTID 검색

TTID를 찾으려면 Logcat 명령줄 도구에서 Displayed라는 값이 포함된 출력 줄을 검색합니다. 이 값은 TTID이며 TTID가 3s534ms인 다음 예와 유사합니다.

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

Android 스튜디오에서 TTID를 찾으려면 필터 드롭다운에서 Logcat 뷰의 필터를 사용 중지한 다음 그림 5와 같이 Displayed 시간을 찾습니다. 앱 자체가 아니라 시스템 서버에서 이 로그를 제공하므로 필터를 사용 중지해야 합니다.

그림 5. logcat에서 필터 및 Displayed 값 사용 중지됨

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

때때로 Logcat 출력의 Displayed 행에 총시간에 관한 추가 필드가 포함되어 있습니다. 예:

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

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

Android 스튜디오에서 Logcat을 사용하는 것이 좋지만 Android 스튜디오를 사용하지 않는 경우에는 adb 셸 활동 관리자 명령어로 앱을 실행하여 TTID를 측정할 수도 있습니다. 예를 들면 다음과 같습니다.

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)은 앱이 사용자와 상호작용할 수 있게 되는 데 걸리는 시간입니다. 앱 UI의 첫 번째 프레임과 초기 프레임이 표시된 후 비동기식으로 로드되는 콘텐츠를 표시하는 데 걸리는 시간으로 보고됩니다. 일반적으로 앱에서 보고한 대로 네트워크나 디스크에서 로드된 기본 콘텐츠입니다. 즉, TTFD에는 TTFD와 앱을 사용할 수 있게 되기까지 걸리는 시간이 포함됩니다. 앱의 TTFD를 낮게 유지하면 사용자가 앱과 빠르게 상호작용할 수 있어 사용자 환경이 개선됩니다.

시스템은 Choreographer가 활동의 onDraw() 메서드를 호출할 때 및 이 메서드를 처음으로 호출하는 것을 알고 있을 때 TTID를 결정합니다. 그러나 시스템은 모든 앱이 다르게 동작하므로 TTFD를 언제 판단해야 할지 알 수 없습니다. TTFD를 확인하려면 앱은 완전히 그려진 상태에 도달했을 때 시스템에 신호를 보내야 합니다.

TTFD 검색

TTFD를 찾으려면 ComponentActivityreportFullyDrawn() 메서드를 호출하여 완전히 그려진 상태를 알립니다. reportFullyDrawn 메서드는 앱이 완전히 그려지고 사용 가능한 상태가 되는 시점을 보고합니다. TTFD는 시스템이 앱 실행 인텐트를 수신한 시점부터 reportFullyDrawn()가 호출되는 시점까지 경과된 시간입니다. reportFullyDrawn()를 호출하지 않으면 TTFD 값이 보고되지 않습니다.

TTFD를 측정하려면 UI와 모든 데이터를 완전히 그린 후 reportFullyDrawn()를 호출합니다. 시스템에서 측정한 대로 첫 번째 활동의 창이 처음 그려지고 표시되기 전에 reportFullyDrawn()를 호출하면 안 됩니다. 시스템에서 측정한 시간을 보고하기 때문입니다. 즉, 시스템이 TTID를 감지하기 전에 reportFullyDrawn()를 호출하면 시스템은 TTID와 TTFD를 모두 동일한 값으로 보고하며, 이 값은 TTID 값이 됩니다.

reportFullyDrawn()를 사용하면 Logcat에 다음 예와 같은 출력이 표시됩니다. 여기서 TTFD는 1s54ms입니다.

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

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

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

완전히 그려진 상태가 달성되었다는 것을 알고 있는 기본적인 사례에서 reportFullyDrawn()를 사용하여 완전히 그려진 상태를 알릴 수 있습니다. 그러나 완전히 그려진 상태가 되기 전에 백그라운드 스레드가 백그라운드 작업을 완료해야 하는 경우 더 정확한 TTFD 측정을 위해 reportFullyDrawn()를 지연해야 합니다. reportFullyDrawn() 지연 방법을 알아보려면 다음 섹션을 참고하세요.

시작 시간 정확성 개선

앱이 지연 로드를 실행하고 초기 디스플레이에 모든 리소스가 포함되어 있지 않은 경우(예: 앱이 네트워크에서 이미지를 가져오는 경우) 벤치마크 타이밍의 일부로 목록 채우기를 포함할 수 있도록 앱을 사용할 수 있게 될 때까지 reportFullyDrawn 호출을 지연하는 것이 좋습니다.

예를 들어 UI에 RecyclerView 또는 지연 목록과 같은 동적 목록이 포함되어 있으면 목록이 처음 그려진 후에 완료되는, 즉 UI가 완전히 그려진 것으로 표시된 후에 완료되는 백그라운드 작업으로 채워질 수 있습니다. 이 경우 벤치마킹에 목록 채우기가 포함되지 않습니다.

벤치마크 타이밍의 일부로 목록 채우기를 포함하려면 getFullyDrawnReporter()를 사용하여 FullyDrawnReporter를 가져오고 앱 코드에서 여기에 리포터를 추가합니다. 백그라운드 작업이 목록 채우기를 완료하면 리포터를 해제합니다.

FullyDrawnReporter는 추가된 모든 리포터를 해제할 때까지 reportFullyDrawn() 메서드를 호출하지 않습니다. 백그라운드 프로세스가 완료될 때까지 리포터를 추가하면 시작 타이밍 데이터에 목록을 채우는 데 걸리는 시간도 타이밍에 포함됩니다. 이렇게 해도 사용자를 위한 앱 동작은 변경되지 않지만 타이밍 시작 데이터에 목록을 채우는 데 걸리는 시간이 포함됩니다. reportFullyDrawn()는 순서와 관계없이 모든 작업이 완료될 때까지 호출되지 않습니다.

다음 예는 각각 자체 보고자를 등록하여 여러 백그라운드 작업을 동시에 실행하는 방법을 보여줍니다.

Kotlin

class MainActivity : ComponentActivity() {

    sealed interface ActivityState {
        data object LOADING : ActivityState
        data object LOADED : ActivityState
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            var activityState by remember {
                mutableStateOf(ActivityState.LOADING as ActivityState)
            }
            fullyDrawnReporter.addOnReportDrawnListener {
                activityState = ActivityState.LOADED
            }
            ReportFullyDrawnTheme {
                when(activityState) {
                    is ActivityState.LOADING -> {
                        // Display the loading UI.
                    }
                    is ActivityState.LOADED -> {
                        // Display the full UI.
                    }
                }
            }
            SideEffect {
                lifecycleScope.launch(Dispatchers.IO) {
                    fullyDrawnReporter.addReporter()

                    // Perform the background operation.

                    fullyDrawnReporter.removeReporter()
                }
                lifecycleScope.launch(Dispatchers.IO) {
                    fullyDrawnReporter.addReporter()

                    // Perform the background operation.

                    fullyDrawnReporter.removeReporter()
                }
            }
        }
    }
}

Java

public class MainActivity extends ComponentActivity {
    private FullyDrawnReporter fullyDrawnReporter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        fullyDrawnReporter = getFullyDrawnReporter();
        fullyDrawnReporter.addOnReportDrawnListener(() -> {
            // Trigger the UI update.
            return Unit.INSTANCE;
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                fullyDrawnReporter.addReporter();

                // Do the background work.

               fullyDrawnReporter.removeReporter();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                fullyDrawnReporter.addReporter();

                // Do the background work.

                fullyDrawnReporter.removeReporter();
            }
        }).start();
    }
}

앱에서 Jetpack Compose를 사용하는 경우 다음 API를 사용하여 완전히 그려진 상태를 나타낼 수 있습니다.

  • ReportDrawn: 컴포저블이 즉시 상호작용할 준비가 되었음을 나타냅니다.
  • ReportDrawnWhen: 컴포저블이 상호작용할 준비가 되었음을 나타내기 위해 조건자(예: list.count > 0)를 사용합니다.
  • ReportDrawnAfter: 완료되면 컴포저블이 상호작용할 준비가 되었음을 나타내는 정지 메서드를 사용합니다.
병목 현상 식별

병목 현상을 찾으려면 Android 스튜디오 CPU 프로파일러를 사용하면 됩니다. 자세한 내용은 CPU 프로파일러를 사용하여 CPU 활동 검사를 참고하세요.

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

일반적인 문제 해결하기

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

과도한 앱 초기화

코드가 Application 객체를 재정의하거나, 객체를 초기화할 때 과도한 작업 또는 복잡한 로직을 실행하면 시작 성능에 문제가 발생할 수 있습니다. Application 서브클래스에서 아직 실행할 필요가 없는 초기화를 실행하는 경우 앱이 시작하는 동안 시간을 허비할 수 있습니다.

일부 초기화는 완전히 불필요합니다. 예를 들어 앱이 인텐트에 따라 실제로 시작할 때 기본 활동의 상태 정보를 초기화하는 경우가 이에 해당합니다. 인텐트에 따라 앱에서는 이전에 초기화된 상태 데이터의 하위 집합만을 사용합니다.

앱을 초기화하는 동안 발생하는 다른 문제로는 영향력이 있거나 다양한 가비지 컬렉션 이벤트, 초기화와 동시에 발생하여 초기화 프로세스를 추가로 차단하는 디스크 I/O 등이 있습니다. Dalvik 런타임의 경우 특히 가비지 컬렉션을 고려해야 합니다. Android 런타임(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 테마 속성을 사용하여 시작 시 시스템에서 그린 초기 빈 화면을 끕니다.
  • 전용 Activity를 사용합니다.

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

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

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