7.7 KiB
7.7 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| android-baseline-profile | Android Baseline Profile — Startup / Scroll 최적화 | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
Android Baseline Profile
Startup / scroll 30% 빠르게. Macrobenchmark 가 critical user flow trace → AOT compiled. R8 + Compose 와 결합.
📖 핵심 개념
- Baseline Profile: 자주 쓰는 코드 path 미리 AOT compile.
- Macrobenchmark: 측정 + profile 생성.
- Default profile: Compose / 일부 lib 자동.
- Startup Profile: cold start 가속.
💻 코드 패턴
Setup
// build.gradle (app)
plugins {
id 'androidx.baselineprofile'
}
dependencies {
implementation 'androidx.profileinstaller:profileinstaller:1.3.1'
baselineProfile project(':baselineprofile')
}
baselineProfile {
saveInSrc = true // src/main/baseline-prof.txt
automaticGenerationDuringBuild = false // CI 에서만
}
baselineprofile module
// :baselineprofile/build.gradle
plugins {
id 'com.android.test'
id 'androidx.baselineprofile'
}
android {
targetProjectPath = ':app'
}
dependencies {
implementation 'androidx.benchmark:benchmark-macro-junit4:1.2.0'
implementation 'androidx.test.ext:junit:1.1.5'
implementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}
Generate profile (Macrobenchmark)
// :baselineprofile/src/main/java/.../BaselineProfileGenerator.kt
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@get:Rule val rule = BaselineProfileRule()
@Test
fun generate() = rule.collect(packageName = "com.acme.app") {
// 1. App 시작 — cold start
startActivityAndWait()
// 2. 핵심 user flow
device.findObject(By.res("login_button")).click()
device.wait(Until.hasObject(By.res("home_screen")), 5000)
// 3. Scroll
val list = device.findObject(By.res("feed_list"))
list.fling(Direction.DOWN)
list.fling(Direction.DOWN)
// 4. 다른 screen
device.findObject(By.text("Profile")).click()
device.wait(Until.hasObject(By.res("profile_screen")), 5000)
}
}
./gradlew :baselineprofile:generateBaselineProfile
# 결과: app/src/main/baseline-prof.txt
Build 안 적용
- baseline-prof.txt 가 APK 안 포함.
- 첫 install 시 ProfileInstaller 가 dexopt.
- 사용자가 처음 launch 부터 빠름.
Macrobenchmark — 측정
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule val rule = MacrobenchmarkRule()
@Test fun startupCold() = rule.measureRepeated(
packageName = "com.acme.app",
metrics = listOf(StartupTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD,
) {
startActivityAndWait()
}
@Test fun scrollFeed() = rule.measureRepeated(
packageName = "com.acme.app",
metrics = listOf(FrameTimingMetric()),
iterations = 5,
startupMode = StartupMode.WARM,
) {
startActivityAndWait()
val list = device.findObject(By.res("feed_list"))
list.fling(Direction.DOWN)
list.fling(Direction.DOWN)
}
}
./gradlew :baselineprofile:connectedAndroidTest
→ Result: startup time, frame timing, jank %.
기대 효과
Startup: 20-30% 빠름
Scroll: jank ↓
Compose 첫 frame ↓
→ R8 + Baseline Profile + Compose 가 함께 사용.
Compose 최적화 (Baseline 와 별도 + 함께)
// 1. Stable / Immutable
@Stable data class UserState(...)
// 2. derivedStateOf
val isAtTop by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
// 3. key + contentType
items(items, key = { it.id }, contentType = { it.kind }) { ... }
// 4. animateItem
items(items, key = { it.id }) { it ->
Row(modifier = Modifier.animateItem()) { ... }
}
Compose Compiler Metrics
android {
composeOptions {
kotlinCompilerExtensionVersion = '1.5.10'
}
}
tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += [
"-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=$buildDir/compose_metrics",
"-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=$buildDir/compose_metrics",
]
}
}
→ Reports: 어떤 component 가 unstable / non-skippable.
Startup 측정 in production
// Firebase Performance / 자체
class App : Application() {
override fun onCreate() {
val startupTrace = Firebase.performance.newTrace("app_startup")
startupTrace.start()
super.onCreate()
// ...
startupTrace.stop()
}
}
App Startup library
class MyInitializer : Initializer<Unit> {
override fun create(context: Context) {
// 빠른 init
}
override fun dependencies() = emptyList<Class<out Initializer<*>>>()
}
<provider android:name="androidx.startup.InitializationProvider" ...>
<meta-data android:name=".MyInitializer" android:value="androidx.startup" />
</provider>
→ ContentProvider 보다 빠른 init.
CI 안 generation
# .github/workflows/baseline-profile.yml
- name: Build + generate
run: ./gradlew :baselineprofile:generateBaselineProfile
- name: Commit if changed
uses: stefanzweifel/git-auto-commit-action@v5
with:
file_pattern: 'app/src/main/baseline-prof.txt'
commit_message: 'chore: update baseline profile'
Profile vs ART AOT
ART (5.0+): 자동 PGO — 사용 후 점진 빠름.
Baseline Profile: 첫 사용부터 빠름.
→ 둘 다.
Common gotchas
- emulator 결과 ≠ 실기. 실기에서 측정.
- Cold start 만 측정 — process 재시작.
- Macrobenchmark 가 자체 process — system overhead.
- Profile 갱신 — 매 release 권장.
- Compose version 변경 시 다시 generate.
Frame timing
StartupTimingMetric: Activity launch 시간
FrameTimingMetric: p50/p95/p99 frame time
TraceSectionMetric: 자체 trace section
PowerMetric: 전력
NetworkUsageMetric: bytes
Custom trace
// App code
Trace.beginSection("loadHomeFeed")
val items = repo.loadFeed()
Trace.endSection()
// Macrobenchmark
metrics = listOf(TraceSectionMetric("loadHomeFeed"))
Profile size
일반: 5-50 KB.
큰 app: 200 KB 까지.
APK 안 별 영향 X.
🤔 의사결정 기준
| 상황 | 적용 |
|---|---|
| 새 release | Baseline Profile 항상 |
| Compose UI 무거움 | 매우 효과 |
| 작은 utility app | 큰 효과 X |
| Startup 핵심 | 우선순위 |
| Scroll-heavy | 매우 효과 |
| Game | Profile 보다 game-specific |
❌ 안티패턴
- Profile 한 번만 + 영원 사용: 매 release 갱신.
- Emulator 만 측정: 실기와 다름.
- 모든 path profile: 큰 file. critical 만.
- R8 / minification 없이: 효과 적음.
- App Startup library 안 씀 + 큰 init: cold start 느림.
- Macrobenchmark 없는 측정: 추측.
- Compose Compiler Metrics 무시: 어떤 게 unstable 모름.
🤖 LLM 활용 힌트
- Macrobenchmark 가 측정 + profile 생성.
- 매 release CI 갱신.
- Compose stable / immutable + key + animateItem.
- App Startup library 로 init 빠르게.