와탭랩스 블로그 오픈 이벤트 😃
자세히 보기
테크
2024-12-12
안드로이드 앱 성능, Android Vitals로 확인하기
안드로이드 바이탈

앱의 성능은 사용자 경험과 이탈률에 직접적인 영향을 미칩니다. 특히, 로딩 시간과 앱 충돌은 사용자 이탈을 유발하는 주요 요인인데요.

로딩 시간에 따른 이탈률:

  • 3초 이상 지연: 사용자의 43%가 이를 용납할 수 없다고 응답했습니다.
  • 5초 이상 지연: 이탈률이 90%로 증가합니다.
  • 6초 이상 지연: 이탈률이 106%로 증가합니다.
  • 10초 이상 지연: 이탈률이 123%로 증가합니다.

고객은 느린 로딩 시간뿐만 아니라 앱이 갑자기 종료될 경우에도 높은 이탈률을 보입니다. 통계에 따르면, 앱이 꺼지는 상황에서 사용자의 70%가 즉시 앱을 떠난다고 합니다.

이러한 통계는 앱의 성능 최적화가 사용자 이탈율과 연결되고, 이는 곧 매출로 연결되는 중요한 포인트 중 하나임을 보여주는데요. 개발자는 이러한 성능을 관리해야 하는 점은 알지만, 어떠한 지표를 중점적으로 확인해야 할지 고민하는 경우가 많습니다.

이번 포스팅에서는 구글이 제안하는 모바일 성능지표인 Android Vitals를 활용하여, 앱의 성능을 효율적으로 관리하기 위해 주목해야 할 핵심 지표들을 살펴보겠습니다.  

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를 지원하고 있으니 참고하시기 바랍니다.

Crash

앞서 언급했듯이, 앱이 어떠한 문제로 인해 종료 된다면, 고객은 불편함을 느끼고 앱을 더이상 사용하지 않을텐데요.

특히 안드로이드에서는 엑티비티뿐만 아니라, 백그라운드에서 실행되는 브로드 캐스트 리시버나, 컨텐트 프로바이더와 같은 안드로이드 구성요소에서도 크래시가 발생할 수 있습니다. 또한, 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 통계을 측정하는 것도 중요합니다. 주요 지표로는 다음과 같은 항목들이 있습니다.

  • 일일 사용자별 Crash 발생수
  • 다중 Crash 경험 사용자수 : 2번 이상 Crash를 경험한 유저

특히 Google Play의 경우에는 다음 임계치를 넘으면 경고가 발생하기도 하니 Crash를 잘 관리해야 할 필요가 있습니다.

  • 전반적인 잘못된 동작 임계값: 모든 기기 모델에서 일일 활성 사용자의 최소 1.09%가 사용자가 감지할 수 있는 충돌을 경험합니다.
  • 기기당 잘못된 동작 임계값: 단일 기기 모델에 대해
    일일 활성 사용자의 최소 8%가 사용자가 감지할 수 있는 충돌을 경험합니다 .

ANR

key

안드로이드 핸드폰을 사용하시는 분들은 아마 한 번쯤 다음과 같은 화면을 경험하셨을 것입니다. 안드로이드의 앱의 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은 다음과 같은 패턴에서 발생하게 됩니다.

  • 앱이 메인 스레드에서 I/O와 관련된 느린 작업을 수행할 때
  • 앱이 메인 스레드에서 긴 계산을 수행할 때
  • 메인 스레드가 다른 프로세스에 동기식 바인더 호출을 하고, 해당 프로세스가 반환하는 데 시간이 오래 걸릴 때
  • 메인스레드와 다른 스레드로 인해 대기 중이거나 데드락에 빠졌을 때

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에서 문제가 발생했음을 확인할 수 있습니다

App Startup Time

key

너무나 당연한 이야기이지만 사용자는 화면이 빨리 뜨길 원할텐데요. 반대로, 느리게 뜨는 경우에 사용자가 앱을 삭제할 확률이 높아진다는 점은 앞서 설명한 바 있습니다.

사용자가 앱의 성능이 빠르거나 느리다고 인식하는 가장 기본적인 기준은 화면 로딩 시간입니다. 빠르게 앱을 시작시키고 화면을 로딩해야 하지만, 대체 얼마나 느려야 느린 것이고, 얼마나 빨라야 빠른 것일까요?

화면 로딩 시간을 측정하는 일은 생각보다 단순하지 않습니다. 여러 가지 상황과 케이스가 존재하기 때문인데요. 특히, 다양한 앱 시작 타입이 있기 때문에 그에 따라 다르게 측정될 수 있습니다.

key

앱 시작에는 Hot, Warm, Cold 스타트 타입이 있습니다. 이중 전체를 포함하는 Cold 스타트의 성능을 잘 개선한다면 나머지 Warm, Hot 스타트 성능도 함께 개선될 수 있습니다.

