android-sdd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: allocation-optimization
|
|
3
|
+
description: >
|
|
4
|
+
Reducing object allocations and GC pressure in Android.
|
|
5
|
+
Load this skill when optimizing hot code paths, reducing garbage
|
|
6
|
+
collection pauses, minimizing allocations in draw/layout loops,
|
|
7
|
+
or improving frame rate by reducing allocation churn.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Allocation Optimization
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Excessive object allocation causes frequent garbage collection pauses, which manifest as frame drops and janky animations. On Android, the ART GC is generational and mostly concurrent, but large or frequent allocations in hot code paths (draw loops, scroll, animation) still cause measurable pauses.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- **Never allocate** in `onDraw`, `Canvas` operations, or tight animation loops
|
|
20
|
+
- Reuse objects with object pools or `remember {}` in Compose
|
|
21
|
+
- Prefer **primitives** over boxed types (`Int` not `Integer`, `IntArray` not `List<Int>`)
|
|
22
|
+
- Use `StringBuilder` for string concatenation in loops — not `+` operator
|
|
23
|
+
- Profile with **Android Studio Profiler → Memory → Allocation** before optimizing
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Common Allocation Hotspots
|
|
28
|
+
|
|
29
|
+
```kotlin
|
|
30
|
+
// ❌ Allocating in draw loop — every frame creates new objects
|
|
31
|
+
class CustomView(context: Context) : View(context) {
|
|
32
|
+
override fun onDraw(canvas: Canvas) {
|
|
33
|
+
val paint = Paint() // allocation every frame ❌
|
|
34
|
+
val rect = RectF(0f, 0f, width.toFloat(), height.toFloat()) // allocation every frame ❌
|
|
35
|
+
canvas.drawRect(rect, paint)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ✅ Pre-allocate outside draw loop
|
|
40
|
+
class CustomView(context: Context) : View(context) {
|
|
41
|
+
private val paint = Paint().apply { color = Color.RED }
|
|
42
|
+
private val rect = RectF()
|
|
43
|
+
|
|
44
|
+
override fun onDraw(canvas: Canvas) {
|
|
45
|
+
rect.set(0f, 0f, width.toFloat(), height.toFloat()) // reuse — no allocation
|
|
46
|
+
canvas.drawRect(rect, paint)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## String Concatenation in Loops
|
|
54
|
+
|
|
55
|
+
```kotlin
|
|
56
|
+
// ❌ String concatenation creates new String each iteration
|
|
57
|
+
var result = ""
|
|
58
|
+
items.forEach { result += it.name + ", " } // O(n²) allocations
|
|
59
|
+
|
|
60
|
+
// ✅ StringBuilder — single allocation
|
|
61
|
+
val sb = StringBuilder()
|
|
62
|
+
items.forEach { sb.append(it.name).append(", ") }
|
|
63
|
+
val result = sb.toString()
|
|
64
|
+
|
|
65
|
+
// ✅ Or use joinToString — optimized internally
|
|
66
|
+
val result = items.joinToString(", ") { it.name }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Avoiding Boxing
|
|
72
|
+
|
|
73
|
+
```kotlin
|
|
74
|
+
// ❌ Boxing primitives in hot path
|
|
75
|
+
val counts: Map<String, Int> = mutableMapOf() // Int boxed to Integer
|
|
76
|
+
|
|
77
|
+
// ✅ Use SparseArray for Int keys — avoids boxing
|
|
78
|
+
val counts = SparseIntArray()
|
|
79
|
+
|
|
80
|
+
// ✅ Use primitive arrays
|
|
81
|
+
val buffer = IntArray(1024) // no boxing
|
|
82
|
+
val floatBuffer = FloatArray(256)
|
|
83
|
+
|
|
84
|
+
// ❌ forEach on ranges — can box index
|
|
85
|
+
for (i in 0..100) { ... } // optimized by compiler, actually fine
|
|
86
|
+
|
|
87
|
+
// ❌ Lambda capturing primitives
|
|
88
|
+
listOf(1, 2, 3).map { it * 2 } // boxes Int in some cases
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Compose Allocation Optimization
|
|
94
|
+
|
|
95
|
+
```kotlin
|
|
96
|
+
// ❌ Creating lambda on every recomposition
|
|
97
|
+
@Composable
|
|
98
|
+
fun UserList(users: List<User>, onUserClick: (User) -> Unit) {
|
|
99
|
+
users.forEach { user ->
|
|
100
|
+
UserItem(
|
|
101
|
+
user = user,
|
|
102
|
+
onClick = { onUserClick(user) } // ❌ new lambda every recomposition
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ✅ Use remember or stable callbacks
|
|
108
|
+
@Composable
|
|
109
|
+
fun UserList(users: List<User>, onUserClick: (User) -> Unit) {
|
|
110
|
+
users.forEach { user ->
|
|
111
|
+
val onClick = remember(user.id) { { onUserClick(user) } }
|
|
112
|
+
UserItem(user = user, onClick = onClick)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ❌ Creating objects in composable body
|
|
117
|
+
@Composable
|
|
118
|
+
fun Chart(data: List<Float>) {
|
|
119
|
+
val path = Path() // ❌ recreated every recomposition
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ✅ remember to reuse
|
|
123
|
+
@Composable
|
|
124
|
+
fun Chart(data: List<Float>) {
|
|
125
|
+
val path = remember { Path() } // ✅ created once
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Reusable Buffers
|
|
132
|
+
|
|
133
|
+
```kotlin
|
|
134
|
+
// ✅ Thread-local buffer for repeated operations
|
|
135
|
+
private val threadLocalBuffer = ThreadLocal.withInitial { ByteArray(8192) }
|
|
136
|
+
|
|
137
|
+
fun processData(input: InputStream) {
|
|
138
|
+
val buffer = threadLocalBuffer.get()!!
|
|
139
|
+
var read: Int
|
|
140
|
+
while (input.read(buffer).also { read = it } != -1) {
|
|
141
|
+
process(buffer, read)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Measuring Allocations
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
Android Studio Profiler → Memory tab:
|
|
152
|
+
1. Start recording allocations
|
|
153
|
+
2. Perform the action to profile (scroll, animation)
|
|
154
|
+
3. Stop recording
|
|
155
|
+
4. Sort by "Alloc Count" — find hotspots
|
|
156
|
+
|
|
157
|
+
Key signals:
|
|
158
|
+
- Many small allocations in RecyclerView/LazyColumn scroll → reuse objects
|
|
159
|
+
- Repeated allocation of same type → use pool or remember
|
|
160
|
+
- Allocation inside animation frame → move outside animation
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Anti-Patterns
|
|
166
|
+
|
|
167
|
+
- Allocating `Paint`, `Path`, `RectF` inside `onDraw` — every frame creates garbage
|
|
168
|
+
- String `+` concatenation in loops — use `StringBuilder`
|
|
169
|
+
- Creating collections inside tight loops — pre-allocate or reuse
|
|
170
|
+
- `List<Int>` when `IntArray` would work — unnecessary boxing
|
|
171
|
+
- Allocating large arrays on the main thread — use background thread
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Related Skills
|
|
176
|
+
- `heap-management` — managing heap size and cache limits
|
|
177
|
+
- `compose-optimization` — Compose-specific allocation patterns
|
|
178
|
+
- `rendering-performance` — frame rendering and draw optimization
|
|
179
|
+
- `benchmark` — measuring allocation count with benchmarks
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: app-startup
|
|
3
|
+
description: >
|
|
4
|
+
Jetpack App Startup library for initializer management in Android.
|
|
5
|
+
Load this skill when controlling SDK initialization order, lazy-loading
|
|
6
|
+
initializers, replacing ContentProvider-based auto-init, or reducing
|
|
7
|
+
the number of ContentProviders at startup.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# App Startup
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
The Jetpack App Startup library provides a standard way to initialize components at app startup. It consolidates multiple `ContentProvider` initializers (used by many SDKs) into a single one, reducing startup overhead. It also supports lazy initialization and explicit dependency ordering between initializers.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Use App Startup to **consolidate** multiple SDK ContentProviders into one
|
|
20
|
+
- Define **dependencies** between initializers to control order
|
|
21
|
+
- Use **lazy initialization** for SDKs not needed immediately
|
|
22
|
+
- Disable auto-init for SDKs that have their own ContentProvider when using App Startup
|
|
23
|
+
- Keep each `Initializer.create()` fast — it runs on the main thread during startup
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
```toml
|
|
30
|
+
# libs.versions.toml
|
|
31
|
+
[libraries]
|
|
32
|
+
androidx-startup = { module = "androidx.startup:startup-runtime", version = "1.1.1" }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```kotlin
|
|
36
|
+
// build.gradle.kts
|
|
37
|
+
dependencies {
|
|
38
|
+
implementation(libs.androidx.startup)
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Creating Initializers
|
|
45
|
+
|
|
46
|
+
```kotlin
|
|
47
|
+
// ✅ Timber initializer
|
|
48
|
+
class TimberInitializer : Initializer<Unit> {
|
|
49
|
+
override fun create(context: Context) {
|
|
50
|
+
if (BuildConfig.DEBUG) {
|
|
51
|
+
Timber.plant(Timber.DebugTree())
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ✅ Analytics initializer — depends on Timber being initialized first
|
|
59
|
+
class AnalyticsInitializer : Initializer<AnalyticsClient> {
|
|
60
|
+
override fun create(context: Context): AnalyticsClient {
|
|
61
|
+
return AnalyticsClient.initialize(context).also {
|
|
62
|
+
Timber.d("Analytics initialized")
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun dependencies(): List<Class<out Initializer<*>>> =
|
|
67
|
+
listOf(TimberInitializer::class.java) // ✅ Timber initializes first
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ✅ WorkManager initializer
|
|
71
|
+
class WorkManagerInitializer : Initializer<WorkManager> {
|
|
72
|
+
override fun create(context: Context): WorkManager {
|
|
73
|
+
val config = Configuration.Builder()
|
|
74
|
+
.setMinimumLoggingLevel(Log.INFO)
|
|
75
|
+
.build()
|
|
76
|
+
WorkManager.initialize(context, config)
|
|
77
|
+
return WorkManager.getInstance(context)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Manifest Registration
|
|
87
|
+
|
|
88
|
+
```xml
|
|
89
|
+
<!-- AndroidManifest.xml -->
|
|
90
|
+
<provider
|
|
91
|
+
android:name="androidx.startup.InitializationProvider"
|
|
92
|
+
android:authorities="${applicationId}.androidx-startup"
|
|
93
|
+
android:exported="false">
|
|
94
|
+
|
|
95
|
+
<!-- Register initializers -->
|
|
96
|
+
<meta-data
|
|
97
|
+
android:name="com.example.TimberInitializer"
|
|
98
|
+
android:value="androidx.startup" />
|
|
99
|
+
|
|
100
|
+
<meta-data
|
|
101
|
+
android:name="com.example.AnalyticsInitializer"
|
|
102
|
+
android:value="androidx.startup" />
|
|
103
|
+
|
|
104
|
+
<meta-data
|
|
105
|
+
android:name="com.example.WorkManagerInitializer"
|
|
106
|
+
android:value="androidx.startup" />
|
|
107
|
+
</provider>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Lazy Initialization
|
|
113
|
+
|
|
114
|
+
```kotlin
|
|
115
|
+
// ✅ Initialize on demand — not at startup
|
|
116
|
+
class HeavySdkInitializer : Initializer<HeavySdk> {
|
|
117
|
+
override fun create(context: Context): HeavySdk {
|
|
118
|
+
return HeavySdk.initialize(context)
|
|
119
|
+
}
|
|
120
|
+
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ✅ Disable auto-init in manifest — initialize manually when needed
|
|
124
|
+
// In manifest:
|
|
125
|
+
// <meta-data android:name="com.example.HeavySdkInitializer" android:value="androidx.startup" />
|
|
126
|
+
// Remove this entry to disable auto-init
|
|
127
|
+
|
|
128
|
+
// Initialize manually when first needed
|
|
129
|
+
AppInitializer.getInstance(context)
|
|
130
|
+
.initializeComponent(HeavySdkInitializer::class.java)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Disabling Third-Party SDK Auto-Init
|
|
136
|
+
|
|
137
|
+
```xml
|
|
138
|
+
<!-- ✅ Disable WorkManager's own ContentProvider to avoid duplicate init -->
|
|
139
|
+
<provider
|
|
140
|
+
android:name="androidx.work.impl.WorkManagerInitializer"
|
|
141
|
+
android:authorities="${applicationId}.workmanager-init"
|
|
142
|
+
android:exported="false"
|
|
143
|
+
tools:node="remove" />
|
|
144
|
+
|
|
145
|
+
<!-- ✅ Disable Firebase auto-init if using App Startup instead -->
|
|
146
|
+
<provider
|
|
147
|
+
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
|
148
|
+
android:authorities="${applicationId}.firebaseinitprovider"
|
|
149
|
+
tools:node="remove" />
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Measuring Startup Impact
|
|
155
|
+
|
|
156
|
+
```kotlin
|
|
157
|
+
// ✅ Use systrace to see initializer timing
|
|
158
|
+
// In Initializer.create():
|
|
159
|
+
Trace.beginSection("TimberInitializer")
|
|
160
|
+
try {
|
|
161
|
+
Timber.plant(Timber.DebugTree())
|
|
162
|
+
} finally {
|
|
163
|
+
Trace.endSection()
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Anti-Patterns
|
|
170
|
+
|
|
171
|
+
- Doing network calls inside `Initializer.create()` — runs on main thread
|
|
172
|
+
- Not declaring dependencies between initializers — ordering not guaranteed
|
|
173
|
+
- Registering all SDKs eagerly when most can be lazy — slows startup
|
|
174
|
+
- Initializing inside `Application.onCreate()` AND via App Startup — double initialization
|
|
175
|
+
- Ignoring `tools:node="remove"` for SDKs with their own ContentProviders — duplicate init
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Related Skills
|
|
180
|
+
- `startup-optimization` — broader startup performance strategy
|
|
181
|
+
- `baseline-profile` — pre-compiling initialization code paths
|
|
182
|
+
- `workmanager` — WorkManager initialization via App Startup
|
|
183
|
+
- `lifecycle` — deferring init beyond startup using lifecycle observers
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: baseline-profile
|
|
3
|
+
description: >
|
|
4
|
+
Baseline Profiles for ahead-of-time compilation in Android.
|
|
5
|
+
Load this skill when generating baseline profiles, reducing app
|
|
6
|
+
startup time, improving scroll jank on first run, or integrating
|
|
7
|
+
profile generation into the CI pipeline.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Baseline Profile
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Baseline Profiles tell the Android Runtime (ART) which code paths to compile ahead-of-time (AOT) during app install. Without a profile, ART uses JIT compilation, which is slower on first run. With a baseline profile, critical code paths (startup, scroll, navigation) are pre-compiled, resulting in faster startup and smoother initial interactions.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Generate profiles for **critical user journeys** — startup, main screen scroll, key flows
|
|
20
|
+
- Profile generation requires a **physical device or emulator with API 28+**
|
|
21
|
+
- Profiles must be **regenerated** after major code changes
|
|
22
|
+
- Combine with **Macrobenchmark** to measure the improvement
|
|
23
|
+
- Ship profiles in the release build — they're included in the APK/AAB automatically
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
```toml
|
|
30
|
+
# libs.versions.toml
|
|
31
|
+
[versions]
|
|
32
|
+
macrobenchmark = "1.3.0"
|
|
33
|
+
profileinstaller = "1.3.1"
|
|
34
|
+
|
|
35
|
+
[libraries]
|
|
36
|
+
androidx-profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version.ref = "profileinstaller" }
|
|
37
|
+
androidx-benchmark-macro = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "macrobenchmark" }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```kotlin
|
|
41
|
+
// app/build.gradle.kts
|
|
42
|
+
dependencies {
|
|
43
|
+
implementation(libs.androidx.profileinstaller)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// macrobenchmark/build.gradle.kts (separate module)
|
|
47
|
+
plugins {
|
|
48
|
+
alias(libs.plugins.android.test)
|
|
49
|
+
}
|
|
50
|
+
android {
|
|
51
|
+
targetProjectPath = ":app"
|
|
52
|
+
experimentalProperties["android.experimental.self-instrumenting"] = true
|
|
53
|
+
}
|
|
54
|
+
dependencies {
|
|
55
|
+
implementation(libs.androidx.benchmark.macro)
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Profile Generator
|
|
62
|
+
|
|
63
|
+
```kotlin
|
|
64
|
+
// macrobenchmark module
|
|
65
|
+
@RunWith(AndroidJUnit4::class)
|
|
66
|
+
class BaselineProfileGenerator {
|
|
67
|
+
|
|
68
|
+
@get:Rule
|
|
69
|
+
val rule = BaselineProfileRule()
|
|
70
|
+
|
|
71
|
+
@Test
|
|
72
|
+
fun generate() = rule.collect(
|
|
73
|
+
packageName = "com.example.app",
|
|
74
|
+
profileBlock = {
|
|
75
|
+
// ✅ Cover critical user journeys
|
|
76
|
+
|
|
77
|
+
// 1. App startup
|
|
78
|
+
startActivityAndWait()
|
|
79
|
+
|
|
80
|
+
// 2. Navigate to main screen
|
|
81
|
+
device.findObject(By.text("Home")).click()
|
|
82
|
+
device.waitForIdle()
|
|
83
|
+
|
|
84
|
+
// 3. Scroll through a list
|
|
85
|
+
val list = device.findObject(By.scrollable(true))
|
|
86
|
+
list.fling(Direction.DOWN)
|
|
87
|
+
list.fling(Direction.UP)
|
|
88
|
+
device.waitForIdle()
|
|
89
|
+
|
|
90
|
+
// 4. Open a detail screen
|
|
91
|
+
device.findObject(By.text("First Item")).click()
|
|
92
|
+
device.waitForIdle()
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Generate and Install Profile
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Generate the profile
|
|
104
|
+
./gradlew :macrobenchmark:connectedAndroidTest \
|
|
105
|
+
-Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
|
|
106
|
+
|
|
107
|
+
# Profile is saved to:
|
|
108
|
+
# app/src/main/baseline-prof.txt (automatically)
|
|
109
|
+
# or manually copy from device:
|
|
110
|
+
# adb pull /sdcard/Android/media/com.example.app/baseline-prof.txt app/src/main/
|
|
111
|
+
|
|
112
|
+
# Verify profile is included in release build
|
|
113
|
+
./gradlew :app:assembleRelease
|
|
114
|
+
# Check APK contains assets/dexopt/baseline.prof
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Manual Profile (Simple)
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
# app/src/main/baseline-prof.txt
|
|
123
|
+
# List critical class and method patterns
|
|
124
|
+
|
|
125
|
+
# Startup classes
|
|
126
|
+
Lcom/example/app/MainActivity;
|
|
127
|
+
Lcom/example/app/di/AppModule;
|
|
128
|
+
|
|
129
|
+
# Compose runtime
|
|
130
|
+
Landroidx/compose/runtime/Composer;->**
|
|
131
|
+
Landroidx/compose/ui/platform/AndroidComposeView;->**
|
|
132
|
+
|
|
133
|
+
# Key repository
|
|
134
|
+
Lcom/example/app/data/UserRepository;->getUser(Ljava/lang/String;)**
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Measuring Impact
|
|
140
|
+
|
|
141
|
+
```kotlin
|
|
142
|
+
// ✅ Measure startup with and without baseline profile
|
|
143
|
+
@RunWith(AndroidJUnit4::class)
|
|
144
|
+
class StartupBenchmark {
|
|
145
|
+
@get:Rule
|
|
146
|
+
val benchmarkRule = MacrobenchmarkRule()
|
|
147
|
+
|
|
148
|
+
@Test
|
|
149
|
+
fun startupWithBaselineProfile() = benchmarkRule.measureRepeated(
|
|
150
|
+
packageName = "com.example.app",
|
|
151
|
+
metrics = listOf(StartupTimingMetric()),
|
|
152
|
+
compilationMode = CompilationMode.Partial(
|
|
153
|
+
baselineProfileMode = BaselineProfileMode.Require // ✅ must use profile
|
|
154
|
+
),
|
|
155
|
+
startupMode = StartupMode.COLD,
|
|
156
|
+
iterations = 5
|
|
157
|
+
) {
|
|
158
|
+
pressHome()
|
|
159
|
+
startActivityAndWait()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@Test
|
|
163
|
+
fun startupWithoutProfile() = benchmarkRule.measureRepeated(
|
|
164
|
+
packageName = "com.example.app",
|
|
165
|
+
metrics = listOf(StartupTimingMetric()),
|
|
166
|
+
compilationMode = CompilationMode.None, // no AOT — baseline comparison
|
|
167
|
+
startupMode = StartupMode.COLD,
|
|
168
|
+
iterations = 5
|
|
169
|
+
) {
|
|
170
|
+
pressHome()
|
|
171
|
+
startActivityAndWait()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## CI Integration
|
|
179
|
+
|
|
180
|
+
```yaml
|
|
181
|
+
# .github/workflows/baseline-profile.yml
|
|
182
|
+
- name: Generate Baseline Profile
|
|
183
|
+
run: |
|
|
184
|
+
./gradlew :macrobenchmark:connectedAndroidTest \
|
|
185
|
+
-Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
|
|
186
|
+
# Commit updated baseline-prof.txt to repo
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Anti-Patterns
|
|
192
|
+
|
|
193
|
+
- Not regenerating profiles after significant code changes — stale profile misses new code paths
|
|
194
|
+
- Generating on emulator only — physical device gives more representative profiles
|
|
195
|
+
- Not measuring improvement — can't justify the maintenance cost without data
|
|
196
|
+
- Including only startup in profile generation — also cover scroll and navigation
|
|
197
|
+
- Shipping without `profileinstaller` dependency — profile won't be installed at app install time
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Related Skills
|
|
202
|
+
- `macrobenchmark` — measuring startup and runtime with Macrobenchmark
|
|
203
|
+
- `startup-optimization` — complementary startup improvements
|
|
204
|
+
- `benchmark` — microbenchmark for method-level measurement
|
|
205
|
+
- `app-startup` — App Startup library for initialization ordering
|