앱의 성능은 사용자 경험과 이탈률에 직접적인 영향을 미칩니다. 특히, 로딩 시간과 앱 충돌은 사용자 이탈을 유발하는 주요 요인인데요.
로딩 시간에 따른 이탈률:
고객은 느린 로딩 시간뿐만 아니라 앱이 갑자기 종료될 경우에도 높은 이탈률을 보입니다. 통계에 따르면, 앱이 꺼지는 상황에서 사용자의 70%가 즉시 앱을 떠난다고 합니다.
이러한 통계는 앱의 성능 최적화가 사용자 이탈율과 연결되고, 이는 곧 매출로 연결되는 중요한 포인트 중 하나임을 보여주는데요. 개발자는 이러한 성능을 관리해야 하는 점은 알지만, 어떠한 지표를 중점적으로 확인해야 할지 고민하는 경우가 많습니다.
이번 포스팅에서는 구글이 제안하는 모바일 성능지표인 Android Vitals를 활용하여, 앱의 성능을 효율적으로 관리하기 위해 주목해야 할 핵심 지표들을 살펴보겠습니다.
Android Vitals는 앱의 품질을 개선하기 위해 google play에서 수집하는 성능 지표입니다. 이미 스토어에 앱을 배포하였다면 성능 데이터가 수집되고 있을텐데요.
Android Vitals가 모바일, 또는 범위를 좁혀 안드로이드의 표준 성능 지표는 아니지만, 현재로서는 안드로이드 성능을 대표하는 표준 지표들이 없고, Google이 제안하는 성능지표인 만큼 앱의 성능 품질을 높이는 핵심 요소들이 포함되어 있습니다.
Android Vitals에서는 앱 종료와 같은 케이스와 연관된 Core Vitals와 성능이 느려지거나, 느려질 가능성이 있는 Other Vitals로 구분을 하고 있는데요. 이번 포스팅에서는 개발자가 다뤄야 할 주요 Android Vitals를 소개해 드리겠습니다.
참고로, 이 글은 Google Play Store의 Android Vitals를 보는 방법과는 관련이 없습니다. 성능 지표로서 Android Vitals에 대해 소개하는 글이며, 다양한 모니터링 도구에서 Android Vitals를 지원하고 있으니 참고하시기 바랍니다.
앞서 언급했듯이, 앱이 어떠한 문제로 인해 종료 된다면, 고객은 불편함을 느끼고 앱을 더이상 사용하지 않을텐데요.
특히 안드로이드에서는 엑티비티뿐만 아니라, 백그라운드에서 실행되는 브로드 캐스트 리시버나, 컨텐트 프로바이더와 같은 안드로이드 구성요소에서도 크래시가 발생할 수 있습니다. 또한, Java나 Kotlin으로 개발된 비즈니스 로직뿐만 아니라, native로 개발된 로직에서도 크래시가 발생하는 경우가 많이 있습니다.
무엇보다 Crash가 발생했을 때 어디서 문제가 발생했는 지를 알아야 하는데요.
안드로이드에서는 Crash Stack을 수집함으로써 문제의 원인을 쉽게 찾을 수 있습니다. 만약 여러분들이 직접 Crash가 발생했을때 문제를 찾아보고 싶다면 UncaughtExceptionHandler
를 활용하여 에러를 간단히 잡아낼 수 있습니다.
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// 크래시 로그 수집 로직
Log.e("Crash", "Unhandled exception", e);
// 예외 정보를 다른 서버로 전송하거나 파일로 저장하는 로직
// 예: Whatap, Crashlytics, Sentry, 서버 전송, 파일 저장 등
}
});
Crash Stack을 통해 문제의 원인을 파악하는 것뿐만 아니라, 다양한 Crash 통계을 측정하는 것도 중요합니다. 주요 지표로는 다음과 같은 항목들이 있습니다.
특히 Google Play의 경우에는 다음 임계치를 넘으면 경고가 발생
하기도 하니 Crash를 잘 관리해야 할 필요가 있습니다.
안드로이드 핸드폰을 사용하시는 분들은 아마 한 번쯤 다음과 같은 화면을 경험하셨을 것입니다. 안드로이드의 앱의 UI 스레드가 오래 작업되면 Application Not Responding, 즉 ANR이 발생하게 됩니다.
ANR은 앱의 성능 문제로 발생하는 이슈로, 이로 인해 고객이 앱을 종료할 가능성이 있게 되며, Crash와 같은 문제가 발생하게 됩니다.
하지만, ANR로 인해 고객이 앱을 종료한 경우, OS가 프로세스를 강제로 종료시키기 때문에서 위에서 언급한 UncaughtExceptionHandler
로는 해당 정보를 수집할 수 없습니다.
ANR을 발생시키는 간단한 샘플을 보겠습니다.
package io.whatap.anrsample;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
button.setOnClickListener(v -> {
// 10초 동안 sleep을 호출하여 ANR을 유발
try {
Thread.sleep(10000); // 10초 동안 UI 스레드를 차단
} catch (InterruptedException e) {
e.printStackTrace();
}
// ANR 발생 후 처리할 코드
Toast.makeText(MainActivity.this, "Button clicked!", Toast.LENGTH_SHORT).show();
});
}
}
버튼을 클릭했을때 10,000동안 Sleep을 주게 되면 Click이벤트의 스레드가 10초동안 반응하지 못하고 기다리게 됩니다. 이때, OS가 ANR을 발생시킵니다.
이처럼 ANR은 스레드가 일정 시간 동안 차단될 때 발생하는데요. 모든 스레드가 대상이 아닌 사용자의 반응을 담당하는 UIThread 또는 Main Thread가 ANR의 대상이 됩니다.
대부분의 ANR은 다음과 같은 패턴에서 발생하게 됩니다.
ANR이 발생하면 ANR로그가 발생하게 됩니다. 만약 위의 코드로 ANR이 발생했다면 다음과 같은 로그가 기록됩니다.
--------- beginning of crash
2024-11-21 12:34:56.789 12345-12345/io.whatap.anrsample E/ActivityManager: ANR in io.whatap.anrsample
PID: 12345
Reason: Executing service io.whatap.anrsample/.MainActivity$onCreate$1$1$1.invoke$lambda$10$lambda$9$lambda$8$lambda$7(MainActivity.java:88)
Load: 0.0 / 0.0 / 0.0
CPU usage from 0ms to 20000ms ago:
0% 12345/io.whatap.anrsample: 0% user + 0% kernel / faults: 1000 minor 5 major
"main" prio=5 tid=1 Sleeping
| group="main" sysTid=12345 nice=-2 sched=0/0 cgrp=[default] handle=0x0000000000 state=S schedstat=( 0 0 0 ) utm=1 stm=0 core=1
| stack=0x0000000000 pc=0x0000000000 pid=12345 tid=12345 comm="main" bootTime=1234567890
"main" prio=5 tid=1 Sleeping
| group="main" sysTid=12345 nice=-2 sched=0/0 cgrp=[default] handle=0x0000000000 state=S schedstat=( 0 0 0 ) utm=1 stm=0 core=1
| stack=0x0000000000 pc=0x0000000000 pid=12345 tid=12345 comm="main" bootTime=1234567890
| Sleep for 10 seconds due to Thread.sleep() on the UI thread.
마지막 줄을 통해 Thread.sleep
에서 문제가 발생했음을 확인할 수 있습니다
너무나 당연한 이야기이지만 사용자는 화면이 빨리 뜨길 원할텐데요. 반대로, 느리게 뜨는 경우에 사용자가 앱을 삭제할 확률이 높아진다는 점은 앞서 설명한 바 있습니다.
사용자가 앱의 성능이 빠르거나 느리다고 인식하는 가장 기본적인 기준은 화면 로딩 시간입니다. 빠르게 앱을 시작시키고 화면을 로딩해야 하지만, 대체 얼마나 느려야 느린 것이고, 얼마나 빨라야 빠른 것일까요?
화면 로딩 시간을 측정하는 일은 생각보다 단순하지 않습니다. 여러 가지 상황과 케이스가 존재하기 때문인데요. 특히, 다양한 앱 시작 타입이 있기 때문에 그에 따라 다르게 측정될 수 있습니다.
앱 시작에는 Hot, Warm, Cold 스타트 타입이 있습니다. 이중 전체를 포함하는 Cold 스타트의 성능을 잘 개선한다면 나머지 Warm, Hot 스타트 성능도 함께 개선될 수 있습니다.
앱을 처음 시작하게 되면 Cold 스타트가 시작 되는데, 이 과정에서 OS는 여러 작업을 수행하고 앱 실행을 위한 프로세스를 시작합니다. 하지만 개발자가 성능을 개선할 수 있는 작업은 다음과 같은 순서로 이루어집니다.
따라서 각 구간을 정확하게 트레이싱하여 시간을 단축시키는 일이 매우 중요합니다.
앱이 시작될 때, 보안프로그램이나 다양한 외부 데몬들을 처음 실행하는 데 너무 오랜 시간이 걸리고 있지 않은지, 액티비티가 실행될 때 너무 무겁고 많은 데이터를 초기에 불러오고 있진 않은지 등 여러분들의 앱에서 발생하는 성능 문제들을 꼼꼼하게 모니터링하고, 동기/비동기 처리, 캐싱/페이징 등을 통해 시간을 줄여 나가는 것이 필요합니다.
고객은 60프레임, 즉 16ms 이내에 프레임을 렌더링해야 화면이 자연스럽게 진행된다고 느낍니다.
16ms 이상 걸리면 느린 프레임으로 구분하고, 700ms 이상이면 프레임이 멈춘 것으로, 5초 이상 지연되면 ANR이 발생하게 되는 것입니다. 프레임이 늦어지면 늦게 표시되는 것이 아니라 프레임이 삭제됩니다. 이로 인해 사용자는 화면의 움직임에 끊김을 느끼게 되는 것인데요. 이를 jank라고 합니다.
Android Vitals에서는 jank를 모니터링할 수 있으며, JankStats 라이브러리(링크)를 사용하면 jank와 관련된 수치들을 확인할 수 있습니다.
하지만 무엇보다 프레임 렌더링은 디바이스 성능과 관련되어 있는데요. 저성능 디바이스에서 문제가 발생할 확률이 높기 때문에 실제 저성능 디바이스나, 에뮬레이터 옵션을 통해 성능을 낮춰서 테스트 해보는 것이 좋습니다. 실제 모니터링 시에도 단순히 jank 값만 확인하는 것이 아니라 디바이스나, 네트워크 상황을 함께 보시면 좋습니다.
배터리와 성능에는 밀접한 관련이 있습니다. 특히 모바일 디바이스에서는 그 연관이 매우 큽니다. 기본적으로 배터리는 발열과 가장 큰 연관이 있는 장비이기 때문에 배터리를 과도하게 소모할 경우 발열이 발생하여 열을 식히기 위해 OS에서 성능을 강제로 낮추는 경우가 있습니다
또한, 배터리가 얼마 남지 않은 경우 배터리 절약모드나 저전력 모드가 활성화되며, 이로 인해 처리 속도나 그래픽 처리 성능이 제한되어 앱이 느려지는 경우가 있습니다. 따라서 앱에서는 과도한 배터리 사용을 관리할 필요가 있습니다. 개발자는 다음과 같은 배터리 소모 요소를 컨트롤해야 합니다.
불필요한 앱 알림, Wi-Fi 스캔, 네트워크 요청이 많으면 배터리를 많이 소모하게 됩니다. CPU나 디바이스의 모듈을 많이 사용할수록 배터리가 소모됩니다. 효과적인 비즈니스 설계를 통해 위와 같은 케이스를 최대한 줄이는 것이 배터리 소모를 줄이는 방법입니다.
배터리 사용량 자체를 모니터링하기 보다는 위와 같은 케이스를 모니터링하고 최적화하는 것이 더 중요합니다.
이번 포스팅에서는 Android Vitals를 통해 모바일 성능을 관리하기 위해 어떤 지표를 수집해야 하는지 알아봤습니다. Google에서 제안하는 안드로이드의 성능 지표인 만큼 Google Play Store에 앱을 배포하면 기본적인 지표들을 확인하실 수 있기 때문에 직접 개발자가 해당 지표들을 수집하는 노력이 필요하진 않지만, 자세한 분석이 필요할 경우 모바일 모니터링 도구를 사용하는 것이 좋습니다.
대부분의 모바일 모니터링 도구는 Android Vitals를 기반으로 지표를 수집하므로, 앱의 퀄리티를 높이기 위해 모니터링 도구를 활용해보는 것도 좋은 선택입니다. 여러분의 앱 퀄리티, Android Vitals를 통해 확인해보세요.