앱을 처음 시작하게 되면 Cold 스타트가 시작 되는데, 이 과정에서 OS는 여러 작업을 수행하고 앱 실행을 위한 프로세스를 시작합니다. 하지만 개발자가 성능을 개선할 수 있는 작업은 다음과 같은 순서로 이루어집니다.

  • Application.onCreate
  • MainActivity.onCreate
  • MainActivity.onStart
key

따라서 각 구간을 정확하게 트레이싱하여 시간을 단축시키는 일이 매우 중요합니다.

앱이 시작될 때, 보안프로그램이나 다양한 외부 데몬들을 처음 실행하는 데 너무 오랜 시간이 걸리고 있지 않은지, 액티비티가 실행될 때 너무 무겁고 많은 데이터를 초기에 불러오고 있진 않은지 등 여러분들의 앱에서 발생하는 성능 문제들을 꼼꼼하게 모니터링하고, 동기/비동기 처리, 캐싱/페이징 등을 통해 시간을 줄여 나가는 것이 필요합니다.

Slow Rendering

고객은 60프레임, 즉 16ms 이내에 프레임을 렌더링해야 화면이 자연스럽게 진행된다고 느낍니다.

16ms 이상 걸리면 느린 프레임으로 구분하고, 700ms 이상이면 프레임이 멈춘 것으로, 5초 이상 지연되면 ANR이 발생하게 되는 것입니다. 프레임이 늦어지면 늦게 표시되는 것이 아니라 프레임이 삭제됩니다. 이로 인해  사용자는 화면의 움직임에 끊김을 느끼게 되는 것인데요. 이를 jank라고 합니다.

Android Vitals에서는 jank를 모니터링할 수 있으며, JankStats 라이브러리(링크)를 사용하면 jank와 관련된 수치들을 확인할 수 있습니다.

하지만 무엇보다 프레임 렌더링은 디바이스 성능과 관련되어 있는데요. 저성능 디바이스에서 문제가 발생할 확률이 높기 때문에 실제 저성능 디바이스나, 에뮬레이터 옵션을 통해 성능을 낮춰서 테스트 해보는 것이 좋습니다. 실제 모니터링 시에도 단순히 jank 값만 확인하는 것이 아니라 디바이스나, 네트워크 상황을 함께 보시면 좋습니다.

Battery

배터리와 성능에는 밀접한 관련이 있습니다. 특히 모바일 디바이스에서는 그 연관이 매우 큽니다. 기본적으로 배터리는 발열과 가장 큰 연관이 있는 장비이기 때문에 배터리를 과도하게 소모할 경우 발열이 발생하여 열을 식히기 위해 OS에서 성능을 강제로 낮추는 경우가 있습니다

또한, 배터리가 얼마 남지 않은 경우 배터리 절약모드나 저전력 모드가 활성화되며, 이로 인해 처리 속도나 그래픽 처리 성능이 제한되어 앱이 느려지는 경우가 있습니다. 따라서 앱에서는 과도한 배터리 사용을 관리할 필요가 있습니다. 개발자는 다음과 같은 배터리 소모 요소를 컨트롤해야 합니다.

  • Excessive wakeups 
  • Excessive Wi-Fi Scanning in the Background
  • Excessive Mobile Network Usage in Background

불필요한 앱 알림, Wi-Fi 스캔, 네트워크 요청이 많으면 배터리를 많이 소모하게 됩니다. CPU나 디바이스의 모듈을 많이 사용할수록 배터리가 소모됩니다. 효과적인 비즈니스 설계를 통해 위와 같은 케이스를 최대한 줄이는 것이 배터리 소모를 줄이는 방법입니다.

배터리 사용량 자체를 모니터링하기 보다는 위와 같은 케이스를 모니터링하고 최적화하는 것이 더 중요합니다.

마치며

이번 포스팅에서는 Android Vitals를 통해 모바일 성능을 관리하기 위해 어떤 지표를 수집해야 하는지 알아봤습니다. Google에서 제안하는 안드로이드의 성능 지표인 만큼 Google Play Store에 앱을 배포하면 기본적인 지표들을 확인하실 수 있기 때문에 직접 개발자가 해당 지표들을 수집하는 노력이 필요하진 않지만, 자세한 분석이 필요할 경우 모바일 모니터링 도구를 사용하는 것이 좋습니다.

대부분의 모바일 모니터링 도구는 Android Vitals를 기반으로 지표를 수집하므로, 앱의 퀄리티를 높이기 위해 모니터링 도구를 활용해보는 것도 좋은 선택입니다. 여러분의 앱 퀄리티, Android Vitals를 통해 확인해보세요.

와탭 모니터링을 무료로 체험해보세요!