From cc-mobile-android
Android performance patterns — Baseline Profiles, Macrobenchmark, Compose recomposition counting, StrictMode, Perfetto traces, startup + scroll measurement. Load when investigating jank, cold-start regressions, or shipping a release build.
How this skill is triggered — by the user, by Claude, or both
Slash command
/cc-mobile-android:android-performanceThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Performance claims without a `Macrobenchmark` run or Perfetto trace are opinions. Every optimization in this repo has a before/after number attached.
Performance claims without a Macrobenchmark run or Perfetto trace are opinions. Every optimization in this repo has a before/after number attached.
Mandatory for release. A Baseline Profile primes AOT compilation for the user journeys captured during a benchmark run; cold-start improvements in the 20-40% range are typical.
baselineprofile module with Google's Gradle plugin.BaselineProfileRule test that drives your critical flows (launch, first screen, navigation to top 2 features)../gradlew :app:generateReleaseBaselineProfile — profile lands under app/src/release/generated/baselineProfiles/.Don't hand-edit the generated profile.
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule val rule = MacrobenchmarkRule()
@Test fun startup() = rule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric()),
iterations = 10,
startupMode = StartupMode.COLD,
) {
pressHome()
startActivityAndWait()
}
@Test fun scrollFeed() = rule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(FrameTimingMetric()),
iterations = 5,
startupMode = StartupMode.WARM,
) {
startActivityAndWait()
device.findObject(By.res("feed")).fling(Direction.DOWN)
}
}
compose-runtime debug helpers.timeToInitialDisplayMs and timeToFullDisplayMs (call reportFullyDrawn() from the feature after first meaningful render).@Composable
fun OrderRow(order: Order) {
SideEffect { Log.d("Recomp", "OrderRow ${order.id}") } // debug only
}
Better: Layout Inspector's recomposition count column (Android Studio Hedgehog+).
@Immutable/@Stable-annotated data classes, persistentListOf/ImmutableList from Kotlinx collections-immutable.onClick = { viewModel.onClick(id) } captures id + viewModel; if id is a stable key, lift the lambda with remember(id) { { viewModel.onClick(id) } }.derivedStateOf for reads derived from State that change less often than the source.key(id) around loop bodies in LazyColumn items — not decorative, controls item identity across updates.kotlin {
compilerOptions {
freeCompilerArgs.addAll(
"-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${project.layout.buildDirectory.dir("compose_reports").get().asFile}",
)
}
}
Look at module.json for restartable / skippable percentages. Unstable classes show up in classes.txt.
Enable in Application onCreate on debug only:
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
.detectDiskReads().detectDiskWrites().detectNetwork()
.penaltyLog().build())
StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder()
.detectLeakedClosableObjects().detectLeakedSqlLiteObjects()
.penaltyLog().build())
}
Crashes you catch in dev save you a 1-star review in prod.
Custom trace sections for expensive work:
import androidx.tracing.trace
trace("OrderListLoad") {
val orders = repository.load()
// ...
}
Record a trace via adb shell perfetto ... or Android Studio's profiler, then annotate the captured trace-...-pftrace with your sections to find long tasks.
ContentProviders.@InstallIn(SingletonComponent::class) modules are lazy — safe.Application.onCreate, including Firebase init (use FirebaseApp.initializeApp(this) in a background executor if you need custom config).LazyColumn / LazyRow over Column { items.forEach { } } for >10 items.items(list, key = { it.id }) — without the key, any mutation re-creates every item.contentType = { it::class } groups compatible items for fewer composition slot resets.coil-compose with SubcomposeAsyncImage for placeholders.size(Dimension.Pixels(w), Dimension.Pixels(h)) on the ImageRequest when the display size is known — avoids re-decoding.remember { mutableStateOf(...) } for values that should come from the ViewModel.onCreate that reads disk or network.CompositionLocal abuse for scalar state that changes frequently — every read site recomposes.npx claudepluginhub dimitriremoiville/cc-mobile --plugin cc-mobile-androidSearches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Implements vector databases with Pinecone, Weaviate, Qdrant, Milvus, pgvector for semantic search, RAG, recommendations, and similarity systems. Optimizes embeddings, indexing, and hybrid search.