Android 앱 성능 분석 완벽 가이드 — Android Studio Profiler, Macrobenchmark, Baseline Profiles 실전 적용

Android 앱을 개발하다 보면 다음과 같은 문제를 경험할 수 있습니다.

  • 앱을 처음 실행할 때 화면이 늦게 표시된다.

  • RecyclerView를 빠르게 스크롤하면 화면이 끊긴다.

  • SQLite 검색 결과가 늦게 나타난다.

  • 이미지가 많은 화면에서 메모리 사용량이 증가한다.

  • Fragment를 전환할 때 일시적으로 화면이 멈춘다.

  • 앱을 오래 사용하면 처음보다 느려지는 느낌이 든다.

이러한 문제를 해결하기 위해 Coroutine을 적용하거나 RecyclerView를 최적화하는 경우가 많습니다.

하지만 코드를 수정했다고 해서 실제로 성능이 개선되었다고 단정할 수는 없습니다.

예를 들어 앱 시작 코드를 변경했더라도 실제 실행 시간이 거의 달라지지 않을 수 있습니다. 반대로 특정 작업을 백그라운드로 옮기면서 화면 반응성은 좋아졌지만 메모리 사용량이 증가할 수도 있습니다.

따라서 Android 성능 최적화에서는 측정 → 원인 분석 → 개선 → 재측정 과정이 중요합니다.

이번 글에서는 Android Studio에서 제공하는 성능 분석 도구와 Jetpack Benchmark 라이브러리를 활용해 앱의 성능을 측정하는 방법을 살펴보겠습니다.

주요 내용은 다음과 같습니다.

  • Android 앱 성능 분석의 기본 개념

  • Android Studio Profiler

  • CPU 및 시스템 트레이스 분석

  • 메모리 사용량과 객체 할당 확인

  • RecyclerView 프레임 끊김 분석

  • SQLite 쿼리 성능 측정

  • Macrobenchmark 프로젝트 구성

  • 앱 시작 속도 측정

  • 스크롤 성능 측정

  • Baseline Profiles 생성 및 적용

  • Startup Profiles와 Baseline Profiles의 차이

  • 실제 기기 테스트 및 성능 회귀 방지

개발 환경: Kotlin, XML ViewBinding, MVVM, RecyclerView, SQLiteOpenHelper, Glide, AndroidX Benchmark, minSdk 24 이상. Android Studio와 Benchmark 라이브러리의 메뉴 및 API는 버전에 따라 달라질 수 있습니다. 아래 코드는 적용 원리를 설명하는 예제이며, 측정 결과 수치는 실제 테스트에서 확인해야 합니다.


1. Android 앱 성능은 무엇을 측정해야 할까?

앱의 성능을 평가할 때 단순히 실행 속도만 확인해서는 안 됩니다.

대표적으로 다음 항목을 측정할 수 있습니다.

성능 항목측정 대상
앱 시작 속도실행부터 첫 화면 표시까지
화면 반응성터치와 화면 갱신 지연
프레임 성능끊김 및 느린 프레임
CPU 사용량연산 부하와 메인 스레드 작업
메모리 사용량객체 할당, 힙 증가, GC
데이터베이스SQL 조회 및 저장 시간
네트워크API 응답과 데이터 처리 시간
배터리CPU, 네트워크, 백그라운드 작업 비용

모든 항목을 동시에 최적화할 필요는 없습니다.

사용자가 실제로 불편함을 느끼는 기능부터 측정하는 것이 효율적입니다.


성능 최적화의 기본 원칙

2. 추측보다 측정이 먼저다

예를 들어 조류 도감 앱의 검색 화면이 느리다고 가정하겠습니다.

가능한 원인은 여러 가지입니다.

  • SQLite LIKE 검색이 오래 걸림

  • 데이터베이스 파일 초기화 지연

  • 검색 결과를 메인 스레드에서 변환

  • RecyclerView 전체 갱신

  • Glide 이미지 로딩 지연

  • 화면 레이아웃 계산 비용 증가

겉으로 보기에는 모두 '검색 화면이 느리다'는 문제로 나타납니다.

하지만 실제 원인은 서로 다릅니다.

따라서 다음 순서로 접근하는 것이 좋습니다.

성능 문제 발견
      ↓
재현 조건 정의
      ↓
기준 성능 측정
      ↓
병목 구간 분석
      ↓
코드 수정
      ↓
동일 조건 재측정
      ↓
개선 여부 판단

특히 측정 전후에 테스트 기기, 앱 빌드, 데이터 양, 네트워크 상태를 최대한 동일하게 유지해야 합니다.

3. Debug 빌드만으로 판단하지 않기

Android Studio에서 실행하는 Debug 빌드는 개발 편의를 위한 여러 설정이 포함될 수 있습니다.

대표적으로 다음과 같은 차이가 있습니다.

  • 디버깅 가능 여부

  • R8 최적화 적용 여부

  • 로그 출력

  • 코드 생성 및 계측

  • 런타임 최적화 상태

따라서 Debug 빌드에서 느리다고 해서 실제 배포 버전에서도 동일하게 느리다고 단정할 수 없습니다.

정확한 성능 비교에는 실제 배포 설정과 유사한 최적화된 빌드를 사용하는 것이 중요합니다.


Android Studio Profiler

4. Android Studio Profiler란?

Android Studio에는 앱의 CPU, 메모리, 시스템 동작을 분석할 수 있는 도구가 제공됩니다.

Android Studio 버전에 따라 Profiler 화면의 구성이나 메뉴 이름은 달라질 수 있습니다.

대표적으로 다음 작업을 수행할 수 있습니다.

  • CPU 사용량 확인

  • 메서드 실행 분석

  • 메모리 사용량 확인

  • Heap Dump 분석

  • 객체 할당 추적

  • 시스템 트레이스 분석

성능 문제가 발생하는 화면을 실제로 조작하면서 관련 데이터를 수집할 수 있다는 장점이 있습니다.

5. Profiler 실행 방법

일반적인 사용 흐름은 다음과 같습니다.

  1. Android Studio에서 프로젝트를 엽니다.

  2. 테스트할 기기를 연결합니다.

  3. 앱을 실행합니다.

  4. Profiler 또는 관련 프로파일링 도구를 엽니다.

  5. 분석할 앱 프로세스를 선택합니다.

  6. CPU, 메모리 또는 시스템 트레이스를 기록합니다.

  7. 문제가 발생하는 기능을 실행합니다.

  8. 기록을 분석합니다.

최신 Android Studio에서는 작업별로 별도의 프로파일링 진입점이 제공될 수 있습니다.

메뉴가 보이지 않는 경우에는 Android Studio의 검색 기능에서 Profiler, System Trace, Heap Dump 등을 검색해 볼 수 있습니다.


CPU 성능 분석

6. CPU 사용량이 높으면 무조건 문제일까?

CPU 사용량이 높다는 사실만으로 성능 문제가 있다고 판단할 수는 없습니다.

예를 들어 이미지 처리나 대량 데이터 변환을 수행할 때는 CPU 사용량이 일시적으로 증가할 수 있습니다.

중요한 것은 다음과 같습니다.

  • CPU 사용량이 높은 작업이 무엇인지

  • 메인 스레드를 차단하는지

  • 사용자 입력이 지연되는지

  • 불필요하게 반복되는 연산인지

  • 작업이 종료된 뒤에도 부하가 유지되는지

특히 Android UI는 메인 스레드의 응답성이 중요합니다.

7. 메인 스레드 블로킹

다음 코드를 살펴보겠습니다.

binding.buttonSearch.setOnClickListener {

    val birds =
        repository.searchBirdsBlocking("참새")

    adapter.submitList(birds)
}

검색 함수가 SQLite를 동기적으로 조회한다면 메인 스레드가 차단될 수 있습니다.

데이터가 적을 때는 문제가 드러나지 않을 수 있지만 데이터 양이 증가하면 화면이 멈추는 현상이 발생할 수 있습니다.

다음과 같이 개선할 수 있습니다.

binding.buttonSearch.setOnClickListener {

    viewLifecycleOwner.lifecycleScope.launch {

        val birds =
            withContext(Dispatchers.IO) {
                repository.searchBirdsBlocking("참새")
            }

        adapter.submitList(birds)
    }
}

다만 실제 MVVM 구조에서는 Fragment가 직접 Repository를 호출하기보다 ViewModel에 이벤트를 전달하는 방식이 좋습니다.

또한 Dispatchers.IO로 옮겼다는 이유만으로 SQL 쿼리 자체가 빨라지는 것은 아닙니다.

메인 스레드의 응답성을 개선하는 것과 데이터 처리 시간을 줄이는 것은 구분해야 합니다.


System Trace 분석

8. System Trace란?

System Trace는 앱과 시스템의 실행 흐름을 시간순으로 분석할 수 있는 도구입니다.

예를 들어 다음과 같은 정보를 확인하는 데 도움이 됩니다.

  • 메인 스레드 실행 구간

  • 스레드 스케줄링

  • 프레임 처리

  • CPU 작업

  • 시스템 서비스와의 상호작용

  • 앱 내부에 추가한 추적 구간

특정 화면에서 끊김이 발생한다면 System Trace를 이용해 어느 작업이 프레임 처리 시간을 차지했는지 확인할 수 있습니다.

9. Trace 구간 직접 추가하기

AndroidX Tracing 라이브러리를 이용하면 특정 작업을 추적 구간으로 표시할 수 있습니다.

trace("bird_search") {

    val result =
        database.searchBirds(keyword)

    processResult(result)
}

이 예제는 androidx.tracing.trace 함수를 사용하는 형태입니다.

실제 프로젝트에서는 AndroidX Tracing 의존성과 import가 필요합니다.

추적 구간 이름은 기능을 식별하기 쉽게 작성하는 것이 좋습니다.

예를 들어 다음과 같습니다.

bird_search
bird_cursor_mapping
bird_list_submit
bird_detail_render

이렇게 하면 System Trace에서 앱의 특정 작업을 구분하기 쉬워집니다.


RecyclerView 프레임 성능

10. 화면이 끊기는 이유

RecyclerView를 빠르게 스크롤할 때 화면이 끊기는 현상을 흔히 Jank라고 부릅니다.

주요 원인은 다음과 같습니다.

  • onBindViewHolder()에서 무거운 작업 실행

  • 이미지 디코딩 비용

  • 복잡한 레이아웃

  • 잦은 전체 목록 갱신

  • 메인 스레드에서 데이터 변환

  • 불필요한 객체 생성

  • 과도한 애니메이션

예를 들어 다음과 같은 코드는 주의해야 합니다.

override fun onBindViewHolder(
    holder: BirdViewHolder,
    position: Int
) {

    val bird = items[position]

    val bitmap =
        BitmapFactory.decodeFile(
            bird.imagePath
        )

    holder.imageView.setImageBitmap(bitmap)
}

이미지 파일을 동기적으로 디코딩하면 스크롤 성능에 영향을 줄 수 있습니다.

11. Glide 활용하기

Glide.with(holder.itemView)
    .load(bird.imageUrl)
    .placeholder(R.drawable.image_placeholder)
    .error(R.drawable.image_error)
    .into(holder.imageView)

Glide는 이미지 로딩, 캐시, 리소스 관리에 도움을 줍니다.

다만 Glide를 사용한다고 모든 스크롤 문제가 해결되는 것은 아닙니다.

이미지 크기, 레이아웃 구조, 목록 갱신 방식도 함께 분석해야 합니다.


12. 프레임 시간 이해하기

화면 주사율에 따라 프레임 하나를 처리할 수 있는 시간은 달라집니다.

화면 주사율프레임 간격
60Hz약 16.67ms
90Hz약 11.11ms
120Hz약 8.33ms

이 수치는 디스플레이의 프레임 간격을 의미합니다.

앱이 해당 시간 안에 필요한 작업을 완료하지 못하면 프레임 지연이 발생할 수 있습니다.

다만 Android의 실제 렌더링 파이프라인은 단순히 메인 스레드 작업 시간 하나로 결정되지 않습니다.

따라서 프레임 성능을 분석할 때는 Frame Timeline과 System Trace를 함께 확인하는 것이 좋습니다.


메모리 성능 분석

13. 메모리 사용량이 증가하는 원인

Android 앱에서 메모리 사용량이 증가하는 대표적인 원인은 다음과 같습니다.

  • 대용량 Bitmap

  • Activity 또는 Fragment 참조 유지

  • WebView 리소스 관리 문제

  • RecyclerView 이미지 캐시

  • 불필요한 객체 생성

  • 정리되지 않은 Listener

  • 장시간 유지되는 Collection

메모리 사용량이 높다고 해서 반드시 메모리 누수가 발생한 것은 아닙니다.

정상적인 캐시 사용이나 일시적인 객체 할당으로 메모리가 증가할 수도 있습니다.

14. Heap Dump 분석

Heap Dump는 특정 시점의 메모리 객체 상태를 분석하는 데 사용합니다.

예를 들어 다음 상황에서 활용할 수 있습니다.

  1. 상세 화면에 진입합니다.

  2. 여러 이미지를 표시합니다.

  3. 목록 화면으로 돌아옵니다.

  4. 가비지 컬렉션 이후 메모리 상태를 확인합니다.

  5. 종료된 화면의 객체가 예상치 못하게 유지되는지 분석합니다.

특히 Fragment의 ViewBinding을 정리하지 않으면 View 객체가 필요 이상으로 유지될 수 있습니다.

private var _binding:
    FragmentBirdDetailBinding? = null

private val binding
    get() = _binding!!

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

이 패턴은 Fragment의 View 생명주기에 맞춰 ViewBinding 참조를 정리하는 기본적인 방법입니다.

하지만 ViewBinding을 null로 변경하는 것만으로 모든 메모리 누수가 해결되는 것은 아닙니다.

Listener, Adapter, Coroutine, WebView 등의 참조도 확인해야 합니다.


SQLite 성능 분석

15. SQLite 검색이 느릴 때

SQLite 검색 성능은 데이터 양, 인덱스, 쿼리 구조 등에 영향을 받습니다.

예를 들어 다음과 같은 쿼리를 생각해 보겠습니다.

SELECT *
FROM korean_birds
WHERE KoreanName LIKE '%참새%';

앞에 %가 있는 부분 문자열 검색은 일반적인 B-tree 인덱스를 효율적으로 활용하기 어려운 경우가 많습니다.

따라서 데이터가 많아지면 검색 시간이 증가할 수 있습니다.

16. EXPLAIN QUERY PLAN 사용하기

SQLite에서는 EXPLAIN QUERY PLAN으로 쿼리 실행 계획을 확인할 수 있습니다.

EXPLAIN QUERY PLAN
SELECT *
FROM korean_birds
WHERE KoreanName LIKE '%참새%';

실행 계획에서 전체 테이블 탐색 여부 등을 확인할 수 있습니다.

다만 실행 계획은 실제 쿼리 시간과 동일한 정보가 아닙니다.

정확한 판단을 위해서는 실행 계획과 실제 측정 시간을 함께 확인해야 합니다.

17. SQLite 쿼리 시간 측정

val elapsedMs =
    measureTimeMillis {

        database.searchBirds("참새")
    }

Log.d(
    "SQLitePerformance",
    "Search elapsed: ${elapsedMs}ms"
)

이 코드는 간단한 개발용 측정 예제입니다.

단, 측정 대상 함수가 동기 함수라고 가정합니다.

정확한 성능 비교를 위해서는 다음 사항을 고려해야 합니다.

  • 데이터베이스 캐시 상태

  • 첫 실행과 반복 실행의 차이

  • 검색 결과 개수

  • 테스트 데이터 양

  • 기기 성능

  • 백그라운드 작업 영향

한 번의 측정값만으로 성능을 판단하지 않는 것이 좋습니다.


Macrobenchmark란?

18. Macrobenchmark의 역할

Macrobenchmark는 실제 앱의 사용자 시나리오를 반복 실행하면서 성능을 측정하는 Jetpack 라이브러리입니다.

대표적으로 다음 작업을 측정할 수 있습니다.

  • 앱 시작 속도

  • RecyclerView 스크롤

  • 화면 전환

  • 애니메이션

  • 특정 사용자 동작

일반적인 단위 테스트와 달리 앱을 실제로 실행하면서 성능을 측정한다는 특징이 있습니다.

19. Microbenchmark와 Macrobenchmark 차이

구분MicrobenchmarkMacrobenchmark
측정 대상작은 코드 단위앱 사용자 시나리오
예시문자열 처리 함수앱 실행 및 스크롤
앱 전체 실행일반적으로 불필요필요
주요 목적알고리즘·함수 성능실제 사용자 경험
측정 환경Benchmark 테스트별도 앱 대상 계측

예를 들어 검색어 정규화 함수의 성능을 비교하려면 Microbenchmark가 적합할 수 있습니다.

반면 검색 화면에서 실제로 목록을 스크롤하는 성능을 측정하려면 Macrobenchmark가 적합합니다.


Macrobenchmark 모듈 구성

20. 별도 테스트 모듈 만들기

일반적으로 Macrobenchmark는 대상 앱과 별도의 테스트 모듈에서 실행합니다.

예를 들어 다음과 같은 구조입니다.

KoreanBirdsApp
├── app
├── core
├── feature
└── benchmark

benchmark 모듈은 테스트용 Android 모듈로 구성합니다.

일반적인 Android Library 모듈과 설정이 다르므로 주의해야 합니다.

21. Gradle 설정 예제

plugins {
    id("com.android.test")
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "com.example.benchmark"
    compileSdk = 36

    defaultConfig {
        minSdk = 24

        testInstrumentationRunner =
            "androidx.test.runner.AndroidJUnitRunner"

        testInstrumentationRunnerArguments[
            "androidx.benchmark.suppressErrors"
        ] = "NONE"
    }

    targetProjectPath = ":app"
}

dependencies {
    implementation(
        "androidx.benchmark:benchmark-macro-junit4:<version>"
    )

    implementation(
        "androidx.test.uiautomator:uiautomator:<version>"
    )
}

위 코드는 핵심 구조를 보여주는 예제입니다.

실제 프로젝트에서는 Macrobenchmark 공식 설정에 따라 테스트 모듈의 빌드 유형, 대상 앱, 프로파일링 설정 등을 추가해야 합니다.

또한 suppressErrors는 문제를 숨기기 위한 용도로 사용하지 않는 것이 좋습니다. 해당 인수는 필요하지 않다면 생략하고, 성능 테스트에 부적합한 환경 경고를 먼저 해결해야 합니다.


앱 시작 속도 측정

22. StartupTimingMetric

Macrobenchmark에서는 StartupTimingMetric을 이용해 앱 시작 성능을 측정할 수 있습니다.

@RunWith(AndroidJUnit4::class)
class StartupBenchmark {

    @get:Rule
    val benchmarkRule =
        MacrobenchmarkRule()

    @Test
    fun startup() =
        benchmarkRule.measureRepeated(
            packageName = "com.example.birdapp",
            metrics = listOf(
                StartupTimingMetric()
            ),
            iterations = 5,
            startupMode = StartupMode.COLD
        ) {

            pressHome()

            startActivityAndWait()
        }
}

이 예제에서 packageName은 실제 측정 대상 앱의 Application ID로 변경해야 합니다.

또한 테스트 실행을 위해 Macrobenchmark 테스트 모듈과 대상 앱의 빌드 구성이 올바르게 연결되어 있어야 합니다.

23. Cold, Warm, Hot Start

앱 시작은 일반적으로 다음과 같이 구분합니다.

시작 유형특징
Cold Start앱 프로세스가 없는 상태에서 시작
Warm Start프로세스는 존재하지만 Activity를 새로 생성하는 등의 작업 필요
Hot Start기존 Activity를 다시 전면에 표시

실제 시작 유형은 Android의 프로세스 및 Activity 상태에 따라 결정됩니다.

앱 시작 속도를 최적화할 때는 Cold Start만 측정하지 않고 사용자가 자주 경험하는 시작 유형도 함께 확인하는 것이 좋습니다.


스크롤 성능 측정

24. FrameTimingMetric

RecyclerView 스크롤 성능을 측정할 때는 FrameTimingMetric을 사용할 수 있습니다.

@RunWith(AndroidJUnit4::class)
class BirdListScrollBenchmark {

    @get:Rule
    val benchmarkRule =
        MacrobenchmarkRule()

    @Test
    fun scrollBirdList() =
        benchmarkRule.measureRepeated(
            packageName = "com.example.birdapp",
            metrics = listOf(
                FrameTimingMetric()
            ),
            iterations = 5,
            startupMode = StartupMode.WARM
        ) {

            startActivityAndWait()

            val list = device.findObject(
                By.res(
                    "com.example.birdapp",
                    "recyclerView"
                )
            )

            list.setGestureMargin(
                device.displayWidth / 5
            )

            list.fling(Direction.DOWN)
            device.waitForIdle()
        }
}

위 코드는 UI Automator를 이용해 목록을 스크롤하는 예제입니다.

실제 앱에서는 RecyclerView의 리소스 ID와 화면 진입 경로를 맞춰야 합니다.

또한 스크롤이 끝나기 전에 측정이 종료되지 않도록 동기화 조건을 신중하게 설계해야 합니다.


Baseline Profiles

25. Baseline Profiles란?

Baseline Profiles는 앱에서 중요한 코드 경로를 미리 정의해 Android Runtime이 해당 코드를 효율적으로 최적화하도록 돕는 기술입니다.

예를 들어 다음과 같은 사용자 동작이 있다고 가정하겠습니다.

앱 실행
   ↓
메인 화면 표시
   ↓
조류 목록 로딩
   ↓
검색 화면 이동
   ↓
상세 화면 표시

이 과정에서 자주 실행되는 코드 경로를 프로파일에 포함할 수 있습니다.

Baseline Profiles는 특히 앱 설치 후 초기 실행 성능과 중요한 사용자 경로의 실행 성능 개선에 도움이 될 수 있습니다.

하지만 모든 앱에서 동일한 수준의 개선이 발생하는 것은 아닙니다.

실제 개선 효과는 앱의 코드 구조, Android 버전, 기기, 프로파일 적용 상태 등에 따라 달라집니다.


26. Baseline Profiles의 동작 개념

앱의 주요 사용자 경로 실행
             ↓
프로파일 생성
             ↓
앱에 프로파일 포함
             ↓
Android Runtime 최적화
             ↓
중요 코드 경로의 실행 성능 개선

Baseline Profiles는 이미지 크기나 SQLite SQL 자체를 최적화하는 기술이 아닙니다.

주로 앱 코드 실행과 관련된 런타임 최적화를 지원합니다.

따라서 데이터베이스 쿼리가 느린 문제는 별도의 SQL 최적화가 필요합니다.


Baseline Profile 생성

27. Baseline Profile Generator

AndroidX Benchmark에서는 Baseline Profile 생성기를 사용할 수 있습니다.

일반적으로 별도 Generator 테스트 모듈을 구성하거나 Android Studio의 관련 기능을 이용합니다.

@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {

    @get:Rule
    val rule =
        BaselineProfileRule()

    @Test
    fun generate() =
        rule.collect(
            packageName = "com.example.birdapp"
        ) {

            pressHome()
            startActivityAndWait()

            // 앱의 주요 화면 이동 및
            // 사용자 동작 수행
        }
}

이 예제는 핵심 API 사용 형태를 보여줍니다.

실제 생성과 결과 파일 복사, 앱 모듈 연결에는 Baseline Profile Gradle Plugin의 공식 설정을 따르는 것이 좋습니다.


28. 어떤 사용자 경로를 포함할까?

조류 도감 앱이라면 다음 시나리오를 고려할 수 있습니다.

  1. 앱 시작

  2. 조류 목록 표시

  3. 검색 화면 진입

  4. 검색어 입력

  5. 검색 결과 표시

  6. 상세 화면 진입

  7. 이미지 갤러리 표시

  8. 좋아요 버튼 조작

모든 기능을 무조건 포함할 필요는 없습니다.

실제 사용 빈도가 높고 성능에 중요한 경로를 우선적으로 선택하는 것이 좋습니다.

또한 로그인, 외부 네트워크 응답 등 실행 결과가 불안정한 동작은 테스트 환경을 제어할 필요가 있습니다.


Baseline Profile Gradle Plugin

29. Gradle 플러그인 적용

Baseline Profile Gradle Plugin을 사용하면 프로파일 생성과 앱 모듈 통합을 관리하기 편리합니다.

예를 들어 앱 모듈에 다음 플러그인을 적용할 수 있습니다.

plugins {
    id("androidx.baselineprofile")
}

Generator 모듈에서도 공식 문서에 따른 플러그인과 의존성을 설정합니다.

실제 구성은 Android Gradle Plugin 및 Baseline Profile Plugin 버전에 따라 달라질 수 있으므로 공식 샘플을 기준으로 적용하는 것이 좋습니다.

30. 생성한 프로파일 확인

Baseline Profile이 생성되면 일반적으로 다음과 같은 파일을 확인할 수 있습니다.

baseline-prof.txt

파일에는 프로파일 규칙이 기록됩니다.

이 파일은 사람이 직접 작성하는 일반적인 ProGuard 규칙과는 다릅니다.

생성된 프로파일을 앱에 포함하고 실제 기기에서 적용 여부를 검증해야 합니다.


Startup Profiles와의 차이

31. Startup Profiles란?

Startup Profiles는 앱 시작 과정에서 중요한 코드의 배치와 관련된 최적화에 활용됩니다.

Baseline Profiles와 이름이 비슷하지만 목적이 완전히 같지는 않습니다.

구분Baseline ProfilesStartup Profiles
주요 목적중요한 코드 경로의 런타임 최적화시작 관련 코드 배치 최적화
적용 영역ART 최적화빌드 도구의 DEX 레이아웃 최적화
대표 효과초기 실행 및 주요 경로 성능 개선 가능시작 시 코드 접근 효율 개선 가능
검증 방법Macrobenchmark 등시작 성능 측정 및 빌드 산출물 확인

두 기술은 서로 대체 관계라기보다 보완적으로 활용할 수 있습니다.

다만 Startup Profile 지원 범위와 생성 방식은 AGP 및 관련 플러그인 버전에 따라 달라질 수 있습니다.


