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,218 @@
1
+ ---
2
+ name: memory-leak-prevention
3
+ description: >
4
+ Memory leak detection and prevention in Android.
5
+ Load this skill when investigating memory leaks, auditing code for
6
+ common leak patterns, using LeakCanary, fixing Context leaks,
7
+ or ensuring proper cleanup of listeners and callbacks.
8
+ ---
9
+
10
+ # Memory Leak Prevention
11
+
12
+ ## Overview
13
+ A memory leak occurs when an object is no longer needed but cannot be garbage collected because something still holds a reference to it. On Android, the most common leaks involve holding a reference to an Activity or Fragment beyond their lifecycle. LeakCanary is the standard tool for detection.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Never store `Activity` or `Fragment` references in long-lived objects (ViewModel, singleton)
20
+ - Always unregister listeners, observers, and callbacks in the symmetric lifecycle callback
21
+ - Use `applicationContext` in singletons — not `Activity` context
22
+ - Clear ViewBinding references in `onDestroyView`
23
+ - Use `WeakReference` only as a last resort — fix the design instead
24
+
25
+ ---
26
+
27
+ ## LeakCanary Setup
28
+
29
+ ```toml
30
+ # libs.versions.toml
31
+ [libraries]
32
+ leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version = "2.14" }
33
+ ```
34
+
35
+ ```kotlin
36
+ // build.gradle.kts — debug only
37
+ dependencies {
38
+ debugImplementation(libs.leakcanary)
39
+ }
40
+ // No code needed — LeakCanary auto-installs via ContentProvider
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Common Leak Patterns and Fixes
46
+
47
+ ```kotlin
48
+ // ❌ Storing Activity in a singleton — leak
49
+ object ImageLoader {
50
+ var context: Context? = null // holds Activity reference
51
+ }
52
+
53
+ // ✅ Use ApplicationContext
54
+ object ImageLoader {
55
+ lateinit var appContext: Context
56
+
57
+ fun init(context: Context) {
58
+ appContext = context.applicationContext // safe
59
+ }
60
+ }
61
+
62
+ // ❌ Anonymous listener holding Activity reference
63
+ class UserActivity : AppCompatActivity() {
64
+ override fun onCreate(savedInstanceState: Bundle?) {
65
+ super.onCreate(savedInstanceState)
66
+ someManager.setListener(object : SomeListener {
67
+ override fun onEvent() {
68
+ updateUi() // captures Activity implicitly
69
+ }
70
+ })
71
+ // listener never removed — Activity leaked
72
+ }
73
+ }
74
+
75
+ // ✅ Remove listener in onDestroy
76
+ class UserActivity : AppCompatActivity() {
77
+ private val listener = SomeListener { updateUi() }
78
+
79
+ override fun onCreate(savedInstanceState: Bundle?) {
80
+ super.onCreate(savedInstanceState)
81
+ someManager.addListener(listener)
82
+ }
83
+
84
+ override fun onDestroy() {
85
+ someManager.removeListener(listener)
86
+ super.onDestroy()
87
+ }
88
+ }
89
+ ```
90
+
91
+ ---
92
+
93
+ ## ViewBinding Leak
94
+
95
+ ```kotlin
96
+ // ❌ Binding held past onDestroyView — Fragment leak
97
+ class UserFragment : Fragment() {
98
+ private lateinit var binding: FragmentUserBinding // never cleared
99
+
100
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
101
+ binding = FragmentUserBinding.bind(view)
102
+ }
103
+ // Fragment view destroyed but binding still holds reference to views
104
+ }
105
+
106
+ // ✅ Clear binding in onDestroyView
107
+ class UserFragment : Fragment() {
108
+ private var _binding: FragmentUserBinding? = null
109
+ private val binding get() = _binding!!
110
+
111
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
112
+ super.onViewCreated(view, savedInstanceState)
113
+ _binding = FragmentUserBinding.bind(view)
114
+ }
115
+
116
+ override fun onDestroyView() {
117
+ _binding = null // ✅ clear reference
118
+ super.onDestroyView()
119
+ }
120
+ }
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Coroutine Scope Leak
126
+
127
+ ```kotlin
128
+ // ❌ Custom scope not cancelled — coroutine leak
129
+ class MyManager {
130
+ private val scope = CoroutineScope(Dispatchers.IO)
131
+
132
+ fun start() {
133
+ scope.launch { doWork() }
134
+ }
135
+ // scope never cancelled — coroutines run forever
136
+ }
137
+
138
+ // ✅ Cancel scope when no longer needed
139
+ class MyManager {
140
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
141
+
142
+ fun start() {
143
+ scope.launch { doWork() }
144
+ }
145
+
146
+ fun stop() {
147
+ scope.cancel() // ✅ cancels all children
148
+ }
149
+ }
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Handler and Runnable Leak
155
+
156
+ ```kotlin
157
+ // ❌ Handler posting to Activity that may be destroyed
158
+ class UserActivity : AppCompatActivity() {
159
+ private val handler = Handler(Looper.getMainLooper())
160
+
161
+ override fun onStart() {
162
+ super.onStart()
163
+ handler.postDelayed({ updateUi() }, 5_000) // captures Activity
164
+ }
165
+ // runnable runs even after Activity destroyed
166
+ }
167
+
168
+ // ✅ Cancel pending callbacks in onStop
169
+ class UserActivity : AppCompatActivity() {
170
+ private val handler = Handler(Looper.getMainLooper())
171
+ private val updateRunnable = Runnable { updateUi() }
172
+
173
+ override fun onStart() {
174
+ super.onStart()
175
+ handler.postDelayed(updateRunnable, 5_000)
176
+ }
177
+
178
+ override fun onStop() {
179
+ handler.removeCallbacks(updateRunnable) // ✅
180
+ super.onStop()
181
+ }
182
+ }
183
+ ```
184
+
185
+ ---
186
+
187
+ ## ViewModel Holding View Reference
188
+
189
+ ```kotlin
190
+ // ❌ ViewModel holding Composable lambda or View reference
191
+ class UserViewModel : ViewModel() {
192
+ var onSuccess: (() -> Unit)? = null // holds UI reference — leak
193
+ }
194
+
195
+ // ✅ Use events via Channel or SharedFlow — no direct reference
196
+ class UserViewModel : ViewModel() {
197
+ private val _events = Channel<UserEvent>(Channel.BUFFERED)
198
+ val events = _events.receiveAsFlow()
199
+ }
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Anti-Patterns
205
+
206
+ - Passing `Activity` context to a singleton or ViewModel
207
+ - Not removing `BroadcastReceiver` registered dynamically
208
+ - Not unregistering `SensorManager` or `LocationManager` listeners
209
+ - Storing references to Composable lambdas in ViewModel
210
+ - Using `registerForActivityResult` outside of `onCreate` or composable
211
+
212
+ ---
213
+
214
+ ## Related Skills
215
+ - `lifecycle` — symmetric resource acquisition and release
216
+ - `viewmodel` — ViewModel scope and what it should not hold
217
+ - `coroutine` — scope cancellation to prevent coroutine leaks
218
+ - `compose` — DisposableEffect for Compose-specific cleanup
@@ -0,0 +1,205 @@
1
+ ---
2
+ name: rendering-performance
3
+ description: >
4
+ UI rendering performance optimization in Android.
5
+ Load this skill when diagnosing dropped frames, reducing overdraw,
6
+ optimizing custom view drawing, improving scroll performance,
7
+ or profiling with GPU Overdraw and Frame Pacing tools.
8
+ ---
9
+
10
+ # Rendering Performance
11
+
12
+ ## Overview
13
+ Android targets 60fps (16ms per frame) or 120fps (8ms per frame) on high-refresh devices. Dropped frames cause visible jank. The main causes are: work on the main thread during frame rendering, overdraw, expensive layout passes, and large hierarchies. The key tool is the **System Trace** and **GPU Overdraw** visualizer.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Keep the **main thread free** during frame rendering — offload work to coroutines
20
+ - Avoid **overdraw** — drawing the same pixel multiple times wastes GPU time
21
+ - Flatten view hierarchies — deep nesting causes expensive measure/layout passes
22
+ - Avoid `invalidate()` calls that trigger full redraws — invalidate only dirty regions
23
+ - Use hardware acceleration — it's on by default, don't disable it
24
+
25
+ ---
26
+
27
+ ## Frame Budget
28
+
29
+ ```
30
+ 60fps target → 16ms per frame
31
+ 120fps target → 8ms per frame
32
+
33
+ Frame work breakdown:
34
+ Input handling ~2ms
35
+ Animation ~2ms
36
+ Measure/Layout ~4ms
37
+ Draw ~4ms
38
+ Sync & Upload ~2ms
39
+ GPU work ~2ms
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Detecting Jank
45
+
46
+ ```kotlin
47
+ // ✅ FrameMetrics API — detect dropped frames programmatically
48
+ class PerformanceMonitor(private val activity: Activity) {
49
+
50
+ private val frameMetricsListener = Window.OnFrameMetricsAvailableListener { _, frameMetrics, _ ->
51
+ val totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION) / 1_000_000L // ns to ms
52
+ if (totalDuration > 16) {
53
+ Timber.w("Slow frame: ${totalDuration}ms")
54
+ }
55
+ }
56
+
57
+ fun start() {
58
+ activity.window.addOnFrameMetricsAvailableListener(
59
+ frameMetricsListener,
60
+ Handler(Looper.getMainLooper())
61
+ )
62
+ }
63
+
64
+ fun stop() {
65
+ activity.window.removeOnFrameMetricsAvailableListener(frameMetricsListener)
66
+ }
67
+ }
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Reducing Overdraw
73
+
74
+ ```kotlin
75
+ // ✅ Remove redundant backgrounds
76
+ // In Compose — don't set background on every layer
77
+ @Composable
78
+ fun Screen() {
79
+ // ❌ Both Scaffold and Column have background — overdraw
80
+ Scaffold {
81
+ Column(modifier = Modifier.background(Color.White)) { ... }
82
+ }
83
+
84
+ // ✅ Only one background needed — Scaffold handles it
85
+ Scaffold {
86
+ Column { ... }
87
+ }
88
+ }
89
+
90
+ // ✅ In XML theme — remove window background if screen has its own
91
+ // android:windowBackground="@null" in theme (if screen fills the window)
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Compose Rendering Tips
97
+
98
+ ```kotlin
99
+ // ✅ Use graphicsLayer for hardware-accelerated transformations
100
+ Box(
101
+ modifier = Modifier
102
+ .graphicsLayer {
103
+ scaleX = scale
104
+ scaleY = scale
105
+ alpha = opacity
106
+ // GPU-accelerated — doesn't trigger recomposition
107
+ }
108
+ )
109
+
110
+ // ✅ Avoid clip + shadow on the same composable — expensive
111
+ // Instead, use separate layers
112
+ Box(
113
+ modifier = Modifier
114
+ .shadow(elevation = 4.dp, shape = RoundedCornerShape(8.dp))
115
+ // shadow handles clipping internally
116
+ )
117
+
118
+ // ✅ Use drawWithCache for custom drawing that doesn't change every frame
119
+ Modifier.drawWithCache {
120
+ val path = Path()
121
+ path.addRoundRect(...) // computed once, reused across frames
122
+ onDrawBehind {
123
+ drawPath(path, color = Color.Blue)
124
+ }
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Main Thread Protection
131
+
132
+ ```kotlin
133
+ // ✅ Never block main thread during scroll/animation
134
+ @Composable
135
+ fun UserList(viewModel: UserListViewModel) {
136
+ val users by viewModel.users.collectAsStateWithLifecycle()
137
+
138
+ LazyColumn {
139
+ items(users, key = { it.id }) { user ->
140
+ // ❌ Don't do I/O or heavy computation here
141
+ // val data = File(user.avatarPath).readBytes() // blocks main thread
142
+
143
+ // ✅ Data already loaded — just render
144
+ UserItem(user = user)
145
+ }
146
+ }
147
+ }
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Custom View — Dirty Region Invalidation
153
+
154
+ ```kotlin
155
+ // ✅ Invalidate only the changed region — not the entire view
156
+ class ProgressView(context: Context) : View(context) {
157
+ private var progress = 0f
158
+ private val dirtyRect = Rect()
159
+
160
+ fun setProgress(value: Float) {
161
+ val oldRight = (progress * width).toInt()
162
+ progress = value
163
+ val newRight = (progress * width).toInt()
164
+
165
+ // Invalidate only the changed horizontal strip
166
+ dirtyRect.set(minOf(oldRight, newRight), 0, maxOf(oldRight, newRight), height)
167
+ invalidate(dirtyRect) // ✅ partial invalidation
168
+ }
169
+ }
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Profiling Tools
175
+
176
+ ```
177
+ GPU Overdraw (Developer Options → Debug GPU overdraw):
178
+ Blue = 1x overdraw (acceptable)
179
+ Green = 2x overdraw (minor issue)
180
+ Pink = 3x overdraw (investigate)
181
+ Red = 4x+ overdraw (fix immediately)
182
+
183
+ System Trace (Android Studio Profiler → CPU → System Trace):
184
+ - See exact frame timing
185
+ - Identify which method causes the long frame
186
+ - Find binder calls, lock contention on main thread
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Anti-Patterns
192
+
193
+ - Performing disk or network I/O on the main thread — causes frame drops
194
+ - Deep nested `ConstraintLayout` inside `ConstraintLayout` — expensive measure passes
195
+ - Setting background on every layout level — causes overdraw
196
+ - Calling `invalidate()` on every frame when nothing changed
197
+ - Using `wrap_content` on `RecyclerView` — measures all items unnecessarily
198
+
199
+ ---
200
+
201
+ ## Related Skills
202
+ - `compose-optimization` — Compose-specific recomposition and stability
203
+ - `allocation-optimization` — reducing GC pauses during rendering
204
+ - `benchmark` — measuring frame rendering with Macrobenchmark
205
+ - `anr-prevention` — keeping the main thread free from blocking work
@@ -0,0 +1,219 @@
1
+ ---
2
+ name: startup-optimization
3
+ description: >
4
+ App startup time optimization for Android.
5
+ Load this skill when reducing cold/warm start time, using App Startup
6
+ library, deferring heavy initialization, measuring startup performance,
7
+ or eliminating unnecessary work during application launch.
8
+ ---
9
+
10
+ # Startup Optimization
11
+
12
+ ## Overview
13
+ App startup time directly impacts user retention. Cold start (process not in memory) is the most expensive. The goal is to do the minimum work needed before the first frame is drawn, and defer everything else. Tools include the App Startup library, baseline profiles, and Macrobenchmark.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Do **minimal work** in `Application.onCreate()` — defer everything non-essential
20
+ - Use **App Startup library** to control and sequence initializer order
21
+ - Never run **network calls or disk I/O** synchronously during startup
22
+ - Use **Baseline Profiles** to pre-compile critical code paths
23
+ - Measure with **Macrobenchmark** — not manual timing
24
+
25
+ ---
26
+
27
+ ## What Happens During Cold Start
28
+
29
+ ```
30
+ 1. Process creation
31
+ 2. Application.onCreate() ← most startup time spent here
32
+ 3. Activity.onCreate()
33
+ 4. Layout inflation / Compose composition
34
+ 5. First frame drawn ← TTFD (Time To First Display)
35
+ ```
36
+
37
+ ---
38
+
39
+ ## App Startup Library
40
+
41
+ ```toml
42
+ # libs.versions.toml
43
+ [libraries]
44
+ androidx-startup = { module = "androidx.startup:startup-runtime", version = "1.1.1" }
45
+ ```
46
+
47
+ ```kotlin
48
+ // ✅ Replace manual SDK init in Application.onCreate with Initializers
49
+ class TimberInitializer : Initializer<Unit> {
50
+ override fun create(context: Context) {
51
+ if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
52
+ }
53
+ override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
54
+ }
55
+
56
+ class AnalyticsInitializer : Initializer<Unit> {
57
+ override fun create(context: Context) {
58
+ FirebaseApp.initializeApp(context)
59
+ }
60
+ override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
61
+ }
62
+ ```
63
+
64
+ ```xml
65
+ <!-- AndroidManifest.xml — register initializers -->
66
+ <provider
67
+ android:name="androidx.startup.InitializationProvider"
68
+ android:authorities="${applicationId}.androidx-startup"
69
+ android:exported="false">
70
+ <meta-data
71
+ android:name="com.example.TimberInitializer"
72
+ android:value="androidx.startup" />
73
+ <meta-data
74
+ android:name="com.example.AnalyticsInitializer"
75
+ android:value="androidx.startup" />
76
+ </provider>
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Deferring Non-Critical Work
82
+
83
+ ```kotlin
84
+ // ✅ Defer non-critical init to after first frame
85
+ class App : Application() {
86
+ override fun onCreate() {
87
+ super.onCreate()
88
+ // Only critical init here
89
+ initCrashReporting()
90
+
91
+ // Defer everything else
92
+ ProcessLifecycleOwner.get().lifecycle.addObserver(
93
+ object : DefaultLifecycleObserver {
94
+ override fun onStart(owner: LifecycleOwner) {
95
+ owner.lifecycle.removeObserver(this)
96
+ initNonCriticalSdks()
97
+ }
98
+ }
99
+ )
100
+ }
101
+
102
+ private fun initCrashReporting() {
103
+ FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG)
104
+ }
105
+
106
+ private fun initNonCriticalSdks() {
107
+ // Analytics, feature flags, etc.
108
+ }
109
+ }
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Lazy Dependency Initialization with Hilt
115
+
116
+ ```kotlin
117
+ // ✅ Use @Singleton with lazy injection — initialized on first use
118
+ @HiltViewModel
119
+ class HomeViewModel @Inject constructor(
120
+ private val analyticsRepository: AnalyticsRepository // injected lazily
121
+ ) : ViewModel()
122
+
123
+ // ✅ Lazy provider for expensive dependencies
124
+ @Provides
125
+ @Singleton
126
+ fun provideExpensiveSdk(
127
+ @ApplicationContext context: Context
128
+ ): Lazy<ExpensiveSdk> = lazy { ExpensiveSdk.init(context) }
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Splash Screen API
134
+
135
+ ```kotlin
136
+ // ✅ Use SplashScreen API — replaces custom splash Activity
137
+ class MainActivity : ComponentActivity() {
138
+ override fun onCreate(savedInstanceState: Bundle?) {
139
+ val splashScreen = installSplashScreen()
140
+
141
+ // Keep splash visible while loading initial data
142
+ splashScreen.setKeepOnScreenCondition {
143
+ !viewModel.isReady.value
144
+ }
145
+
146
+ super.onCreate(savedInstanceState)
147
+ setContent { AppContent() }
148
+ }
149
+ }
150
+ ```
151
+
152
+ ```xml
153
+ <!-- res/values/themes.xml -->
154
+ <style name="Theme.App.Starting" parent="Theme.SplashScreen">
155
+ <item name="windowSplashScreenBackground">@color/primary</item>
156
+ <item name="windowSplashScreenAnimatedIcon">@drawable/ic_logo</item>
157
+ <item name="postSplashScreenTheme">@style/Theme.App</item>
158
+ </style>
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Measuring Startup
164
+
165
+ ```kotlin
166
+ // ✅ Macrobenchmark for startup measurement
167
+ @RunWith(AndroidJUnit4::class)
168
+ class StartupBenchmark {
169
+ @get:Rule
170
+ val benchmarkRule = MacrobenchmarkRule()
171
+
172
+ @Test
173
+ fun coldStartup() = benchmarkRule.measureRepeated(
174
+ packageName = "com.example.app",
175
+ metrics = listOf(StartupTimingMetric()),
176
+ iterations = 5,
177
+ startupMode = StartupMode.COLD
178
+ ) {
179
+ pressHome()
180
+ startActivityAndWait()
181
+ }
182
+ }
183
+ ```
184
+
185
+ ---
186
+
187
+ ## StrictMode for Development
188
+
189
+ ```kotlin
190
+ // ✅ Enable StrictMode in debug to catch startup violations
191
+ if (BuildConfig.DEBUG) {
192
+ StrictMode.setThreadPolicy(
193
+ StrictMode.ThreadPolicy.Builder()
194
+ .detectDiskReads()
195
+ .detectDiskWrites()
196
+ .detectNetwork()
197
+ .penaltyLog()
198
+ .build()
199
+ )
200
+ }
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Anti-Patterns
206
+
207
+ - Running SharedPreferences reads synchronously in `Application.onCreate()`
208
+ - Initializing all SDKs eagerly — most can be deferred or lazy-initialized
209
+ - Custom splash Activity — use the SplashScreen API instead
210
+ - Not measuring startup before and after optimizations — can't prove improvement
211
+ - Doing Room database creation synchronously on the main thread at startup
212
+
213
+ ---
214
+
215
+ ## Related Skills
216
+ - `baseline-profile` — pre-compiling critical code paths
217
+ - `app-startup` — App Startup library details
218
+ - `macrobenchmark` — measuring startup and runtime performance
219
+ - `hilt` — lazy dependency injection at startup