brunch

You can make anything
by writing

C.S.Lewis

by 이승현 Apr 10. 2017

App Launch time 101

Launch-Time Performance

Launch-Time Performance



당연히 사용자는 시작이 빠른 앱을 기대합니다.

만약 기대와 달리 시작이 느리다면 앱 사용률도 낮아지게 됩니다.


앱 시작 시간을 최적화하기 위한 몇 가지 방법을 정리했습니다.




Launch Internals


빠른 실행을 위해 앱을 최적화하려면 우선 시스템 및 앱 레벨에서 어떤 일이 벌어지고 있는지, 그리고 어떻게 상호작용하는지 이해해야 합니다.


시작 시간(사용자가 앱을 볼 수 있을 때까지 걸리는 시간)에 따라 앱 시작을 3가지로 나눌 수 있습니다.


#01 Launch Internals



#01 Cold Start


Cold Start 란 앱이 처음부터 시작되는 것을 말합니다.

기기 부팅 후 앱이 처음 실행되거나, 시스템이 앱을 종료한 후 다시 실행하는 경우입니다.

다른 시작들 보다 수행해야 할 작업이 많기에 시작 시간을 줄이기 위한 공수가 더 들어갑니다.


Cold Start 시 기술적인 측면에서 볼 때 다음과 같이 내부적으로 동작합니다.

#02 Launch process


시스템에서는 3가지 작업을 수행합니다.

1. 앱 로드 및 실행 (Load & Launch Application)
2. 실행 직 후 시작 화면(빈 화면) 표시 (Display start window)
3. 앱 프로세스 만들기 (Creating the app process)


시스템이 앱 프로세스를 생성하자마자 앱 프로세스가 다음 단계를 담당합니다.

4. 앱 객체 만들기 (Creating the app object)
5. 메인 스레드를 시작 (Launching the main thread)
6. 메인 액티비티 만들기 (Creating the main activity)
7. Inflating views
8. 화면 배치 (Laying out the screen)
9. 초기 draw 수행 (Performing the initial draw)


앱 프로세스가 초기 draw를 수행 완료하면, 

10. 현재 표시된 시작 화면과 앱의 메인 액티비티를 교체(Swap start window for app)
11. 이후 사용자는 앱을 사용




#02 Warm Start


Warm Start는 Cold Start보다 훨씬 단순하고 오버 헤드가 적습니다.

Warm Start는 처음부터 시작하는 Cold Start와 달리 액티비티가 메모리에 상주했기 때문에 시스템에서는 단순히 액티비티를 foreground로 가져오고 객체 초기화, 레이아웃 배치 및 렌더링을 반복하지 않아도 됩니다.


만약 메모리 부족과 같은 시스템에 의해 일부 객체가 제거된 경우에는 Warm Start에서 재생성해야 합니다.

그리고 Cold Start와 동일하게 앱 프로세스가 초기 draw를 수행할 때까지 빈 화면을 표시합니다.




#03 Lukewarm Start


Lukewarm Start는 Cold Start 중 발생할 수 있지만 Warm Start보다 오버헤드가 적습니다.

예를 들면 사용자가 앱을 종료 후 곧바로 다시 시작할 때나(empty process), 메모리 부족으로 시스템이 앱을 다시 시작할 때 onCreate를 통해 bundle 값을 전달받은 경우입니다.

Empty process
- 종료되었지만 메모리에 상주해 있는 프로세스. 가장 낮은 우선순위를 가진 프로세스 




Profiling Launch Performance


내부 동작 이해를 바탕으로 앱의 시작 시간 성능을 판단하기 위한 몇 가지 방법이 있습니다.



#01 Time to initial display


#03 Displayed logcat


Android 4.4 (API 레벨 19) 이상부터 Logcat은 프로세스 시작 후 해당 액티비티가 화면에 그려질 때까지 경과한 시간을 나타내는 Displayed 값을 보여줍니다.

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


Displayed 값은 아래와 같은 이벤트들을 모두 포함한 값입니다.

1. 프로세스 실행 (Launch the process)
2. 객체 초기화 (Initialize the objects)
3. 액티비티 생성, 초기화 (Create and initialize the activity)
4. Inflate the layout
5. Draw your application for the first time




#02 Time to full display


#04Time to full display


Logcat에서 Displayed 값을 통해 앱을 시작해서 처음 사용자에게 노출될 때까지 걸리는 시간을 측정할 수 있지만, 이후 앱의 모든 리소스와 뷰 계층 구조를 업데이트하거나 비동기적으로 처리하는 경우에는 측정할 수가 없습니다.


#05 Fully drawn logcat


이 문제를 해결하기 위해 개발자가 직접 activity.reportFullyDrawn() api를 호출하여 액티비티가 완료되었음을 시스템에 알려줘야 합니다.

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


#06 reportFullyDrawn()





#03 Tracing


Displayed 값이나 reportFullyDrawn() api를 통해 전반적인 시작 시간을 파악할 수 있지만 어느 부분에서 얼마나 시간이 소모되는지는 자세히 나오지 않습니다.

좀 더 자세히 파악하기 위해서는 2가지 도구를 이용할 수 있습니다.


1. Android Studio의 Method Tracer
2. 인라인 추적 (TraceSystrace)





Common Problems


앱의 시작 시간 성능에 영향을 미치는 여러 가지 문제가 있습니다.


#01 Heavy app initialization


#07 Heavy app initialization


Application 객체를 초기화할 때, 복잡하고 무거운 작업들을 수행한다면 시작 시간 성능에 영향을 줄 수 있습니다. 그리고 초기화하는 동안 발생하는 수많은 이벤트(GC 이벤트...)들도 영향을 미칠 수 있습니다.


앱이 커지고 복잡해질수록 개발의 편리성을 위해 Custom Application 객체를 global static object처럼 쓰는 경우가 많은데, 사실 일부 기능은 꼭 Application 객체에서 초기화하거나 수행할 필요가 없습니다.


불필요한 초기화를 줄이고, global static objects 보다는 singleton pattern을 이용해서 꼭 필요한 작업만 수행하도록 해야 합니다.





#02 Heavy activity initialization


#08 Heavy activity initialization


Activity에서는 Application 객체 보다 시작 시간 성능에 영향을 줄 수 있는 문제가 아래와 같이 많이 발생할 수 있습니다.

1. 크고 복잡한 레이아웃 (Inflating large or complex layouts.)
2. 디스크나 네트워크 I/O의 화면 차단 (Blocking screen drawing on disk, or network I/O.)
3. 비트맵 로딩, 디코딩 (Loading and decoding bitmaps.)
4.Rasterizing VectorDrawable objects.
5. 다른 서브시스템 초기화 (Initialization of other subsystems of the activity.)


View 계층 구조가 클수록 앱이 화면을 inflate 하는 데 더 많은 시간이 소요됩니다.

이 문제는 다음과 같이 해결할 수 있습니다.

중복 또는 중첩된 레이아웃을 줄임으로써 뷰 계층을 평평하게 합니다.

실행 중에 표시할 필요가 없는 UI 부분에서 ViewStub class를 활용할 수 있습니다. 


메인 스레드에서 리소스 초기화를 모두 수행하면 시작 속도가 느려질 수 있습니다.

이 문제는 다음과 같이 해결할 수 있습니다.

모든 리소스 초기화를 다른 스레드에서 수행하도록 합니다.

일단 View를 로드하고 표시한 다음, 나중에 비트맵 및 기타 리소스를 이용해 업데이트해줍니다.




#03 Themed launch screens


#09 Themed launch screens


앱이 사용자에게 노출될 때까지 시스템에서 기본적으로 제공하는 시작 화면(빈 화면)을 아래와 같이 비활성화시킬 수 있습니다. 하지만 이 경우 프로세스가 초기화되는 동안 아무런 반응이 없어서 사용자가 느끼기에 앱 실행이 느리다고 느낄 수 있어 좋은 방법은 아닙니다.

저는 써본 적은 없는데 게임에서는 쓰이는 경우가 있다고 하네요.


#09 windowDisablePreview


시작 화면을 비활성화하는 대신 머티리얼 디자인 패턴을 따르는 것이 좋습니다.

예를 들어 새로운 drawalbe 파일을 만들고 XML이나 매니페스트 파일에서 다음과 같이 참조할 수 있습니다.


#10-1 res/drawable/launch.xml
10-2 res/values/styles.xml
10-3 MainActivity.java





이 외에 자세한 내용은 아래 링크를 참조해 주시기 바랍니다.


https://developer.android.com/topic/performance/launch-time.html


http://yifeng.studio/2016/11/15/android-optimize-for-cold-start/





앱 시작 시간을 줄이기 위해 이런 저런 노력을 했었는데, 결국 start activity나 custom application을 최대한 단순화 시켜야 해야하네요.



작가의 이전글 Droid Knights 후기
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari