"좋은 UX" 를 위해서라면 속도, 자연스러움, 앱의 컨셉에 맞는 정제된 화려함은 필수인 것 같은데, 그 중에서 앱을 클릭해서 처음 실행할 때 걸리는 시간을 단축시켜서 사용자가 오래 기다리지 않게 할 수 있는 성능 개선이 있다.
Baseline Profile은 AOT 컴파일 최적화 기법인데, 앱이 처음 설치되거나 업데이트될 때 자주 사용되는 코드 경로를 미리 컴파일하여 앱 시작 성능과 런타임 성능을 크게 개선시킬 수 있다.
Android Runtime(ART)은 기본적으로 다음과 같은 컴파일 전략을 사용한다:
이 방식의 문제점:
Baseline Profile을 적용하면:
app/build.gradle.kts
plugins {
alias(libs.plugins.androidx.baselineprofile)
}
dependencies {
implementation(libs.androidx.profileinstaller)
"baselineProfile"(project(":baselineprofile"))
}
주요 구성 요소:
androidx.baselineprofile 플러그인: Baseline Profile 생성 자동화androidx.profileinstaller: 런타임에 프로파일 설치baselineProfile 의존성: 생성 모듈과 연결baselineProfile/build.gradle.kts
plugins {
alias(libs.plugins.android.test)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.androidx.baselineprofile)
}
android {
targetProjectPath = ":app"
}
baselineProfile {
useConnectedDevices = true
}
dependencies {
implementation(libs.androidx.junit)
implementation(libs.androidx.espresso.core)
implementation(libs.androidx.uiautomator)
implementation(libs.androidx.benchmark.macro.junit4)
}
핵심 설정:
targetProjectPath = ":app": 프로파일을 생성할 대상 앱 지정useConnectedDevices = true: 에뮬레이터 없이 실제 기기로 테스트할 경우 USB, 무선 디버깅한다는 의미로 선언해줘야함[versions]
benchmark = "1.4.1"
profileinstaller = "1.4.1"
uiautomator = "2.3.0"
[libraries]
androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmark" }
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" }
androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
[plugins]
androidx-baselineprofile = { id = "androidx.baselineprofile", version.ref = "benchmark" }
BaselineProfileGenerator.kt는 앱의 주요 사용자 플로우를 시뮬레이션할 수 있기 때문에 홈 이외에도 다른 탭에 이동해서 스크롤하는 행동까지 시나리오처럼 미리 작성해둘 수 있다.
그래서 실제로 테스트할 때 baseline profile generator를 실행하면 자동으로 앱이 켜졌다 꺼졌다를 반복하는 모습을 볼 수 있다.
코드 구조
@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generate() {
rule.collect(
packageName = InstrumentationRegistry.getArguments().getString("targetAppId")
?: throw Exception("targetAppId not passed as instrumentation runner arg"),
includeInStartupProfile = true
) {
// Journey 1: App Startup - Basic MainActivity launch
pressHome()
startActivityAndWait()
// Wait for initial content to load
device.wait(Until.hasObject(By.pkg(packageName)), 3000)
device.waitForIdle()
}
}
}
주요 구성 요소 설명
BaselineProfileRule
includeInStartupProfile = true
사용자 플로우 시뮬레이션
pressHome(): 홈 화면으로 이동startActivityAndWait(): 앱 시작 및 대기device.wait(): 초기 콘텐츠 로딩 대기device.waitForIdle(): UI 안정화 대기# Release 빌드용 Baseline Profile 생성
./gradlew :app:generateReleaseBaselineProfile
실행 과정:
1. 앱을 릴리스 모드로 빌드
2. 실제 디바이스에 설치
3. BaselineProfileGenerator 테스트 실행
4. 프로파일 데이터 수집
5. baseline-prof.txt 파일 생성

StartupBenchmarks.kt는 Baseline Profile의 효과를 측정한다.
Android Studio IDE에서 New Module을 생성하겠다고 하면 기본적인 포맷을 제공해주기 때문에 해당 파일을 자동으로 작성해서 편했다.
코드 구조
@RunWith(AndroidJUnit4::class)
@LargeTest
class StartupBenchmarks {
@get:Rule
val rule = MacrobenchmarkRule()
@Test
fun startupCompilationNone() =
benchmark(CompilationMode.None())
@Test
fun startupCompilationBaselineProfiles() =
benchmark(CompilationMode.Partial(BaselineProfileMode.Require))
private fun benchmark(compilationMode: CompilationMode) {
rule.measureRepeated(
packageName = InstrumentationRegistry.getArguments().getString("targetAppId")
?: throw Exception("targetAppId not passed as instrumentation runner arg"),
metrics = listOf(StartupTimingMetric()),
compilationMode = compilationMode,
startupMode = StartupMode.COLD,
iterations = 10,
setupBlock = {
pressHome()
},
measureBlock = {
startActivityAndWait()
}
)
}
}
Test 1: startupCompilationNone()
CompilationMode.None()Test 2: startupCompilationBaselineProfiles()
CompilationMode.Partial(BaselineProfileMode.Require)StartupTimingMetric()이 측정하는 항목:
# 모든 벤치마크 실행
./gradlew :baselineProfile:connectedBenchmarkReleaseAndroidTest
# 결과는 다음 위치에 저장됨
# app/build/outputs/connected_android_test_additional_output/


실제 결과:
variant를 baenchmark로 설정하다보니 release 모드에서는 즉 플레이스토어에 런칭할 때는 해당 내용이 적용이 안되지 않을까하고 생각했었는데 apk가 올라가는 것이 아니라 aab 번들이 올라가므로 괜찮았다.