Save this part
The short version
- Protect the starting state and separate existing failures from benchmark setup.
- Audit the complete request lifecycle, but treat code findings as hypotheses until measured.
- Benchmark release-representative builds on a controlled physical device.
- Save an immutable human-readable baseline outside generated build directories.
- Implement one numbered optimization, rerun the same test, and keep it only when the gain exceeds noise without breaking guardrails.
A code audit tells you what might be slow. A controlled benchmark tells you what the tested user journey actually experienced. Use both: audit, measure, preserve the baseline, fix one task, and measure again.
Prefer the 60-second version?Watch the original mobile app performance reel↗The system
Use one repeatable performance loop
The workflow is intentionally sequential: audit the code, create a trustworthy benchmark, save the baseline, merge evidence into a numbered plan, implement one task, and compare under the same conditions.
Macrobenchmark is Android-specific. For iOS, use XCTest performance tests, MetricKit, Instruments, and signposts. The audit, prioritization, baseline, and comparison discipline still applies.
- A code-based performance audit with stable finding IDs.
- A separate Android Macrobenchmark module and release-representative target.
- Startup and realistic user-journey benchmarks with Perfetto traces.
- A permanent baseline, benchmark-backed task plan, and separate before/after report.
- Regression checks that make performance changes reviewable and reversible.
Step 01
Protect the starting point
Record the commit and existing working-tree changes before an AI agent edits build files. Confirm that the app already launches in a release or profile build so benchmark configuration failures are not confused with pre-existing app failures.
git rev-parse HEAD
git status --shortDo not run git add . blindly. Mobile repositories may contain signing keys, environment files, service configuration, generated artifacts, or personal test data.
Step 02
Audit the complete request lifecycle
Run the audit before changing performance-sensitive code. It should follow representative actions across UI, state, storage, network, backend, and rendering while clearly separating verified code findings from unmeasured hypotheses.
You are a senior Staff Mobile Performance Engineer with expertise in Android, iOS, Flutter, React Native, backend systems, networking, databases, distributed systems, and cloud infrastructure.
Your goal is to identify credible sources of latency in this mobile application and recommend improvements that maximize real-world responsiveness while preserving correctness, scalability, reliability, maintainability, accessibility, privacy, and security.
Before analyzing:
1. Inspect the repository structure and identify the mobile framework, app modules, build variants, backend boundaries, and available tests or telemetry.
2. State what you can verify from code and what requires runtime, device, backend, or production evidence.
3. Do not modify code. Do not optimize yet.
4. Treat secrets, signing files, user data, tokens, URLs, and production logs as sensitive. Never copy them into the report.
Trace important user actions through the complete system:
app startup → authentication → navigation → local storage → network → backend/database → response parsing → state update → rendering.
Analyze these layers:
1. Application architecture
- Cold, warm, and hot startup
- Dependency and SDK initialization
- Lazy initialization and feature loading
- Modularization and state management
- Memory usage, lifecycle work, and background tasks
2. UI performance
- Main/UI thread blocking
- Rendering, layout, overdraw, rebuilds or recompositions
- Frame drops, jank, animations, lists, and virtualization
- Skeleton, optimistic, and progressive rendering
3. Networking
- Request count, duplication, batching, prioritization, and cancellation
- Connection reuse, protocol, compression, payload and serialization
- DNS, TLS, TTFB, retries, timeouts, and offline/slow-network behavior
- Recommend GraphQL, HTTP/3, or API aggregation only when evidence shows a real benefit
4. Backend and database
- API latency and blocking work
- N+1 queries, joins, indexes, query plans, and connection usage
- Safe caching, queueing, and parallelization opportunities
- Separate code evidence from infrastructure assumptions
5. Storage and caching
- Local database and disk I/O
- Preferences and secure storage
- Memory and persistent cache behavior, invalidation, and eviction
6. Media
- Image/video size, decoding, thumbnails, formats, caching, CDN, and progressive loading
7. Concurrency
- Async execution, workers, coroutines, Swift concurrency, isolates, thread contention, locks, and race risks
8. Data loading
- Pagination, prefetching, incremental loading, offline-first and cache-first behavior
9. Mobile constraints
- Battery, memory pressure, thermal state, low-end devices, variable networks, airplane mode, background limits, and lifecycle transitions
10. Infrastructure
- CDN, edge caching, load balancing, geographic latency, API gateways, regional placement, and service-to-service latency
- Do not recommend Redis, a CDN, a new database, microservices, or regional deployment without evidence and an ownership plan
11. Measurement and monitoring
Recommend how to measure before and after:
- Startup TTID and meaningful TTFD
- Screen readiness and user-journey latency
- API/TTFB p50, p95, and p99
- Frame duration, frame overrun, jank, FPS where appropriate
- Memory, CPU, battery, network usage, crashes, ANRs, and cache hit ratio
For every finding provide:
1. Stable finding ID
2. Problem
3. Exact code location or evidence
4. Root-cause hypothesis
5. Confidence: confirmed / likely / possible / unknown
6. Impact: low / medium / high
7. Effort: easy / medium / hard
8. Recommended measurement
9. Recommended solution
10. Why it may improve latency
11. Trade-offs, correctness risks, and rollback trigger
12. Perceived latency, actual latency, or both
Prioritize:
1. High impact + low effort
2. High impact + medium effort
3. High impact + high effort
4. Medium impact
5. Nice-to-have
Avoid premature micro-optimizations. Prefer fewer round trips, less main-thread work, safe caching, incremental loading, bounded parallelism, and backend fixes over frontend workarounds—but only when supported by evidence.
Produce:
1. Executive summary
2. System map and measurement gaps
3. Prioritized findings table
4. Top 10 highest-ROI candidates
5. Quick wins (<1 day)
6. Medium-term improvements (1–2 weeks)
7. Long-term architectural considerations
8. Evidence required before implementation
9. Step-by-step measurement-first roadmap
Do not invent performance percentages. If an improvement cannot be estimated from existing measurements, say “not estimable until benchmarked.” Clearly separate observed facts, inference, and recommendation.Save the completed audit as Markdown at: <AUDIT_PATH>
Preserve every finding ID. Include the executive summary, system map, prioritized findings, limitations, measurement gaps, and measurement-first roadmap.
Do not modify the app. Do not optimize yet. Label every percentage or latency estimate as a hypothesis unless it comes from comparable measurements. Do not include secrets, user content, tokens, signed URLs, or personal data.A static audit cannot prove that a code path is responsible for user-visible latency. Treat “50% faster” and similar claims as hypotheses until comparable measurements exist.
Steps 03–04
Set up Android Macrobenchmark and critical journeys
Paste the prompt below into your coding agent. It first identifies the framework and Android project structure, then creates the separate benchmark module, selects app-specific critical user journeys, adds stable automation, and verifies the build.
For Flutter, also inspect UI/build and raster timing because Flutter renders through its own surface. For React Native, correlate JavaScript, UI, and native threads. Expo needs a native Android project; generating one can overwrite native changes, so inspect first.
- Use a separate com.android.test module, usually named :macrobenchmark.
- Benchmark a non-debuggable build based on release and keep test code outside the production APK.
- Measure cold, warm, and hot startup plus approximately five product-specific journeys.
- Create both end-to-end tests and focused tests whose setup happens outside the measured block.
- Prefer accessibility descriptions, stable text, or resource IDs over coordinates.
You are a Senior Android Performance Engineer specializing in Android Macrobenchmark, Perfetto, UI Automator, and production mobile performance.
Your goal is to set up an evidence-driven Android Macrobenchmark suite for this repository. Follow the current official Android documentation and the project’s existing conventions. Do not use deprecated APIs, suppress benchmark safety errors, or weaken production security.
Step 1 — Understand the project
Before changing files:
- Inspect the repository structure.
- Determine whether it is native Android, Flutter, React Native, or Expo with a generated native project.
- Identify the Android application module, application ID, launch activity, Gradle plugin style, build variants, min/target SDK, navigation architecture, authentication requirements, and important journeys.
- Identify uncommitted changes and files that may contain secrets or signing material.
- Explain the proposed plan, expected files, risks, and verification steps.
Stop and ask for clarification if the target module, package, login fixture, or destructive-test behavior cannot be determined safely.
Step 2 — Configure Macrobenchmark
- Create a separate com.android.test Macrobenchmark module targeting the real app module.
- Use current stable AndroidX Benchmark dependencies verified from official Android documentation or the project version catalog.
- Create a non-debuggable, release-representative benchmark build type, normally initialized from release and locally signed with the debug signing config.
- Configure matching fallbacks where multi-module variants require them.
- Make the benchmark target profileable using the narrowest benchmark-only configuration the project supports.
- Confirm ProfileInstaller requirements from current official documentation.
- Configure the instrumentation runner and ensure JSON results and Perfetto traces are generated.
- Do not package benchmark test code into the production APK.
Step 3 — Select critical user journeys
Inspect the product and select only journeys that matter for this app. Consider cold, warm, and hot startup; authenticated home readiness; primary navigation; scrolling; search; opening detail; and the main create/update flow.
For every selected journey explain:
- Why it matters
- Required account/data state
- End-to-end versus focused scope
- Stable selectors and readiness signals
- Metrics to collect
- Cleanup and repeatability
Use a dedicated synthetic test account and non-sensitive fixture data. Do not run destructive flows without deterministic setup and cleanup.
Step 4 — Implement tests
- Create cold, warm, and hot startup benchmarks using StartupTimingMetric and appropriate StartupMode values.
- Use FrameTimingMetric for interaction journeys. Use other stable metrics only when supported by the device, API level, and a clear decision.
- Use UI Automator with accessibility descriptions, stable text, or resource IDs. Coordinates are a last resort.
- Wait for known UI state instead of relying on long arbitrary sleeps.
- Create reusable helpers without hiding the measured boundaries.
- Use approximately 10 iterations for a normal local baseline unless runtime or official guidance justifies another count.
- Keep setup outside measureBlock for focused tests and inside it for intentional end-to-end tests.
Step 5 — Improve trace visibility
- Identify expensive operations owned by the app: initialization, dependency injection, framework startup, database/cache loading, parsing, image work, navigation, synchronization, or large computation.
- Add the minimum useful custom trace sections using current Android tracing APIs.
- Use stable names and never include user content, email, tokens, filenames, signed URLs, or credentials.
- Explain every trace and keep continuous instrumentation benchmark-only where appropriate.
Step 6 — Verify
- Build the production app and benchmark variants.
- Compile instrumentation tests.
- Run a smoke benchmark on a connected physical device when one is available and authorized.
- Use the current official Gradle task for this module, normally :macrobenchmark:connectedCheck.
- Do not claim the suite is runnable if device, login, permission, API-level, or environment prerequisites remain unmet.
- Fix configuration and compilation failures until resolved; report genuine environment blockers precisely.
Step 7 — Report
Provide:
- Files created and modified
- Selected journeys and metrics
- Device/account/data prerequisites
- Exact build, smoke-test, single-test, and full-suite commands
- Result JSON and Perfetto trace locations discovered from the actual run
- How to open traces in Android Studio or Perfetto
- Remaining measurement gaps
- No TODOs or placeholder package/class names in committed code
Do not optimize application code as part of this setup task. The output of this task is a trustworthy measurement suite, not a faster-looking app.Step 05
Prepare a controlled physical device
Use an emulator to debug automation, not to make final timing or graphics decisions. Record the device, Android build, app commit, display refresh rate, battery/charging/power state, thermal state, network, and test-data size. Keep them stable across comparisons.
Android 14 or later is preferable when authenticated state must survive benchmark compilation resets. On older versions, compilation handling can reinstall the target app; design deterministic setup or use an appropriate documented compilation strategy.
- The device must show as device, not unauthorized.
- Use a dedicated test account and synthetic, repeatable data.
- Grant required permissions and return the app to the documented starting state.
- Do not record credentials, personal content, or signed URLs in selectors, logs, or trace names.
adb devicescd <ANDROID_DIRECTORY>
./gradlew :app:installBenchmarkStep 06
Run a smoke benchmark, then the complete baseline
Watch the physical device during the smoke test. Confirm the automation reaches the intended screen and the measured block covers the intended action. Reject failed, interrupted, thermally compromised, or incorrectly automated runs.
./gradlew :macrobenchmark:connectedCheck -P android.testInstrumentationRunnerArguments.class=<FULL_CLASS>#<METHOD>./gradlew :macrobenchmark:connectedCheckI ran this Macrobenchmark command:
<COMMAND>
Complete sanitized output:
<OUTPUT>
Find the root cause. Check build variants, package and activity names, login state, permissions, selectors, readiness waits, device/API support, test data, battery/thermal state, and benchmark warnings.
Fix the minimum necessary code and rerun only the affected test. Do not suppress safety warnings, weaken assertions, add arbitrary long sleeps, or expose secrets.The exact module and task names must match the generated project. Do not suppress Macrobenchmark errors for debuggable, non-profileable, emulator, or low-battery configurations merely to obtain output.
Step 07
Find the outputs and understand the metrics
Current Android tooling normally copies result JSON and traces under the benchmark module’s connected-test additional-output directory. Search instead of assuming one Gradle path because plugin and module layouts vary.
find . -type f \( -name '*benchmarkData.json' -o -name '*.perfetto-trace' \) | sortI completed the Macrobenchmark suite.
Find the result JSON, test report, and Perfetto traces under the benchmark module outputs. Confirm passed and failed tests and the trace count.
Extract every benchmark’s available min, median, max, P50, P90, P95, and P99 values. Calculate CoV only when the raw iteration values are available and the calculation is statistically appropriate.
Explain the metrics for a beginner. Distinguish TTID from a meaningful TTFD, and do not claim remote content was ready unless the app reports that state correctly. Do not change code.Analyze a trace near the median and the worst valid Perfetto trace for:
<BENCHMARK_NAME>
Correlate slow frames or startup delay with app trace sections, UI/main thread, render/raster thread, CPU scheduling, disk, network, platform calls, locks, garbage collection, and process state.
Separate observed trace facts from inference. Recommend the smallest likely next experiment or fix, but do not implement it. Do not infer backend timing that the trace cannot observe.| Metric | Meaning | How to use it |
|---|---|---|
| TTID | Time until the first visible frame | Compare typical startup using the median. |
| TTFD | Time until the app reports the screen fully drawn | Verify that the app’s readiness signal represents usable content. |
| Frame duration CPU | CPU time spent producing a frame | Inspect P95/P99 to find expensive UI or render work. |
| Frame overrun | Time relative to the hardware frame deadline | Positive values indicate a missed deadline; Android 12+ is required. |
| P50 / median | Typical measured result | Use as the primary comparison under equivalent conditions. |
| P95 / P99 | Slow-tail results | Find intermittent stutter hidden by a good median. |
| CoV | Variation relative to the mean | High variation means small before/after differences are unreliable. |
TTFD can equal TTID when “fully drawn” is reported too early. Do not claim remote data or interactive content was ready unless the application defines that state meaningfully.
Step 08
Save an immutable human-readable baseline
Generated files under build/ may disappear after cleanup. Save a readable baseline outside generated directories and preserve the raw JSON plus a small set of representative traces in access-controlled storage when long-term analysis is needed.
Benchmark results are at:
<RESULT_PATH>
Create a beginner-friendly Markdown baseline at:
<BASELINE_PATH>
Include device model, OS/build, app commit/version, build variant, compilation mode, display refresh rate, power/charging/thermal state, network, test-account data conditions, iteration counts, passed/failed tests, and all available benchmark-level statistics.
Include clear metric definitions, startup iteration values, initial observations, comparison formulas, and raw JSON/Perfetto paths. Keep large per-frame arrays in the source JSON. Mark missing, noisy, framework-specific, or invalid metrics.
Verify that every benchmark appears exactly once, units match the source, the baseline is outside build/, and no secrets or user data are copied. Do not modify app or benchmark code.Perfetto traces can contain runtime metadata. Review and restrict access before sharing them outside the development team.
Step 09
Merge the audit with measured evidence
Reinspect the code because benchmark setup may change file locations or invalidate the original audit. Reclassify every finding and build a plan whose tasks can be implemented and measured independently.
- Measured: the benchmark shows the problem and code contains a related mechanism.
- Correlated: the numbers fit the theory but do not prove that cause.
- Code-confirmed, unmeasured: inefficient code exists outside measured journeys.
- Rejected or outdated: current code or evidence contradicts the original finding.
- External or unverified: backend, database, network, or production evidence is missing.
- Deferred: credible but lower priority than measured work.
Performance audit:
<AUDIT_PATH>
Human-readable benchmark baseline:
<BASELINE_PATH>
Reinspect the current code and verify every audit finding against the benchmark. Do not preserve the original priority order automatically.
Classify each finding as measured, correlated, code-confirmed but unmeasured, rejected/outdated, external/unverified, or deferred. Use exact benchmark values and representative trace evidence. State when the benchmark cannot prove a root cause.
Create a detailed Markdown plan at:
<PLAN_PATH>
Give every task a stable ID such as PERF-00. Include priority, findings addressed, evidence, exact files and symbols, implementation steps, dependencies, edge cases, correctness/security/accessibility risks, tests, targeted benchmark, acceptance threshold larger than likely noise, guardrails, rollback trigger, and definition of done.
Order tasks by measured ROI and prerequisites. Add a focused benchmark before optimizing an unmeasured flow. Preserve the audit and baseline unchanged. Do not invent an improvement percentage. If CoV is high, repeat the campaign before accepting a small difference.Step 10
Implement one numbered optimization
One task at a time makes the effect understandable, reviewable, and reversible. Run the project’s static analysis and tests before touching the device, then run only the targeted benchmark first.
Implement <TASK_ID> from: <PLAN_PATH>
Before editing, verify the current code and benchmark/trace evidence. Explain the approach, edge cases, tests, security and accessibility implications, and expected files.
Implement only this task and its required tests. Preserve the original baseline. Run non-device checks, then provide the exact physical-device benchmark command.
Do not accept speed gained through stale or cross-user data, duplicate or lost mutations, broken authentication, missed notifications, incomplete results, degraded media, retry storms, accessibility regressions, higher crashes/ANRs, memory growth, or battery regressions.Correctness wins. Faster stale data, broken authentication, lost mutations, missing results, accessibility regressions, crashes, memory growth, or battery drain is not an optimization.
Step 11
Rerun under the same conditions and decide
Use the same device, data, network, display, battery, thermal state, build, compilation mode, steps, and iteration count. Keep the original baseline immutable and write a separate report for each task.
cd <ANDROID_DIRECTORY>
./gradlew :macrobenchmark:connectedCheckImplemented task: <TASK_ID>
Original baseline: <BASELINE_PATH>
New results: <NEW_RESULTS_PATH>
Optimization plan: <PLAN_PATH>
Create a separate before/after Markdown report at:
<AFTER_REPORT_PATH>
Compare the same journey under equivalent device, data, network, display, battery, thermal, build, compilation, and iteration conditions. Report median, min/max, available P95/P99, frame overrun or jank, CoV where valid, and task-specific guardrails.
Show absolute and relative changes. Inspect representative traces, list correctness tests, and issue KEEP, REVISE, or ROLLBACK:
- KEEP: improvement exceeds likely noise, guardrails remain healthy, tests pass, and complexity is justified.
- REVISE: direction is promising but noisy or incomplete.
- ROLLBACK: gain is not repeatable or another important metric regresses.
Never overwrite the original baseline. Do not claim causation beyond the evidence.| Comparison | Formula |
|---|---|
| Latency improvement | (old − new) ÷ old × 100 |
| Jank percentage-point improvement | old jank % − new jank % |
| Jank relative improvement | (old jank − new jank) ÷ old jank × 100 |
Do not skip
Track what Macrobenchmark does not prove
Macrobenchmark measures controlled Android startup and interaction journeys. It does not, by itself, represent every user, device, network, backend, reliability, memory, or battery condition.
- Backend and database execution, DNS, TLS, TTFB, bytes, retries, and request fan-out.
- Memory growth, garbage collection, leaks, battery, wakeups, alarms, and long background work.
- Crash and ANR rates or production regressions across the device population.
- iOS behavior and framework-specific work not visible in standard Android frame metrics.
- Real-user latency across geography, network quality, account size, and low-end devices.
Add privacy-safe production telemetry for readiness, API percentiles, errors, request counts, cache hits, memory pressure, crashes, and ANRs. Never record personal content, tokens, signed URLs, filenames, email, or raw AI output.
Common questions
Mobile performance and Android Macrobenchmark FAQ
Should Android Macrobenchmark run on an emulator?
Use an emulator to debug test automation, but use a controlled physical device for performance decisions. Macrobenchmark warns about emulator configurations because virtualized CPU and graphics behavior can distort results.
Does a Macrobenchmark module increase the production APK size?
The separate com.android.test benchmark APK is not packaged into the production app. App-side profileable configuration and trace instrumentation still require review so they are limited appropriately and do not expose sensitive information.
What is the difference between TTID and TTFD?
TTID ends at the first visible frame. TTFD ends when the app reports that the relevant screen is fully drawn. TTFD is useful only when the app reports a meaningful usable state rather than immediately signaling at the first frame.
How many Macrobenchmark iterations should I run?
Ten iterations is a practical local starting point for many journeys, not a universal rule. Increase repetitions when results are noisy, and compare equivalent conditions. Runtime, device temperature, compilation mode, and journey length affect the appropriate count.
Can AI optimize an app from a code audit alone?
It can identify plausible bottlenecks and measurement gaps, but code inspection does not prove user-visible impact. Preserve findings as hypotheses until benchmarks, traces, backend measurements, or production telemetry support them.
Found something worth remembering?
Save the useful idea in Offload with the context that made it matter. Ask for it naturally when you’re ready to use it.
Try Offload free