Skip to content

Repository files navigation

Compose vs Android View System Performance Benchmark

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.

Maintenance status (2026-07-09, corrected 2026-07-23)

  • Benchmark classes currently present in the project are ComposeBenchmarks and ViewBenchmarks under benchmark/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/ β€” see results/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.

πŸ“– Abstract

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.


🧩 Repository Structure

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)

βš™οΈ Methodology

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

Benchmark source sets

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.kt
  • benchmark/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.

Test Workflow

  1. 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
  2. Run Macrobenchmark tests via Gradle β€” recommended: run the benchmark instrumentation on the connected device serial and explicitly set the benchmark target package. Replace RFCT71GG5XA with 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 --info and --stacktrace flags 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
  3. Where to find results and traces

    • Autogenerated HTML test report (connected instrumentation report): benchmark/build/reports/androidTests/connected/benchmark/index.html You 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.html benchmark/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.

  4. 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/
  5. 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. See results/README.md for the artifact policy.


πŸ“Š Metrics Collected and Planned

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

πŸ“ˆ Example Results (verified from emulator run 2026-07-06)

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. |


πŸ”¬ Implementation Details

Current text-only baseline

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 LazyColumn and a single Text containing both title and subtitle.
  • The View app renders the list with RecyclerView and item_row.xml, currently a 56dp row with 12dp padding and separate title/subtitle TextViews (16sp and 14sp).
  • Release/build configuration parity should be verified before publishing measurements.

Planned visual-parity target

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.

Current row parity checklist

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.


🧠 Research Goals

  • 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

πŸ—Ύ How to Reproduce

  1. Clone this repository
    git clone https://github.com/e-Garcia/Compose-vs-Android-View-System-Performance.git
  2. Build the release APKs
    ./gradlew :app-compose:assembleRelease :app-view:assembleRelease :benchmark:assembleBenchmark
  3. Install and run benchmarks on connected physical device(s).
  4. Export generated benchmark outputs from benchmark/build/outputs/connected_android_test_additional_output/, retain the raw artifacts, and add a curated summary or manifest under results/ before comparing results.

πŸ“š Citation

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.


πŸ—³ License

This project is released under the MIT License.
You are free to use, reproduce, and extend this research with attribution.


πŸ“£ Acknowledgments

Thanks to the AndroidX team for Macrobenchmark tooling and to the open-source community for continuous contributions to Android performance research.

About

A side-by-side performance benchmark of Jetpack Compose vs the traditional Android View system for the same UI and dataset. It measures cold startup time, first frame latency, jank during fast scroll, and frame time percentiles using AndroidX Macrobenchmark on real devices.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages