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,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: battery-optimization
|
|
3
|
+
description: >
|
|
4
|
+
Battery usage optimization in Android apps.
|
|
5
|
+
Load this skill when reducing background battery drain, handling Doze
|
|
6
|
+
mode, batching network requests, optimizing location usage,
|
|
7
|
+
or profiling energy usage with Android Studio Energy Profiler.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Battery Optimization
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Battery drain is one of the top reasons users uninstall apps. The main causes are: excessive wake locks, frequent network requests, GPS usage, and background processing that ignores Doze/App Standby. The goal is to do necessary work efficiently and batch or defer work that doesn't need to happen immediately.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- **Batch** network requests — fewer wakeups is better than many small ones
|
|
20
|
+
- **Defer** non-urgent work to when the device is charging or on Wi-Fi (WorkManager constraints)
|
|
21
|
+
- Release **WakeLocks** as quickly as possible — always in a `finally` block
|
|
22
|
+
- Use **passive location** or lower accuracy when precise GPS is not needed
|
|
23
|
+
- Never poll — use push (FCM) or `ContentObserver` instead
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## WorkManager Constraints
|
|
28
|
+
|
|
29
|
+
```kotlin
|
|
30
|
+
// ✅ Defer heavy sync to favorable conditions
|
|
31
|
+
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.HOURS)
|
|
32
|
+
.setConstraints(
|
|
33
|
+
Constraints.Builder()
|
|
34
|
+
.setRequiredNetworkType(NetworkType.UNMETERED) // Wi-Fi only
|
|
35
|
+
.setRequiresCharging(true) // only when charging
|
|
36
|
+
.setRequiresBatteryNotLow(true) // not low battery
|
|
37
|
+
.build()
|
|
38
|
+
)
|
|
39
|
+
.build()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Batching Network Requests
|
|
45
|
+
|
|
46
|
+
```kotlin
|
|
47
|
+
// ❌ Individual requests — each wakes radio
|
|
48
|
+
suspend fun syncAll() {
|
|
49
|
+
syncUsers() // wakes radio
|
|
50
|
+
delay(100)
|
|
51
|
+
syncOrders() // wakes radio again
|
|
52
|
+
delay(100)
|
|
53
|
+
syncProducts() // wakes radio again
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ✅ Single batched request — one radio wakeup
|
|
57
|
+
suspend fun syncAll() {
|
|
58
|
+
val batch = syncRepository.syncAll() // one API call returning all data
|
|
59
|
+
processBatchResult(batch)
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Doze Mode Awareness
|
|
66
|
+
|
|
67
|
+
```kotlin
|
|
68
|
+
// Doze mode restricts:
|
|
69
|
+
// - Network access
|
|
70
|
+
// - Wake locks
|
|
71
|
+
// - AlarmManager (except setAndAllowWhileIdle)
|
|
72
|
+
// - JobScheduler / WorkManager (deferred)
|
|
73
|
+
//
|
|
74
|
+
// Exempt from Doze:
|
|
75
|
+
// - FCM high-priority messages
|
|
76
|
+
// - ForegroundService
|
|
77
|
+
// - WorkManager with setExpedited
|
|
78
|
+
|
|
79
|
+
// ✅ Expedited work for urgent tasks during Doze
|
|
80
|
+
val urgentRequest = OneTimeWorkRequestBuilder<UrgentSyncWorker>()
|
|
81
|
+
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
|
82
|
+
.build()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## WakeLock — Use Sparingly
|
|
88
|
+
|
|
89
|
+
```kotlin
|
|
90
|
+
// ✅ Acquire and release WakeLock safely
|
|
91
|
+
class WakeLockManager @Inject constructor(
|
|
92
|
+
@ApplicationContext context: Context
|
|
93
|
+
) {
|
|
94
|
+
private val powerManager = context.getSystemService(PowerManager::class.java)
|
|
95
|
+
private val wakeLock = powerManager.newWakeLock(
|
|
96
|
+
PowerManager.PARTIAL_WAKE_LOCK,
|
|
97
|
+
"MyApp::SyncWakeLock"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
fun withWakeLock(timeoutMs: Long = 10_000, block: () -> Unit) {
|
|
101
|
+
wakeLock.acquire(timeoutMs) // always set timeout
|
|
102
|
+
try {
|
|
103
|
+
block()
|
|
104
|
+
} finally {
|
|
105
|
+
if (wakeLock.isHeld) wakeLock.release() // ✅ always release
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Location Battery Optimization
|
|
114
|
+
|
|
115
|
+
```kotlin
|
|
116
|
+
// ✅ Use lowest accuracy tier that meets the need
|
|
117
|
+
val locationRequest = LocationRequest.Builder(
|
|
118
|
+
Priority.PRIORITY_BALANCED_POWER_ACCURACY, // cell tower + Wi-Fi — less battery than GPS
|
|
119
|
+
intervalMillis = 30_000 // 30 second interval
|
|
120
|
+
)
|
|
121
|
+
.setMinUpdateDistanceMeters(50f) // only update if moved 50m
|
|
122
|
+
.build()
|
|
123
|
+
|
|
124
|
+
// ✅ Use PRIORITY_PASSIVE for background — piggyback on other apps' location fixes
|
|
125
|
+
val passiveRequest = LocationRequest.Builder(
|
|
126
|
+
Priority.PRIORITY_PASSIVE,
|
|
127
|
+
intervalMillis = 60_000
|
|
128
|
+
).build()
|
|
129
|
+
|
|
130
|
+
// ✅ Stop updates when not needed
|
|
131
|
+
override fun onStop() {
|
|
132
|
+
locationClient.removeLocationUpdates(locationCallback)
|
|
133
|
+
super.onStop()
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Polling vs Push
|
|
140
|
+
|
|
141
|
+
```kotlin
|
|
142
|
+
// ❌ Polling — wakes device repeatedly
|
|
143
|
+
fun startPolling() {
|
|
144
|
+
viewModelScope.launch {
|
|
145
|
+
while (true) {
|
|
146
|
+
fetchNewMessages() // unnecessary if no new messages
|
|
147
|
+
delay(30_000)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ✅ FCM push — device wakes only when there's actual data
|
|
153
|
+
// Server sends FCM notification → app wakes → fetches only when needed
|
|
154
|
+
class MessagingService : FirebaseMessagingService() {
|
|
155
|
+
override fun onMessageReceived(message: RemoteMessage) {
|
|
156
|
+
// Triggered only when server sends a message
|
|
157
|
+
syncMessages()
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Battery Profiling
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
Android Studio → Profiler → Energy:
|
|
168
|
+
- Shows CPU, network, and location wakeups over time
|
|
169
|
+
- Identify: which operations cause the most wakeups
|
|
170
|
+
- Compare before/after optimization
|
|
171
|
+
|
|
172
|
+
adb shell dumpsys batterystats --reset // reset stats
|
|
173
|
+
adb shell dumpsys batterystats // dump current stats
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Anti-Patterns
|
|
179
|
+
|
|
180
|
+
- Holding WakeLock without a timeout — device never sleeps
|
|
181
|
+
- Polling for updates instead of using FCM or WebSocket
|
|
182
|
+
- Using `PRIORITY_HIGH_ACCURACY` location for all use cases — drains GPS
|
|
183
|
+
- Running WorkManager without constraints — executes even on low battery/metered network
|
|
184
|
+
- Ignoring Doze mode — work silently skipped without the app knowing
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Related Skills
|
|
189
|
+
- `workmanager` — scheduling work with battery-aware constraints
|
|
190
|
+
- `background-processing` — choosing the right background mechanism
|
|
191
|
+
- `firebase-messaging` — push notifications to replace polling
|
|
192
|
+
- `foreground-service` — when continuous background work is truly needed
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: benchmark
|
|
3
|
+
description: >
|
|
4
|
+
Microbenchmarking with Jetpack Benchmark in Android.
|
|
5
|
+
Load this skill when measuring method-level performance, comparing
|
|
6
|
+
algorithm implementations, benchmarking serialization or data processing,
|
|
7
|
+
or setting up the benchmark module.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Benchmark
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Jetpack Benchmark (microbenchmark) measures the performance of individual code units — methods, algorithms, serialization — with statistical accuracy. It handles warm-up, clock stability, and GC pauses automatically. Use it to compare implementations and catch regressions.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Benchmark measures **one thing at a time** — isolate the code under test
|
|
20
|
+
- Run on a **physical device** — emulator results are unreliable
|
|
21
|
+
- Use `BenchmarkRule` — it handles warm-up and measurement automatically
|
|
22
|
+
- Benchmark in **release mode** — debug builds are significantly slower
|
|
23
|
+
- Compare relative results — absolute numbers vary by device
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
```toml
|
|
30
|
+
# libs.versions.toml
|
|
31
|
+
[libraries]
|
|
32
|
+
androidx-benchmark-junit4 = { module = "androidx.benchmark:benchmark-junit4", version = "1.2.4" }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```kotlin
|
|
36
|
+
// benchmark/build.gradle.kts (separate module)
|
|
37
|
+
plugins {
|
|
38
|
+
alias(libs.plugins.android.library)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
android {
|
|
42
|
+
defaultConfig {
|
|
43
|
+
testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
|
|
44
|
+
testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR,LOW-BATTERY"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
buildTypes {
|
|
48
|
+
release {
|
|
49
|
+
isMinifyEnabled = true
|
|
50
|
+
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
|
|
51
|
+
}
|
|
52
|
+
create("benchmark") {
|
|
53
|
+
initWith(buildTypes.getByName("release"))
|
|
54
|
+
signingConfig = signingConfigs.getByName("debug")
|
|
55
|
+
matchingFallbacks += listOf("release")
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
dependencies {
|
|
61
|
+
androidTestImplementation(libs.androidx.benchmark.junit4)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Writing a Benchmark
|
|
68
|
+
|
|
69
|
+
```kotlin
|
|
70
|
+
// ✅ Basic benchmark
|
|
71
|
+
@RunWith(AndroidJUnit4::class)
|
|
72
|
+
class SerializationBenchmark {
|
|
73
|
+
|
|
74
|
+
@get:Rule
|
|
75
|
+
val benchmarkRule = BenchmarkRule()
|
|
76
|
+
|
|
77
|
+
private val json = Json { ignoreUnknownKeys = true }
|
|
78
|
+
private val sampleJson = """{"id":"1","name":"John","email":"john@example.com"}"""
|
|
79
|
+
|
|
80
|
+
@Test
|
|
81
|
+
fun deserializeUser() = benchmarkRule.measureRepeated {
|
|
82
|
+
// ✅ Only the code under test inside measureRepeated
|
|
83
|
+
json.decodeFromString<UserDto>(sampleJson)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Test
|
|
87
|
+
fun serializeUser() = benchmarkRule.measureRepeated {
|
|
88
|
+
val user = UserDto("1", "John", "john@example.com")
|
|
89
|
+
json.encodeToString(user)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Comparing Implementations
|
|
97
|
+
|
|
98
|
+
```kotlin
|
|
99
|
+
// ✅ Compare two sorting algorithms
|
|
100
|
+
@RunWith(AndroidJUnit4::class)
|
|
101
|
+
class SortingBenchmark {
|
|
102
|
+
|
|
103
|
+
@get:Rule
|
|
104
|
+
val benchmarkRule = BenchmarkRule()
|
|
105
|
+
|
|
106
|
+
private val data = (1..10_000).shuffled()
|
|
107
|
+
|
|
108
|
+
@Test
|
|
109
|
+
fun sortWithSortedBy() = benchmarkRule.measureRepeated {
|
|
110
|
+
data.sortedBy { it }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@Test
|
|
114
|
+
fun sortWithSort() = benchmarkRule.measureRepeated {
|
|
115
|
+
val copy = data.toMutableList()
|
|
116
|
+
copy.sort()
|
|
117
|
+
copy
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Setup and Teardown
|
|
125
|
+
|
|
126
|
+
```kotlin
|
|
127
|
+
// ✅ runWithTimingDisabled for setup outside measurement
|
|
128
|
+
@Test
|
|
129
|
+
fun processLargeList() = benchmarkRule.measureRepeated {
|
|
130
|
+
val data = runWithTimingDisabled {
|
|
131
|
+
// Setup not counted in benchmark
|
|
132
|
+
generateLargeDataSet(1000)
|
|
133
|
+
}
|
|
134
|
+
// Only this is measured
|
|
135
|
+
processData(data)
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Running Benchmarks
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Run all benchmarks
|
|
145
|
+
./gradlew :benchmark:connectedAndroidTest \
|
|
146
|
+
-Pandroid.testInstrumentationRunnerArguments.class=com.example.SerializationBenchmark
|
|
147
|
+
|
|
148
|
+
# Output in logcat:
|
|
149
|
+
# SerializationBenchmark.deserializeUser: min=45us, median=47us, max=62us
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Allocation Benchmark
|
|
155
|
+
|
|
156
|
+
```kotlin
|
|
157
|
+
// ✅ Measure allocations alongside time
|
|
158
|
+
@Test
|
|
159
|
+
fun checkAllocations() = benchmarkRule.measureRepeated {
|
|
160
|
+
// BenchmarkRule automatically tracks allocations
|
|
161
|
+
// Check "allocationCount" in results
|
|
162
|
+
val result = mapper.toDomain(userDto)
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Anti-Patterns
|
|
169
|
+
|
|
170
|
+
- Running benchmarks on emulator — results are not representative
|
|
171
|
+
- Putting setup code inside `measureRepeated` — inflates measurement
|
|
172
|
+
- Benchmarking in debug build — JIT and debug overhead skew results
|
|
173
|
+
- Benchmarking with the screen on and other apps running — interference from system
|
|
174
|
+
- Comparing results across different devices — only compare on same device
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Related Skills
|
|
179
|
+
- `macrobenchmark` — end-to-end performance measurement
|
|
180
|
+
- `baseline-profile` — using benchmark results to generate profiles
|
|
181
|
+
- `allocation-optimization` — reducing allocations flagged by benchmark
|
|
182
|
+
- `rendering-performance` — frame-level performance measurement
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bitmap-optimization
|
|
3
|
+
description: >
|
|
4
|
+
Bitmap loading, scaling, and memory optimization in Android.
|
|
5
|
+
Load this skill when loading images efficiently, scaling bitmaps
|
|
6
|
+
to required dimensions, using inSampleSize, managing Bitmap recycling,
|
|
7
|
+
or reducing native heap usage from image decoding.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Bitmap Optimization
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Bitmaps are the largest consumers of native heap on Android. A single 12MP photo decoded as ARGB_8888 uses ~48MB. Proper bitmap optimization involves decoding at the required size, choosing the right pixel format, and using a caching library (Coil, Glide) rather than manual management.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Never decode full-resolution bitmaps for thumbnail display — use `inSampleSize`
|
|
20
|
+
- Use **Coil** (or Glide) for image loading — they handle caching, sampling, and lifecycle
|
|
21
|
+
- Prefer `RGB_565` for images that don't need transparency — half the memory of `ARGB_8888`
|
|
22
|
+
- Recycle bitmaps manually only if not using a caching library
|
|
23
|
+
- Load bitmaps on a background thread — never on the main thread
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Coil — Recommended Image Loader
|
|
28
|
+
|
|
29
|
+
```toml
|
|
30
|
+
# libs.versions.toml
|
|
31
|
+
[libraries]
|
|
32
|
+
coil = { module = "io.coil-kt:coil-compose", version = "2.7.0" }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```kotlin
|
|
36
|
+
// ✅ Load image in Compose with Coil
|
|
37
|
+
@Composable
|
|
38
|
+
fun UserAvatar(imageUrl: String?, modifier: Modifier = Modifier) {
|
|
39
|
+
AsyncImage(
|
|
40
|
+
model = ImageRequest.Builder(LocalContext.current)
|
|
41
|
+
.data(imageUrl)
|
|
42
|
+
.crossfade(true)
|
|
43
|
+
.size(64, 64) // ✅ decode at display size — not full res
|
|
44
|
+
.build(),
|
|
45
|
+
contentDescription = null,
|
|
46
|
+
contentScale = ContentScale.Crop,
|
|
47
|
+
placeholder = painterResource(R.drawable.avatar_placeholder),
|
|
48
|
+
error = painterResource(R.drawable.avatar_error),
|
|
49
|
+
modifier = modifier
|
|
50
|
+
.size(64.dp)
|
|
51
|
+
.clip(CircleShape)
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Manual Bitmap Sampling (inSampleSize)
|
|
59
|
+
|
|
60
|
+
```kotlin
|
|
61
|
+
// ✅ Decode at required size without loading full bitmap
|
|
62
|
+
fun decodeSampledBitmap(filePath: String, reqWidth: Int, reqHeight: Int): Bitmap {
|
|
63
|
+
// First pass: get dimensions only
|
|
64
|
+
val options = BitmapFactory.Options().apply {
|
|
65
|
+
inJustDecodeBounds = true
|
|
66
|
+
}
|
|
67
|
+
BitmapFactory.decodeFile(filePath, options)
|
|
68
|
+
|
|
69
|
+
// Calculate sample size
|
|
70
|
+
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
|
|
71
|
+
options.inJustDecodeBounds = false
|
|
72
|
+
|
|
73
|
+
return BitmapFactory.decodeFile(filePath, options)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
|
|
77
|
+
val height = options.outHeight
|
|
78
|
+
val width = options.outWidth
|
|
79
|
+
var inSampleSize = 1
|
|
80
|
+
|
|
81
|
+
if (height > reqHeight || width > reqWidth) {
|
|
82
|
+
val halfHeight = height / 2
|
|
83
|
+
val halfWidth = width / 2
|
|
84
|
+
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
|
|
85
|
+
inSampleSize *= 2
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return inSampleSize
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Pixel Format Selection
|
|
95
|
+
|
|
96
|
+
```kotlin
|
|
97
|
+
// ✅ RGB_565 — half memory, no transparency
|
|
98
|
+
val options = BitmapFactory.Options().apply {
|
|
99
|
+
inPreferredConfig = Bitmap.Config.RGB_565 // 2 bytes/pixel vs 4 bytes for ARGB_8888
|
|
100
|
+
}
|
|
101
|
+
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.background, options)
|
|
102
|
+
|
|
103
|
+
// ✅ ARGB_8888 — full quality with alpha
|
|
104
|
+
val options = BitmapFactory.Options().apply {
|
|
105
|
+
inPreferredConfig = Bitmap.Config.ARGB_8888
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Memory comparison for 1920x1080 image:
|
|
109
|
+
// ARGB_8888 = 1920 * 1080 * 4 = ~8MB
|
|
110
|
+
// RGB_565 = 1920 * 1080 * 2 = ~4MB
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Bitmap Reuse (inBitmap)
|
|
116
|
+
|
|
117
|
+
```kotlin
|
|
118
|
+
// ✅ Reuse existing bitmap memory — avoids new allocation
|
|
119
|
+
val reusableBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
120
|
+
|
|
121
|
+
val options = BitmapFactory.Options().apply {
|
|
122
|
+
inMutable = true
|
|
123
|
+
inBitmap = reusableBitmap // reuse existing allocation
|
|
124
|
+
}
|
|
125
|
+
val newBitmap = BitmapFactory.decodeFile(filePath, options)
|
|
126
|
+
// newBitmap === reusableBitmap — same memory, new content
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Compress Before Upload
|
|
132
|
+
|
|
133
|
+
```kotlin
|
|
134
|
+
// ✅ Compress bitmap before sending to server
|
|
135
|
+
fun Bitmap.toCompressedByteArray(
|
|
136
|
+
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
|
|
137
|
+
quality: Int = 85
|
|
138
|
+
): ByteArray {
|
|
139
|
+
return ByteArrayOutputStream().use { out ->
|
|
140
|
+
compress(format, quality, out)
|
|
141
|
+
out.toByteArray()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ✅ Scale down before compressing
|
|
146
|
+
fun Uri.toScaledBitmap(context: Context, maxDimension: Int = 1024): Bitmap {
|
|
147
|
+
val original = BitmapFactory.decodeStream(context.contentResolver.openInputStream(this))
|
|
148
|
+
val scale = maxDimension.toFloat() / maxOf(original.width, original.height)
|
|
149
|
+
return if (scale < 1f) {
|
|
150
|
+
Bitmap.createScaledBitmap(
|
|
151
|
+
original,
|
|
152
|
+
(original.width * scale).toInt(),
|
|
153
|
+
(original.height * scale).toInt(),
|
|
154
|
+
true
|
|
155
|
+
).also { original.recycle() }
|
|
156
|
+
} else {
|
|
157
|
+
original
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Anti-Patterns
|
|
165
|
+
|
|
166
|
+
- Loading full-resolution images for small thumbnails — massive memory waste
|
|
167
|
+
- Decoding bitmaps on the main thread — ANR risk
|
|
168
|
+
- Not recycling bitmaps when done (if managing manually) — native heap leak
|
|
169
|
+
- Using `ARGB_8888` for all images including opaque backgrounds — unnecessary memory
|
|
170
|
+
- Storing decoded bitmaps in static fields — never GC'd
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Related Skills
|
|
175
|
+
- `heap-management` — overall heap and memory pressure management
|
|
176
|
+
- `memory-leak-prevention` — preventing bitmap reference leaks
|
|
177
|
+
- `loading-strategy` — showing placeholders while images load
|
|
178
|
+
- `allocation-optimization` — reducing GC pressure from bitmap operations
|