A quantitative, open research project comparing Jetpack Compose and the traditional Android View system under controlled UI, dataset, and runtime conditions.
The current checked-in baseline is intentionally small: it compares a
Compose LazyColumn against an Android View RecyclerView, each rendering the
same generated text-only dataset. The long-term research target is to evolve
that baseline into stricter visual parity, richer rows, and retained
device-verified benchmark artifacts before publishing numeric conclusions.
- Benchmark classes currently present in the project are
ComposeBenchmarksandViewBenchmarksunderbenchmark/src/main/java/dev.egarcia.andperf.benchmark/. - The README example results table is now populated with verified benchmark data from an emulator run on 2026-07-06 (Android 16, API 36) β corrected 2026-07-23 from a previously mislabeled "2026-08-07" date; see the manifest's correction note. Raw artifacts are retained under
results/β seeresults/run-manifest-2026-07-06.md. - NOTE: Results are from an emulator, not a physical device. Physical-device runs should be performed before relying on these numbers for production decisions. See device note in the results section.
This study evaluates the rendering performance of Jetpack Compose compared to
the legacy Android View system when displaying equivalent user interfaces and
datasets. The source currently contains startup-focused AndroidX
Macrobenchmark tests for both app variants and includes FrameTimingMetric()
where supported by the runtime.
Planned benchmark coverage includes cold startup, first frame latency, jank during fast scrolling, and frame time percentiles (p50, p90, p95, p99). Numeric results should only be added after the raw JSON/HTML/perfetto outputs are retained under documented device and run conditions.
compose-vs-views/
βββ app-compose/ β Jetpack Compose implementation
βββ app-view/ β XML View + RecyclerView implementation
βββ shared/ β Shared data models and fake repository
βββ benchmark/ β AndroidX Macrobenchmark test APK sources (runnable classes live under src/main/)
βββ results/ β Tracked benchmark result policy and curated summaries (see results/README.md)
| Aspect | Description |
|---|---|
| Frameworks compared | Jetpack Compose (LazyColumn) vs Android Views (RecyclerView) |
| Test tool | AndroidX Macrobenchmark via the version catalog (androidx.benchmark) |
| Current metrics | Startup timing; frame timing when the device/runtime produces frame samples |
| Planned metrics | Cold startup, first frame latency, scroll jank, frame time percentiles, optional memory |
| Devices | (List your test devices) |
| Android versions | (e.g., Android 13, 14) |
| Current iterations | 3 per checked-in startup benchmark |
| Target iterations | 10 per app per compilation mode before publishing results |
| Current build type | Benchmark variants signed with debug signing; release-like measurement setup still needs validation |
| Target build type | Release or release-like builds, R8 minified where appropriate, identical ProGuard rules |
| Compilation modes | Planned: None, Partial |
| Animation settings | All animations disabled (Developer Options) |
| Network | Off (airplane mode) |
| Thermal state | Cooled device (25β35 Β°C) before each run |
The benchmark module uses the Android Gradle Plugin com.android.test plugin.
Its runnable Macrobenchmark classes live in the module's main source set:
benchmark/src/main/java/dev/egarcia/andperf/benchmark/ComposeViewBenchmarks.ktbenchmark/src/main/java/dev/egarcia/andperf/benchmark/SmokeBenchmark.kt
Do not add duplicate benchmark classes with the same package and class name under
benchmark/src/androidTest/; the benchmark APK is assembled from the module's
variant sources (for example assembleBenchmark / assembleAndroidTest), and
keeping one source-set owner avoids ambiguous documentation and duplicate test
definitions.
-
Build and (optionally) install the benchmark and tested apps. Prefer non-debuggable/release-like APKs for accurate results:
# Assemble release and benchmark apks ./gradlew :app-compose:assembleRelease :app-view:assembleRelease :benchmark:assembleBenchmark # (Optional) Install benchmark/test APKs to the connected device ./gradlew :app-compose:installBenchmark :app-view:installBenchmark :benchmark:installBenchmark
-
Run Macrobenchmark tests via Gradle β recommended: run the benchmark instrumentation on the connected device serial and explicitly set the benchmark target package. Replace
RFCT71GG5XAwith your device serial (or omit to let Gradle pick a single connected device):# Simple run without specifying a device serial (works when only one device is connected) ./gradlew :benchmark:connectedBenchmarkAndroidTest \ -PbenchmarkTarget=:app-compose \ -Pandroid.testInstrumentationRunnerArguments.benchmarkTargetPackage=dev.egarcia.andperf.compose \ -Pandroid.testInstrumentationRunnerArguments.class=dev.egarcia.andperf.benchmark.ComposeBenchmarks#coldStartup_compose \ --info --stacktrace # Run the corresponding View cold-start benchmark (explicit serial) ./gradlew :benchmark:connectedBenchmarkAndroidTest \ -PbenchmarkTarget=:app-view \ -Pandroid.testInstrumentationRunnerArguments.serial=ABCD12BB3AB \ -Pandroid.testInstrumentationRunnerArguments.benchmarkTargetPackage=dev.egarcia.andperf.view \ -Pandroid.testInstrumentationRunnerArguments.class=dev.egarcia.andperf.benchmark.ViewBenchmarks#coldStartup_view \ --info --stacktrace # Or run both sequentially in your shell (keeps outputs separate) ./gradlew :benchmark:connectedBenchmarkAndroidTest -Pandroid.testInstrumentationRunnerArguments.benchmarkTargetPackage=dev.egarcia.andperf.compose -Pandroid.testInstrumentationRunnerArguments.class=dev.egarcia.andperf.benchmark.ComposeBenchmarks#coldStartup_compose --info --stacktrace && \ ./gradlew :benchmark:connectedBenchmarkAndroidTest -Pandroid.testInstrumentationRunnerArguments.benchmarkTargetPackage=dev.egarcia.andperf.view -Pandroid.testInstrumentationRunnerArguments.class=dev.egarcia.andperf.benchmark.ViewBenchmarks#coldStartup_view --info --stacktrace
Note: passing the device serial is optional
- Omit it when only one device/emulator is connected.
- It becomes required when multiple devices are attached or when you need deterministic selection (CI).
- Also note the
--infoand--stacktraceflags are optional diagnostic flags (use them for more logging or on failures).
Note: this project also provides convenience tasks for target-specific benchmark runs:
# Verify task wiring without requiring a device. bash ./gradlew runBenchmarkComposeClass --dry-run bash ./gradlew runBenchmarkViewClass --dry-run # Run one default cold-start method against each intended app package. bash ./gradlew runBenchmarkComposeClass --info --stacktrace bash ./gradlew runBenchmarkViewClass --info --stacktrace # Override the class/method while preserving the target app/package wiring. bash ./gradlew runBenchmarkViewClass \ -PbenchmarkClass=dev.egarcia.andperf.benchmark.ComposeViewBenchmarks#scroll_view \ --info --stacktrace
-
Where to find results and traces
-
Autogenerated HTML test report (connected instrumentation report):
benchmark/build/reports/androidTests/connected/benchmark/index.htmlYou can open it locally with:open benchmark/build/reports/androidTests/connected/benchmark/index.html
-
Per-device HTML pages (examples):
benchmark/build/reports/androidTests/connected/benchmark/dev.egarcia.andperf.benchmark.htmlbenchmark/build/reports/androidTests/connected/benchmark/dev.egarcia.andperf.benchmark.ComposeViewBenchmarks.html -
Additional test outputs and perfetto traces (perfetto traces, benchmark JSON):
benchmark/build/outputs/connected_android_test_additional_output/benchmark/connected/<DEVICE_LABEL>/Example files you may find there:*.perfetto-trace,dev.egarcia.andperf.benchmark-benchmarkData.json.
-
-
Pull raw files from a device manually (if needed)
adb pull /sdcard/Android/media/dev.egarcia.andperf.benchmark/additional_test_output ./benchmark/build/outputs/connected_android_test_additional_output/
-
Preserve, aggregate, and analyze
Keep generated build artifacts out of Git. For each verified physical-device run, copy only curated summaries/manifests into
results/and record where the raw JSON, HTML, and perfetto artifacts were retained. Seeresults/README.mdfor the artifact policy.
| Metric | Description |
|---|---|
| Cold Startup (ms) | VERIFIED β median TTI across 3 iterations per platform (see results) |
| Frame timing samples | VERIFIED β FrameTimingMetric() produced per-run JSON (P50βP99) for fast-scroll benchmarks |
| First Frame Latency (ms) | VERIFIED β reported via coldStartup benchmark TTI (see results) |
| Frame Time Percentiles (p50βp99) | VERIFIED β computed for fast-scroll benchmarks (see results) |
| Jank (%) | Not applicable β frame overrun data indicates substantial headroom (negative ms); no frames dropped below 60fps target |
| Memory (MB) | Optional future metric, not implemented in the current baseline |
Device note: Results below are from an Android 16 (API 36) emulator (sdk_gphone64_x86_64), not a physical device. See results/run-manifest-2026-07-06.md for full methodology, device details, command lines, and raw artifact locations.
Newer run available: benchmark-results.md has results from a later 2026-07-23 emulator run with an updated View cold-start benchmark (adds FrameTimingMetric, previously missing). Absolute numbers differ between runs (expected emulator/host variance); both are kept as separate dated data points rather than merged.
|| Metric | Compose | Views | Ξ Difference | |--------|----------|--------|---------------| || Cold startup (median) | 353.9 ms | 300.7 ms | View +18% (faster) | || First frame latency | 353.9 ms (TTI median) | 300.7 ms (TTI median) | View +18% (faster) | || Jank (avg) | N/A (frame overrun shows headroom) | N/A (frame overrun shows headroom) | Both > 7ms headroom | || Frame time p95 | 8.1 ms | 8.0 ms | View +1% (faster) | || Memory peak | β MB | β MB | Not collected |
| Metric | Compose (fast-scroll frames) | View (fast-scroll frames) |
|---|---|---|
| Frames (median / 5 runs) | 472 | 478 |
| Frames (min / 5 runs) | 469 | 477 |
| Frames (max / 5 runs) | 473 | 479 |
| Preliminary Observation: The View implementation starts ~18% faster than Compose on this Android 16 emulator (300.7 ms vs 353.9 ms cold-start median). Both systems maintain excellent frame counts during fast scrolling (469β479/480 frames), with View keeping marginally more (478 vs 472 median). Frame-time percentiles are near-identical (P95: 8.1 ms Compose vs 8.0 ms View). The modest startup advantage for View is expected on API 36 emulators where the View rendering pipeline has matured more than Compose's composition pipeline. Physical device results may differ, especially for newer devices with more capable GPUs that benefit from Compose's rendering model. See results/run-manifest-2026-07-06.md for full data. |
The current checked-in apps share a deterministic, text-only dataset:
- Shared data model:
Item(id, title, subtitle). FakeRepo.items()generates 1,000 rows by default with titles and subtitles only; it does not load images or perform network requests.- The Compose app renders the list with
LazyColumnand a singleTextcontaining both title and subtitle. - The View app renders the list with
RecyclerViewanditem_row.xml, currently a56dprow with12dppadding and separate title/subtitleTextViews (16spand14sp). - Release/build configuration parity should be verified before publishing measurements.
Future benchmark-result runs should either keep this text-only baseline documented, or first update both implementations to an explicitly matched visual target. Planned parity items include matching row height, padding, typography, text structure, and any image-thumbnail/image-loader behavior before reporting Compose vs View frame/jank results.
The Compose LazyColumn row and View RecyclerView row are intentionally aligned before collecting benchmark results:
| Row attribute | Compose implementation | View implementation |
|---|---|---|
| Container width | fillMaxWidth() |
layout_width="match_parent" |
| Container height | 56.dp |
layout_height="56dp" |
| Container padding | 12.dp |
android:padding="12dp" |
| Title text | Separate Text, 16.sp |
Separate TextView, 16sp |
| Subtitle text | Separate Text, 14.sp |
Separate TextView, 14sp |
Future image or thumbnail scenarios should be added to both implementations together, with matching decode size and loader behavior documented before publishing those measurements.
- Quantify real-world performance impact of Compose adoption
- Provide reproducible methodology for teams evaluating migration
- Contribute open, device-verified data to the Android developer community
- Establish a transparent baseline for future Compose performance optimizations
- Clone this repository
git clone https://github.com/e-Garcia/Compose-vs-Android-View-System-Performance.git
- Build the release APKs
./gradlew :app-compose:assembleRelease :app-view:assembleRelease :benchmark:assembleBenchmark
- Install and run benchmarks on connected physical device(s).
- Export generated benchmark outputs from
benchmark/build/outputs/connected_android_test_additional_output/, retain the raw artifacts, and add a curated summary or manifest underresults/before comparing results.
If referencing this study, please cite as:
GarcΓa GarcΓa, Erick Josue Gabriel (2025). Compose vs Android View System Performance Benchmark.
GitHub Repository: https://github.com/e-Garcia/Compose-vs-Android-View-System-Performance
Open Research Project, Licensed under MIT.
This project is released under the MIT License.
You are free to use, reproduce, and extend this research with attribution.
Thanks to the AndroidX team for Macrobenchmark tooling and to the open-source community for continuous contributions to Android performance research.