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.
Files changed (176) hide show
  1. package/dist/index.js +143 -0
  2. package/package.json +27 -0
  3. package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
  4. package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
  5. package/skills/Android Platform/Configuration/SKILL.md +201 -0
  6. package/skills/Android Platform/Filesystem/SKILL.md +216 -0
  7. package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
  8. package/skills/Android Platform/Manifest/SKILL.md +226 -0
  9. package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
  10. package/skills/Android Platform/Resources/SKILL.md +234 -0
  11. package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
  12. package/skills/Android Platform/State Restoration/SKILL.md +210 -0
  13. package/skills/Architecture/Bounded Context/SKILL.md +207 -0
  14. package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
  15. package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
  16. package/skills/Architecture/Entity Design/SKILL.md +243 -0
  17. package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
  18. package/skills/Architecture/MVI/SKILL.md +224 -0
  19. package/skills/Architecture/MVVM/SKILL.md +198 -0
  20. package/skills/Architecture/Modularization/SKILL.md +194 -0
  21. package/skills/Architecture/Offline First/SKILL.md +249 -0
  22. package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
  23. package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
  24. package/skills/Architecture/State Management/SKILL.md +229 -0
  25. package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
  26. package/skills/Architecture/Use Case Design/SKILL.md +244 -0
  27. package/skills/Architecture/Value Object/SKILL.md +226 -0
  28. package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
  29. package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
  30. package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
  31. package/skills/Build System/Build Cache/SKILL.md +233 -0
  32. package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
  33. package/skills/Build System/Build Variant/SKILL.md +215 -0
  34. package/skills/Build System/Convention Plugin/SKILL.md +288 -0
  35. package/skills/Build System/Dependency Management/SKILL.md +261 -0
  36. package/skills/Build System/Gradle/SKILL.md +284 -0
  37. package/skills/Build System/Incremental Build/SKILL.md +199 -0
  38. package/skills/Build System/KAPT/SKILL.md +198 -0
  39. package/skills/Build System/KSP/SKILL.md +263 -0
  40. package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
  41. package/skills/Build System/Specialized/C++/SKILL.md +308 -0
  42. package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
  43. package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
  44. package/skills/Build System/Version Catalog/SKILL.md +304 -0
  45. package/skills/Concurrency/Background Processing/SKILL.md +185 -0
  46. package/skills/Concurrency/Channel/SKILL.md +207 -0
  47. package/skills/Concurrency/Coroutine/SKILL.md +200 -0
  48. package/skills/Concurrency/Flow/SKILL.md +179 -0
  49. package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
  50. package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
  51. package/skills/Concurrency/StateFlow/SKILL.md +175 -0
  52. package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
  53. package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
  54. package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
  55. package/skills/Core Language/DSL/SKILL.md +186 -0
  56. package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
  57. package/skills/Core Language/Immutability/SKILL.md +156 -0
  58. package/skills/Core Language/KMP/SKILL.md +182 -0
  59. package/skills/Core Language/Kotlin/SKILL.md +187 -0
  60. package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
  61. package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
  62. package/skills/Core Language/Serialization/SKILL.md +191 -0
  63. package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
  64. package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
  65. package/skills/Data Layer/DAO/SKILL.md +225 -0
  66. package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
  67. package/skills/Data Layer/DataStore/SKILL.md +264 -0
  68. package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
  69. package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
  70. package/skills/Data Layer/File Storage/SKILL.md +247 -0
  71. package/skills/Data Layer/Indexing/SKILL.md +184 -0
  72. package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
  73. package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
  74. package/skills/Data Layer/Migration/SKILL.md +243 -0
  75. package/skills/Data Layer/Paging/SKILL.md +264 -0
  76. package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
  77. package/skills/Data Layer/Room/SKILL.md +244 -0
  78. package/skills/Data Layer/SQLite/SKILL.md +255 -0
  79. package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
  80. package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
  81. package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
  82. package/skills/Dependency Injection/Koin/SKILL.md +282 -0
  83. package/skills/Developer Experience/Detekt/SKILL.md +272 -0
  84. package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
  85. package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
  86. package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
  87. package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
  88. package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
  89. package/skills/Media/Audio/SKILL.md +257 -0
  90. package/skills/Media/Camera/SKILL.md +229 -0
  91. package/skills/Media/CameraX/SKILL.md +295 -0
  92. package/skills/Media/ExoPlayer/SKILL.md +258 -0
  93. package/skills/Media/Video/SKILL.md +228 -0
  94. package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
  95. package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
  96. package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
  97. package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
  98. package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
  99. package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
  100. package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
  101. package/skills/Navigation/Navigation/SKILL.md +215 -0
  102. package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
  103. package/skills/Networking/API Contract/SKILL.md +220 -0
  104. package/skills/Networking/Authentication/SKILL.md +210 -0
  105. package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
  106. package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
  107. package/skills/Networking/Ktor/SKILL.md +219 -0
  108. package/skills/Networking/Multipart Upload/SKILL.md +213 -0
  109. package/skills/Networking/OkHttp/SKILL.md +193 -0
  110. package/skills/Networking/REST/SKILL.md +178 -0
  111. package/skills/Networking/Rate Limiting/SKILL.md +170 -0
  112. package/skills/Networking/Retrofit/SKILL.md +241 -0
  113. package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
  114. package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
  115. package/skills/Networking/WebSocket/SKILL.md +224 -0
  116. package/skills/Observability/Crash Reporting/SKILL.md +219 -0
  117. package/skills/Observability/Logging/SKILL.md +168 -0
  118. package/skills/Observability/Metrics/SKILL.md +227 -0
  119. package/skills/Observability/Structured Logging/SKILL.md +234 -0
  120. package/skills/Performance/ANR Prevention/SKILL.md +192 -0
  121. package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
  122. package/skills/Performance/App Startup/SKILL.md +183 -0
  123. package/skills/Performance/Baseline Profile/SKILL.md +205 -0
  124. package/skills/Performance/Battery Optimization/SKILL.md +192 -0
  125. package/skills/Performance/Benchmark/SKILL.md +182 -0
  126. package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
  127. package/skills/Performance/Compose Optimization/SKILL.md +187 -0
  128. package/skills/Performance/Heap Management/SKILL.md +184 -0
  129. package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
  130. package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
  131. package/skills/Performance/Rendering Performance/SKILL.md +205 -0
  132. package/skills/Performance/Startup Optimization/SKILL.md +219 -0
  133. package/skills/Security/Biometric/SKILL.md +224 -0
  134. package/skills/Security/Certificate Transparency/SKILL.md +158 -0
  135. package/skills/Security/Cryptography/SKILL.md +244 -0
  136. package/skills/Security/Encrypted Storage/SKILL.md +273 -0
  137. package/skills/Security/Frida Detection/SKILL.md +230 -0
  138. package/skills/Security/Hook Detection/SKILL.md +197 -0
  139. package/skills/Security/Keystore/SKILL.md +272 -0
  140. package/skills/Security/Network Security Config/SKILL.md +186 -0
  141. package/skills/Security/Obfuscation/SKILL.md +226 -0
  142. package/skills/Security/Proguard/SKILL.md +202 -0
  143. package/skills/Security/R8/SKILL.md +234 -0
  144. package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
  145. package/skills/Security/Root Detection/SKILL.md +220 -0
  146. package/skills/Security/Secure Networking/SKILL.md +220 -0
  147. package/skills/System Integration/AlarmManager/SKILL.md +182 -0
  148. package/skills/System Integration/App Widget/SKILL.md +182 -0
  149. package/skills/System Integration/Deep Link/SKILL.md +187 -0
  150. package/skills/System Integration/Foreground Service/SKILL.md +212 -0
  151. package/skills/System Integration/Notification/SKILL.md +237 -0
  152. package/skills/System Integration/WorkManager/SKILL.md +256 -0
  153. package/skills/System Integration/clipboard/SKILL.md +155 -0
  154. package/skills/System Integration/share-intent/SKILL.md +182 -0
  155. package/skills/Testing/Compose Testing/SKILL.md +296 -0
  156. package/skills/Testing/Espresso/SKILL.md +292 -0
  157. package/skills/Testing/Fake Data/SKILL.md +245 -0
  158. package/skills/Testing/Integration Testing/SKILL.md +288 -0
  159. package/skills/Testing/Mocking/SKILL.md +229 -0
  160. package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
  161. package/skills/Testing/UI Testing/SKILL.md +293 -0
  162. package/skills/Testing/Unit Testing/SKILL.md +309 -0
  163. package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
  164. package/skills/UI System/Compose/SKILL.md +296 -0
  165. package/skills/UI System/Compose Animation/SKILL.md +281 -0
  166. package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
  167. package/skills/UI System/Compose Navigation/SKILL.md +255 -0
  168. package/skills/UI System/Compose Performance/SKILL.md +274 -0
  169. package/skills/UI System/Design System/SKILL.md +217 -0
  170. package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
  171. package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
  172. package/skills/UI System/Loading Strategy/SKILL.md +254 -0
  173. package/skills/UI System/Material 3/SKILL.md +279 -0
  174. package/skills/UI System/RTL/SKILL.md +179 -0
  175. package/src/index.ts +182 -0
  176. package/tsconfig.json +19 -0
@@ -0,0 +1,187 @@
1
+ ---
2
+ name: compose-optimization
3
+ description: >
4
+ Jetpack Compose performance optimization.
5
+ Load this skill when reducing unnecessary recompositions, using
6
+ stability annotations, optimizing LazyList performance, profiling
7
+ with Layout Inspector, or fixing skipped frames in Compose UI.
8
+ ---
9
+
10
+ # Compose Optimization
11
+
12
+ ## Overview
13
+ Compose performance is dominated by **recomposition** — the process of re-running composables when state changes. Unnecessary recompositions cause dropped frames. The goal is to ensure only the composables that actually depend on changed state are recomposed, using stable types, correct key usage, and proper state scoping.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Recomposition is triggered by **state reads** — scope reads as narrowly as possible
20
+ - Use **stable types** for parameters — unstable types cause recomposition on every parent recompose
21
+ - Pass **lambdas** instead of state to leaf composables — reduces recomposition scope
22
+ - Use `key()` in `LazyColumn` — helps Compose identify and reuse items correctly
23
+ - Profile with **Compose Compiler Metrics** and **Layout Inspector** before optimizing
24
+
25
+ ---
26
+
27
+ ## Stability and @Stable / @Immutable
28
+
29
+ ```kotlin
30
+ // ❌ Unstable class — List is not stable (Compose can't verify it won't change)
31
+ data class UserListState(
32
+ val users: List<User>, // mutable List — Compose treats as unstable
33
+ val isLoading: Boolean
34
+ )
35
+
36
+ // ✅ Use ImmutableList or @Immutable annotation
37
+ @Immutable
38
+ data class UserListState(
39
+ val users: ImmutableList<User>, // kotlinx.collections.immutable
40
+ val isLoading: Boolean
41
+ )
42
+
43
+ // ✅ Or annotate domain model as stable
44
+ @Stable
45
+ data class User(
46
+ val id: String,
47
+ val name: String,
48
+ val email: String
49
+ )
50
+ ```
51
+
52
+ ---
53
+
54
+ ## State Scoping — Read State as Low as Possible
55
+
56
+ ```kotlin
57
+ // ❌ Reading state high in the tree — recomposes entire subtree
58
+ @Composable
59
+ fun UserScreen(viewModel: UserViewModel) {
60
+ val state by viewModel.state.collectAsStateWithLifecycle()
61
+ // entire screen recomposes when any state field changes
62
+ UserList(users = state.users, isLoading = state.isLoading)
63
+ SearchBar(query = state.searchQuery)
64
+ }
65
+
66
+ // ✅ Pass lambdas to defer state reads to leaf composables
67
+ @Composable
68
+ fun UserScreen(viewModel: UserViewModel) {
69
+ UserList(
70
+ users = { viewModel.state.value.users }, // read inside UserList
71
+ isLoading = { viewModel.state.value.isLoading }
72
+ )
73
+ }
74
+ ```
75
+
76
+ ---
77
+
78
+ ## LazyColumn Optimization
79
+
80
+ ```kotlin
81
+ // ✅ Always provide stable keys
82
+ LazyColumn {
83
+ items(
84
+ items = users,
85
+ key = { user -> user.id } // ✅ stable key — enables item reuse
86
+ ) { user ->
87
+ UserItem(user = user)
88
+ }
89
+ }
90
+
91
+ // ✅ Use contentType for heterogeneous lists
92
+ LazyColumn {
93
+ items(
94
+ items = feedItems,
95
+ key = { it.id },
96
+ contentType = { item -> item::class } // ✅ different composables for different types
97
+ ) { item ->
98
+ when (item) {
99
+ is FeedItem.Post -> PostItem(item)
100
+ is FeedItem.Ad -> AdItem(item)
101
+ }
102
+ }
103
+ }
104
+
105
+ // ✅ rememberLazyListState to preserve scroll position
106
+ val listState = rememberLazyListState()
107
+ LazyColumn(state = listState) { ... }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## remember and derivedStateOf
113
+
114
+ ```kotlin
115
+ // ✅ derivedStateOf — compute derived value only when inputs change
116
+ @Composable
117
+ fun UserList(users: List<User>, searchQuery: String) {
118
+ val filteredUsers by remember(users, searchQuery) {
119
+ derivedStateOf {
120
+ users.filter { it.name.contains(searchQuery, ignoreCase = true) }
121
+ }
122
+ }
123
+ // filteredUsers recomputes only when users or searchQuery change
124
+ }
125
+
126
+ // ✅ remember expensive computation
127
+ @Composable
128
+ fun ExpensiveScreen(data: List<Data>) {
129
+ val processed = remember(data) {
130
+ data.map { processExpensive(it) } // only recomputes when data changes
131
+ }
132
+ }
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Avoiding Lambda Allocations
138
+
139
+ ```kotlin
140
+ // ❌ New lambda on every recomposition
141
+ @Composable
142
+ fun UserItem(user: User, onDelete: (User) -> Unit) {
143
+ Button(onClick = { onDelete(user) }) { ... } // new lambda every recompose
144
+ }
145
+
146
+ // ✅ remember the lambda
147
+ @Composable
148
+ fun UserItem(user: User, onDelete: (User) -> Unit) {
149
+ val onClick = remember(user.id) { { onDelete(user) } }
150
+ Button(onClick = onClick) { ... }
151
+ }
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Compose Compiler Metrics
157
+
158
+ ```kotlin
159
+ // build.gradle.kts — enable compiler metrics
160
+ tasks.withType<KotlinCompile>().configureEach {
161
+ kotlinOptions {
162
+ freeCompilerArgs += listOf(
163
+ "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${project.buildDir}/compose_metrics",
164
+ "-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=${project.buildDir}/compose_metrics"
165
+ )
166
+ }
167
+ }
168
+ // Then check build/compose_metrics/ for stability reports
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Anti-Patterns
174
+
175
+ - Passing unstable `List<T>` to composables — triggers recomposition unnecessarily
176
+ - Reading `StateFlow.value` directly in composable body — doesn't trigger recomposition
177
+ - No `key` in `LazyColumn` items — items are recreated instead of reused on scroll
178
+ - Doing heavy computation directly in composable body — use `remember` or `derivedStateOf`
179
+ - Deeply nested composables all reading the same state — scope reads lower in the tree
180
+
181
+ ---
182
+
183
+ ## Related Skills
184
+ - `compose` — Compose fundamentals
185
+ - `compose-performance` — profiling and measuring Compose performance
186
+ - `state-management` — state hoisting and StateFlow patterns
187
+ - `allocation-optimization` — reducing object creation in Compose
@@ -0,0 +1,184 @@
1
+ ---
2
+ name: heap-management
3
+ description: >
4
+ Android heap memory management and optimization.
5
+ Load this skill when investigating OutOfMemoryError, reducing memory
6
+ footprint, understanding heap limits, managing large objects,
7
+ or profiling memory usage with Android Studio Memory Profiler.
8
+ ---
9
+
10
+ # Heap Management
11
+
12
+ ## Overview
13
+ Each Android app has a limited heap size (typically 128–512MB depending on device). Exceeding this limit causes `OutOfMemoryError`. Proper heap management involves avoiding large allocations, reusing objects, releasing memory under pressure, and profiling with Android Studio Memory Profiler.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Never hold large objects (Bitmap, byte arrays) longer than necessary
20
+ - Release memory in `onTrimMemory` — the system signals when memory is low
21
+ - Use object pools for frequently created/destroyed objects
22
+ - Profile memory with **Android Studio Memory Profiler** before optimizing
23
+ - Prefer streaming over loading entire files into memory
24
+
25
+ ---
26
+
27
+ ## Heap Limits
28
+
29
+ ```kotlin
30
+ // ✅ Check available heap at runtime
31
+ val runtime = Runtime.getRuntime()
32
+ val maxHeap = runtime.maxMemory() // max heap the app can use
33
+ val usedHeap = runtime.totalMemory() - runtime.freeMemory()
34
+ val availableHeap = maxHeap - usedHeap
35
+
36
+ Timber.d("Heap: used=${usedHeap/1024/1024}MB, max=${maxHeap/1024/1024}MB")
37
+
38
+ // ✅ Request large heap in manifest (use sparingly)
39
+ // android:largeHeap="true" in <application> — last resort
40
+ ```
41
+
42
+ ---
43
+
44
+ ## onTrimMemory — Release Under Pressure
45
+
46
+ ```kotlin
47
+ // ✅ Release non-essential memory when system requests it
48
+ class App : Application() {
49
+ override fun onTrimMemory(level: Int) {
50
+ super.onTrimMemory(level)
51
+ when (level) {
52
+ TRIM_MEMORY_UI_HIDDEN -> {
53
+ // App went to background — release UI caches
54
+ imageCache.evictAll()
55
+ }
56
+ TRIM_MEMORY_RUNNING_CRITICAL,
57
+ TRIM_MEMORY_COMPLETE -> {
58
+ // Critical — release everything possible
59
+ imageCache.evictAll()
60
+ dataCache.clear()
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ // ✅ Implement in ViewModel too
67
+ class UserListViewModel : ViewModel(), ComponentCallbacks2 {
68
+ override fun onTrimMemory(level: Int) {
69
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
70
+ cachedUsers.clear()
71
+ }
72
+ }
73
+ override fun onConfigurationChanged(newConfig: Configuration) {}
74
+ override fun onLowMemory() { cachedUsers.clear() }
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## LruCache for Memory Caching
81
+
82
+ ```kotlin
83
+ // ✅ LruCache — evicts least-recently-used entries when full
84
+ class ImageMemoryCache {
85
+ private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
86
+ private val cacheSize = maxMemory / 8 // use 1/8 of available heap
87
+
88
+ private val cache = object : LruCache<String, Bitmap>(cacheSize) {
89
+ override fun sizeOf(key: String, bitmap: Bitmap): Int {
90
+ return bitmap.byteCount / 1024 // size in KB
91
+ }
92
+ }
93
+
94
+ fun put(key: String, bitmap: Bitmap) = cache.put(key, bitmap)
95
+ fun get(key: String): Bitmap? = cache.get(key)
96
+ fun evictAll() = cache.evictAll()
97
+ }
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Avoiding Large Allocations
103
+
104
+ ```kotlin
105
+ // ❌ Loading entire file into memory
106
+ val bytes = File(path).readBytes() // OOM for large files
107
+
108
+ // ✅ Stream the file
109
+ File(path).inputStream().use { stream ->
110
+ val buffer = ByteArray(8192) // 8KB buffer
111
+ var bytesRead: Int
112
+ while (stream.read(buffer).also { bytesRead = it } != -1) {
113
+ process(buffer, bytesRead)
114
+ }
115
+ }
116
+
117
+ // ❌ Parsing entire JSON response before using it
118
+ val json = response.body?.string() // loads everything into memory
119
+
120
+ // ✅ Stream JSON parsing with Kotlinx Serialization
121
+ val users = json.decodeFromStream<List<UserDto>>(response.body!!.byteStream())
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Object Pooling
127
+
128
+ ```kotlin
129
+ // ✅ Reuse objects instead of creating new ones in tight loops
130
+ class RectPool {
131
+ private val pool = ArrayDeque<RectF>()
132
+
133
+ fun acquire(): RectF = pool.removeFirstOrNull() ?: RectF()
134
+
135
+ fun release(rect: RectF) {
136
+ rect.setEmpty()
137
+ pool.addLast(rect)
138
+ }
139
+ }
140
+
141
+ // ✅ Usage
142
+ val rectPool = RectPool()
143
+ val rect = rectPool.acquire()
144
+ try {
145
+ // use rect
146
+ } finally {
147
+ rectPool.release(rect)
148
+ }
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Profiling Tools
154
+
155
+ ```
156
+ Android Studio Memory Profiler:
157
+ - Heap dump: snapshot of all objects in memory
158
+ - Allocation tracking: which code allocates the most
159
+ - GC events: frequency indicates memory pressure
160
+
161
+ Key metrics to watch:
162
+ - Java Heap: should not grow unboundedly
163
+ - Native Heap: images, bitmaps (decoded natively)
164
+ - Shallow size: size of object itself
165
+ - Retained size: size of object + everything it keeps alive
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Anti-Patterns
171
+
172
+ - Holding Bitmap references in static fields — never GC'd
173
+ - Caching without size limits — unbounded cache grows until OOM
174
+ - Loading full-resolution images for thumbnails — decode at required size
175
+ - Creating many small objects in tight loops — GC pressure
176
+ - Not implementing `onTrimMemory` — app doesn't release memory under pressure
177
+
178
+ ---
179
+
180
+ ## Related Skills
181
+ - `bitmap-optimization` — efficient Bitmap loading and scaling
182
+ - `memory-leak-prevention` — preventing reference leaks
183
+ - `compose-optimization` — reducing recompositions and allocations
184
+ - `startup-optimization` — reducing memory allocations at startup
@@ -0,0 +1,214 @@
1
+ ---
2
+ name: macrobenchmark
3
+ description: >
4
+ Macrobenchmark for end-to-end performance measurement in Android.
5
+ Load this skill when measuring app startup time, scroll jank,
6
+ navigation transitions, or any user journey performance metric
7
+ using the Macrobenchmark library.
8
+ ---
9
+
10
+ # Macrobenchmark
11
+
12
+ ## Overview
13
+ Macrobenchmark measures complete user journeys — startup, scroll performance, navigation — from outside the app using UI automation. Unlike microbenchmark, it measures the full system including rendering, I/O, and process startup. It is the standard tool for startup time regression testing and scroll jank measurement.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Macrobenchmark runs in a **separate test process** — it instruments the app from outside
20
+ - Always test in **release** or **benchmark** build type — not debug
21
+ - Use `StartupMode.COLD` for startup measurement — ensures process is killed first
22
+ - Test on **physical devices** — emulator performance is not representative
23
+ - Run multiple iterations — Macrobenchmark reports min, median, and max automatically
24
+
25
+ ---
26
+
27
+ ## Setup
28
+
29
+ ```toml
30
+ # libs.versions.toml
31
+ [libraries]
32
+ androidx-benchmark-macro = { module = "androidx.benchmark:benchmark-macro-junit4", version = "1.3.0" }
33
+ androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.3.0" }
34
+ ```
35
+
36
+ ```kotlin
37
+ // macrobenchmark/build.gradle.kts
38
+ plugins {
39
+ alias(libs.plugins.android.test)
40
+ }
41
+
42
+ android {
43
+ targetProjectPath = ":app"
44
+ experimentalProperties["android.experimental.self-instrumenting"] = true
45
+
46
+ defaultConfig {
47
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
48
+ testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR"
49
+ }
50
+
51
+ buildTypes {
52
+ create("benchmark") {
53
+ isDebuggable = true
54
+ signingConfig = signingConfigs.getByName("debug")
55
+ matchingFallbacks += listOf("release")
56
+ }
57
+ }
58
+ }
59
+
60
+ dependencies {
61
+ implementation(libs.androidx.benchmark.macro)
62
+ implementation(libs.androidx.test.uiautomator)
63
+ }
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Startup Benchmark
69
+
70
+ ```kotlin
71
+ @RunWith(AndroidJUnit4::class)
72
+ class StartupBenchmark {
73
+
74
+ @get:Rule
75
+ val benchmarkRule = MacrobenchmarkRule()
76
+
77
+ @Test
78
+ fun coldStart() = benchmarkRule.measureRepeated(
79
+ packageName = "com.example.app",
80
+ metrics = listOf(StartupTimingMetric()),
81
+ compilationMode = CompilationMode.Partial(
82
+ baselineProfileMode = BaselineProfileMode.Require
83
+ ),
84
+ startupMode = StartupMode.COLD, // kill process before each iteration
85
+ iterations = 5
86
+ ) {
87
+ pressHome()
88
+ startActivityAndWait() // wait for first frame
89
+ }
90
+
91
+ @Test
92
+ fun warmStart() = benchmarkRule.measureRepeated(
93
+ packageName = "com.example.app",
94
+ metrics = listOf(StartupTimingMetric()),
95
+ compilationMode = CompilationMode.Partial(),
96
+ startupMode = StartupMode.WARM, // process alive, Activity recreated
97
+ iterations = 5
98
+ ) {
99
+ startActivityAndWait()
100
+ }
101
+ }
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Scroll Jank Benchmark
107
+
108
+ ```kotlin
109
+ @RunWith(AndroidJUnit4::class)
110
+ class ScrollBenchmark {
111
+
112
+ @get:Rule
113
+ val benchmarkRule = MacrobenchmarkRule()
114
+
115
+ @Test
116
+ fun scrollUserList() = benchmarkRule.measureRepeated(
117
+ packageName = "com.example.app",
118
+ metrics = listOf(FrameTimingMetric()), // measures frame timing and jank
119
+ compilationMode = CompilationMode.Partial(),
120
+ startupMode = StartupMode.WARM,
121
+ iterations = 5
122
+ ) {
123
+ startActivityAndWait()
124
+
125
+ // Navigate to list screen
126
+ device.findObject(By.text("Users")).click()
127
+ device.waitForIdle()
128
+
129
+ // ✅ Scroll the list — FrameTimingMetric captures frame data
130
+ val list = device.findObject(By.scrollable(true))
131
+ repeat(3) {
132
+ list.fling(Direction.DOWN)
133
+ device.waitForIdle()
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Custom Metrics
142
+
143
+ ```kotlin
144
+ // ✅ Measure time to specific state (e.g. data loaded)
145
+ @Test
146
+ fun timeToDataLoaded() = benchmarkRule.measureRepeated(
147
+ packageName = "com.example.app",
148
+ metrics = listOf(
149
+ StartupTimingMetric(),
150
+ TraceSectionMetric("data_loaded") // custom trace section
151
+ ),
152
+ startupMode = StartupMode.COLD,
153
+ iterations = 5
154
+ ) {
155
+ pressHome()
156
+ startActivityAndWait()
157
+ // Wait for custom trace section to appear
158
+ device.wait(Until.hasObject(By.text("Users loaded")), 5_000)
159
+ }
160
+
161
+ // In app code — mark the trace section
162
+ class UserListViewModel : ViewModel() {
163
+ fun loadUsers() {
164
+ viewModelScope.launch {
165
+ val users = repository.getUsers()
166
+ trace("data_loaded") { // ✅ marks trace section for benchmark
167
+ _state.value = UiState.Success(users)
168
+ }
169
+ }
170
+ }
171
+ }
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Compilation Modes
177
+
178
+ ```kotlin
179
+ // CompilationMode.None — no AOT, pure JIT (worst case baseline)
180
+ // CompilationMode.Partial() — use existing baseline profile if present
181
+ // CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require) — must have profile
182
+ // CompilationMode.Full — fully AOT compiled (best case)
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Running and Reading Results
188
+
189
+ ```bash
190
+ # Run benchmarks
191
+ ./gradlew :macrobenchmark:connectedBenchmarkAndroidTest
192
+
193
+ # Results saved to:
194
+ # build/outputs/connected_android_test_additional_output/benchmark/
195
+ # Open in Android Studio via Profiler → Import benchmark results
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Anti-Patterns
201
+
202
+ - Running on emulator — performance not representative
203
+ - Testing in debug build — JIT + debug overhead inflates results
204
+ - Not using `startActivityAndWait()` — benchmark ends before first frame
205
+ - Single iteration — statistical noise makes results unreliable
206
+ - Not using `CompilationMode.None` as baseline — can't see profile improvement
207
+
208
+ ---
209
+
210
+ ## Related Skills
211
+ - `baseline-profile` — generating profiles measured by Macrobenchmark
212
+ - `benchmark` — microbenchmark for method-level measurement
213
+ - `startup-optimization` — techniques to improve startup metrics
214
+ - `rendering-performance` — understanding frame timing results