성능 측정 시 주의사항

32. 에뮬레이터만으로 판단하지 않기

에뮬레이터는 기능 테스트와 초기 분석에 편리합니다.

하지만 실제 스마트폰과 CPU, GPU, 메모리, 저장장치 특성이 다를 수 있습니다.

따라서 최종 성능 검증에는 실제 기기를 사용하는 것이 좋습니다.

가능하면 다음과 같은 기기를 포함합니다.

  • 중급형 Android 스마트폰

  • 고급형 Android 스마트폰

  • 메모리가 적은 기기

  • 화면 주사율이 다른 기기

  • 다양한 Android 버전

고급형 기기에서 문제가 없더라도 저사양 기기에서는 프레임 끊김이나 앱 시작 지연이 발생할 수 있습니다.


33. 기기 온도와 배터리 상태

성능 테스트 결과는 기기의 상태에 영향을 받을 수 있습니다.

예를 들어 다음과 같은 상황입니다.

  • 기기가 과열된 상태

  • 배터리 절약 모드 활성화

  • 백그라운드 앱 실행

  • 시스템 업데이트 진행

  • 저장 공간 부족

  • 충전 상태에 따른 성능 정책 차이

따라서 동일한 테스트를 여러 번 실행하고 결과의 분포를 확인하는 것이 좋습니다.

단일 측정값만으로 개선 여부를 판단하지 않는 것이 중요합니다.


성능 개선 효과 검증

34. 개선 전후 비교 방법

예를 들어 RecyclerView 스크롤 성능을 개선했다고 가정하겠습니다.

다음과 같이 측정 결과를 기록할 수 있습니다.

항목개선 전개선 후
앱 시작 시간측정값 기록측정값 기록
프레임 시간 P50측정값 기록측정값 기록
프레임 시간 P90측정값 기록측정값 기록
프레임 시간 P95측정값 기록측정값 기록
메모리 사용량측정값 기록측정값 기록

위 표에는 임의의 성능 수치를 넣지 않았습니다.

실제 프로젝트에서 동일한 조건으로 측정한 결과를 입력해야 합니다.

35. 평균값만 보면 안 되는 이유

성능 테스트에서는 평균값뿐 아니라 분포도 중요합니다.

예를 들어 대부분의 프레임은 빠르지만 일부 프레임이 매우 느리다면 사용자는 화면 끊김을 경험할 수 있습니다.

따라서 다음 지표를 함께 확인하는 것이 좋습니다.

  • Median 또는 P50

  • P90

  • P95

  • P99

  • 느린 프레임 발생 빈도

어떤 지표가 제공되는지는 측정 도구와 Metric에 따라 다를 수 있습니다.


CI/CD와 성능 테스트

36. GitHub Actions에서 성능 테스트할 수 있을까?

GitHub Actions에서도 Android 테스트를 자동화할 수 있습니다.

하지만 Macrobenchmark는 일반적인 JVM 단위 테스트와 다릅니다.

실제 Android 기기 또는 적절한 테스트 환경이 필요하며, 실행 환경의 변동성이 측정 결과에 영향을 줄 수 있습니다.

따라서 다음과 같은 구성을 고려할 수 있습니다.

GitHub Actions
      │
      ├── Unit Test
      │
      ├── Lint
      │
      ├── Build
      │
      └── Performance Test
               │
               └── 전용 기기 또는
                   관리형 테스트 환경

정확한 성능 회귀 검증을 위해서는 테스트 기기와 실행 조건을 최대한 고정하는 것이 좋습니다.

일반적인 공유 CI 환경에서 측정한 결과를 절대적인 성능 수치로 판단하는 것은 주의해야 합니다.


실무 적용 전략

37. 기존 앱에 어떤 순서로 적용할까?

이미 출시한 Android 앱이라면 다음 순서로 성능 분석을 시작하는 것이 좋습니다.

1단계: 실제 문제 선정

앱 시작, 검색, 스크롤, 상세 화면 이동 중 가장 문제가 큰 기능을 선택합니다.

2단계: 기준 성능 측정

최적화 전의 성능을 기록합니다.

3단계: Profiler 분석

CPU, 메모리, System Trace를 통해 병목을 찾습니다.

4단계: 코드 수정

원인에 맞는 최적화를 적용합니다.

5단계: Macrobenchmark 작성

주요 사용자 시나리오를 반복 측정합니다.

6단계: Baseline Profiles 검토

앱 시작과 자주 사용하는 기능의 런타임 최적화를 적용합니다.

