Skip to content

Native Android

laqieer edited this page Jun 15, 2026 · 4 revisions

Native Android

FEBuilderGBA has an experimental native Android port built on the cross-platform Avalonia GUI. It is a real APK that runs the shared Avalonia UI on-device — not the Windows desktop build running under an x86 emulation layer.

Authoritative reference: the full, evidence-backed design and status lives in the in-repo docs/ANDROID.md. This page is a summary.

What it is

  • A FEBuilderGBA.Android/ application head that builds and packages a signed APK consuming the same Avalonia views as the desktop GUI.
  • The shared FEBuilderGBA.Avalonia project multi-targets net9.0;net9.0-android.
  • Built on top of FEBuilderGBA.Core (platform-independent) + FEBuilderGBA.Avalonia (shared UI). WinForms is not Android-capable and is excluded.

Multi-target opt-in (EnableAndroidTarget)

The android target framework is opt-in and OFF by default, so the desktop solution and all desktop CI keep seeing net9.0 only and are completely untouched:

dotnet workload install android
dotnet build FEBuilderGBA.Android/FEBuilderGBA.Android.csproj -c Release -p:EnableAndroidTarget=true

The -p:EnableAndroidTarget=true global property is required so NuGet restore cross-targets the shared project too. The Android head is deliberately excluded from FEBuilderGBA.sln — the required desktop build check runs the whole solution on a Windows runner with no android workload, so adding the head to the .sln would break unrelated PRs.

Key engineering pieces

Area How it works
config/ bundling The config/ tree ships as an AndroidAsset and is version-stamped, extracted on first run to the app's FilesDir, so the app finds its game-data definitions without a desktop-style working directory.
ROM file I/O Stream-based via Avalonia IStorageProvider / Android SAF (Storage Access Framework), since Android content:// picks have no plain file path. Loads rewind the stream; seekable saves truncate.
Navigation The desktop multi-window model is replaced by a single-activity shell: an INavigationService + single-view host routes the editor-launch call sites through a page/view-stack on Android.
SkiaSharp pin SkiaSharp.NativeAssets.Android is pinned to 2.88.x to match Avalonia's bundled native Skia — a 3.x managed package against an 88.x native crashes inside the Avalonia process.

Render parity is emulator-validated in CI

The rendering pipeline is validated byte-identical on a real Android emulator. The CI workflow android-emulator-parity.yml runs the SkiaSharp render byte-parity + version-guard tests on an API-34 x86_64 emulator on every push/PR to master, using a direct reflection test runner (.NET 9 Android embeds test assemblies as .so, so xUnit's on-disk discovery doesn't apply). A second advisory workflow (android.yml) builds the APK and uploads it as an artifact. Both are non-blocking by design (separate job contexts from the required build check) so a slow/flaky Android job can never block a desktop PR merge.

Current limitations

The honest status: the APK builds and the render pipeline is emulator-validated for parity. The remaining frontier is full on-device UX:

  • Touch interaction and per-editor attached-Window dialogs are not yet fully reworked for a phone form factor.
  • arm64-v8a / armeabi-v7a are covered by the same SkiaSharp package (same upstream Skia build, ABI-specific binaries) but are not directly emulated on GitHub-hosted x86_64 runners.

See docs/ANDROID.md for the layer-by-layer capability assessment, the phased roadmap, and the build prerequisites.

See also

Clone this wiki locally