agentsclimarketplace

Performance optimization

Skill GuillemRoca/agent-skills-android/skills/performance-optimization

Production-grade engineering skills for AI coding agents tailored to Android

Install
npx -y skills add GuillemRoca/agent-skills-android --skill performance-optimization

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when measuring or improving Android app performance. Covers Android Vitals (startup, jank, ANR), Macrobenchmark, APK size, recomposition tracing, and profiling with Android Studio tools.

SKILL.md

7.9 KB, as published. Nobody here has run it

Performance Optimization

Overview

"Performance optimization without measurement is guessing." Measure first, identify bottlenecks with data, fix with targeted changes, verify the improvement, and guard against regressions. Never optimize based on assumptions.

When to Use

  • App startup exceeds 500ms (cold) or 200ms (warm)
  • UI jank (dropped frames, janky scrolling)
  • ANR (Application Not Responding) reports
  • APK/AAB size exceeds budget
  • Before a release (performance regression check)
  • Users report slowness or battery drain

Skip when: No performance issue is observed or measured.

Android Vitals Targets

MetricTargetCritical
Cold startup< 500ms> 1s
Warm startup< 200ms> 500ms
Frame rendering (jank)< 5% slow frames> 10% slow frames
ANR rate< 0.47%> 1%
APK size (compressed)< 10MB> 50MB
Memory usage< 150MB typical> 256MB

Core Process

Step 1: Measure

  1. Baseline Profiles (startup and scrolling):
// benchmark/src/main/java/BaselineProfileGenerator.kt
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generateBaselineProfile() {
        rule.collect(packageName = "com.example.app") {
            // Cold start
            pressHome()
            startActivityAndWait()

            // Critical user journeys
            device.findObject(By.text("Tasks")).click()
            device.waitForIdle()

            // Scroll the list
            val list = device.findObject(By.res("task_list"))
            list.setGestureMargin(device.displayWidth / 5)
            list.fling(Direction.DOWN)
            device.waitForIdle()
        }
    }
}
  1. Macrobenchmark (startup timing):
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
    @get:Rule
    val rule = MacrobenchmarkRule()

    @Test
    fun coldStartup() {
        rule.measureRepeated(
            packageName = "com.example.app",
            metrics = listOf(StartupTimingMetric()),
            startupMode = StartupMode.COLD,
            iterations = 5,
        ) {
            pressHome()
            startActivityAndWait()
        }
    }
}
  1. Android Studio Profiler:
    • CPU Profiler: Record method traces, identify hot methods
    • Memory Profiler: Track allocations, find leaks, heap dumps
    • Network Profiler: Inspect API calls, timing, payload sizes
    • Energy Profiler: CPU, network, and GPS wake lock usage

Step 2: Identify Bottlenecks

  1. Common performance anti-patterns:
Anti-PatternImpactFix
N+1 queries in RoomSlow list loadingUse @Transaction with @Relation or single JOIN query
Unbounded data fetchOOM, slow renderingPaging3
Large images unscaledMemory pressure, OOMCoil/Glide with size constraints
Work on main threadANR, jankwithContext(Dispatchers.IO)
Unnecessary recompositionJank in ComposeStable types, key(), derivedStateOf
Large APKSlow downloadsR8, resource shrinking, dynamic delivery
Missing Baseline ProfilesSlow cold startGenerate and include profiles
Unoptimized importsSlow build, large APKOnly import what's needed
Synchronous initializationSlow startupApp Startup library, lazy init

Step 3: Fix

  1. Startup optimization:
// Use App Startup library for lazy initialization
class AnalyticsInitializer : Initializer<Analytics> {
    override fun create(context: Context): Analytics {
        return Analytics.init(context)
    }
    override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}

// Defer non-critical work
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Critical path only — show UI immediately
        setContent { AppTheme { AppNavigation() } }

        // Defer non-critical initialization
        lifecycleScope.launch {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                initializeAnalytics()
                prefetchUserData()
            }
        }
    }
}
  1. Compose recomposition optimization:
// Use key() for list items
LazyColumn {
    items(tasks, key = { it.id }) { task ->
        TaskItem(task = task)
    }
}

// Use derivedStateOf for computed values
val showScrollToTop by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 5 }
}

// Use ImmutableList for stable parameters
@Composable
fun TaskList(
    tasks: ImmutableList<Task>, // from kotlinx.collections.immutable
    onToggle: (String) -> Unit,
)

// Avoid lambda allocations in loops
items(tasks, key = { it.id }) { task ->
    // BAD: new lambda per recomposition
    TaskItem(onToggle = { viewModel.toggle(task.id) })
    // GOOD: method reference
    TaskItem(onToggle = viewModel::toggleTask)
}
  1. APK size reduction:
// build.gradle.kts
android {
    buildTypes {
        release {
            isMinifyEnabled = true     // R8 code shrinking
            isShrinkResources = true   // Remove unused resources
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

// Use WebP for images, vector drawables where possible
// Use dynamic feature modules for large optional features
// Analyze APK: Build → Analyze APK in Android Studio
  1. Image loading optimization:
// Coil with size constraints — request only the pixels you render.
// Size.ORIGINAL decodes the full bitmap and defeats the point.
AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data(task.imageUrl)
        .size(200, 200)  // match the display size; never Size.ORIGINAL for thumbnails
        .crossfade(true)
        .build(),
    contentDescription = task.title,
    modifier = Modifier.size(64.dp),
)

Step 4: Verify

  1. Confirm improvement with measurements:
    • Re-run Macrobenchmark — compare before/after
    • Check frame metrics in Android Studio Profiler
    • Verify APK size: ./gradlew assembleRelease → Analyze APK
    • Run on lower-end devices (not just your development device)

Step 5: Guard

  1. Prevent regressions:
    • Baseline Profiles generated in CI
    • Macrobenchmark tests run on pre-release builds
    • APK size budget checked in CI
    • Performance monitoring in production (Firebase Performance)

Common Rationalizations

ShortcutWhy It Fails
"It's fast on my Pixel 8"Your flagship device is not your users' device. Test on low-end hardware.
"We'll optimize later"Performance debt compounds. Fixing later costs 10x more.
"The profiler shows it's fine"Profiling in debug mode hides R8 optimizations and ART compilation. Profile release builds.
"Only 5% of users hit this"5% of 1M users is 50,000 people. Every percentage matters.

Red Flags

  • No Baseline Profiles
  • No Macrobenchmark tests
  • APK size growing without tracking
  • Thread.sleep or busy-wait patterns
  • Unbounded list loading (no Paging3)
  • Heavy computation on main thread
  • Images loaded at full resolution
  • Profiling only done on debug builds

Verification

  • Startup time measured (cold and warm)
  • Frame rendering metrics checked (slow frames < 5%)
  • APK size within budget
  • Baseline Profiles generated and included
  • No N+1 query patterns
  • Images loaded with proper sizing
  • Heavy work off main thread
  • Macrobenchmark tests guard critical paths
  • Performance tested on low-end devices

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.