7단계: 실제 기기 재측정

개선 전후 결과를 동일 조건에서 비교합니다.


38. 조류 도감 앱 추천 성능 측정 항목

조류 도감 앱에서는 다음 기능을 우선 측정할 수 있습니다.

기능주요 측정 항목
앱 시작Cold Start 시간
초기 목록SQLite 로딩 및 첫 화면 표시
검색SQL 조회 시간과 UI 반영
목록 스크롤FrameTimingMetric
상세 화면Fragment 전환 시간
이미지 갤러리메모리와 프레임 성능
좋아요SQLite 저장과 UI 갱신
화면 반복 이동메모리 증가 및 누수

특히 이미지가 많은 RecyclerView와 SQLite 검색은 사용자 경험에 직접적인 영향을 주기 쉬운 부분입니다.


39. Android 성능 분석 체크리스트

  • 실제 성능 문제를 재현할 수 있는지 확인

  • 최적화 전 기준 성능 기록

  • Debug와 Release 빌드 차이 이해

  • CPU 사용량 분석

  • 메인 스레드 블로킹 확인

  • System Trace 기록

  • 필요한 작업에 Trace 구간 추가

  • RecyclerView 프레임 성능 확인

  • 이미지 디코딩과 로딩 방식 점검

  • Heap Dump 분석

  • Fragment 및 ViewBinding 참조 정리

  • SQLite 실행 계획 확인

  • SQLite 조회 시간 측정

  • Macrobenchmark 모듈 구성

  • StartupTimingMetric 적용

  • FrameTimingMetric 적용

  • 실제 사용자 시나리오 자동화

  • Baseline Profile 생성

  • Baseline Profile 적용 여부 검증

  • Startup Profile 적용 필요성 검토

  • 실제 기기에서 반복 측정

  • 기기 온도 및 백그라운드 상태 확인

  • 평균값과 백분위 지표 비교

  • 개선 후 성능 회귀 여부 확인


40. 마무리

Android 앱 성능 최적화에서 가장 중요한 것은 코드를 무조건 빠르게 만드는 것이 아닙니다.

실제로 사용자가 느끼는 지연을 찾아내고, 그 원인을 분석한 뒤, 개선 효과를 검증하는 것입니다.

Android Studio Profiler는 CPU와 메모리, 시스템 실행 흐름을 분석하는 데 도움이 됩니다.

Macrobenchmark는 앱 시작이나 RecyclerView 스크롤처럼 실제 사용자 시나리오의 성능을 반복 측정할 수 있습니다.

Baseline Profiles는 중요한 코드 경로의 런타임 최적화를 지원합니다.

이 도구들을 함께 활용하면 감각적인 판단에 의존하지 않고 성능 개선을 진행할 수 있습니다.

이번 글의 핵심을 정리하면 다음과 같습니다.

  1. 성능 최적화는 측정부터 시작해야 합니다.

  2. Debug 빌드 결과만으로 실제 배포 성능을 판단하지 않습니다.

  3. CPU와 System Trace로 메인 스레드 병목을 분석합니다.

  4. RecyclerView는 프레임 성능을 기준으로 평가합니다.

  5. 메모리 사용량 증가와 메모리 누수를 구분해야 합니다.

  6. SQLite는 실행 계획과 실제 조회 시간을 함께 확인합니다.

  7. Macrobenchmark로 실제 사용자 시나리오를 반복 측정합니다.

  8. Baseline Profiles로 중요한 코드 경로의 최적화를 지원할 수 있습니다.

  9. 실제 기기에서 동일한 조건으로 측정해야 합니다.

  10. 개선 전후의 수치를 비교해 효과를 검증해야 합니다.

기존 Kotlin + XML 기반 앱이라면 우선 앱 시작과 RecyclerView 스크롤 성능을 측정하는 것부터 시작해도 좋습니다.

그다음 SQLite 검색, 이미지 로딩, Fragment 전환 등으로 측정 범위를 확장할 수 있습니다.

Android 성능 최적화의 핵심은 빠를 것 같은 코드를 작성하는 것이 아니라, 실제로 빨라졌다는 사실을 측정으로 확인하는 것입니다.


참고 자료

태그: Android, Kotlin, Android Studio Profiler, Macrobenchmark, Baseline Profiles, Startup Profiles, 성능 최적화, 앱 시작 속도, RecyclerView, SQLite, System Trace, FrameTimingMetric, StartupTimingMetric, 메모리 분석, Android Benchmark, Android 앱 개발