7.0 KiB
Building NetDesk for Android
NetDesk's Android app is a Flutter UI (ui/) bound, through
flutter_rust_bridge (FRB v2), to a
compiled Rust library. The Rust FFI layer is the netdesk-frb crate
(frb/), a cdylib that statically links netdesk-core (signaling, E2E
crypto, transport, H.264 decode). On Android the library ships inside the
APK/AAB as lib/<abi>/libnetdesk_frb.so and the Dart side dlopens it (the FRB
loader looks for the stem netdesk_frb).
netdesk-frb (+ netdesk-core) --cargo-ndk--> ui/android/app/src/main/jniLibs/<abi>/libnetdesk_frb.so
Flutter (ui/) --gradle-----> release .apk / split .apk / .aab (signed)
The one command
From the repo root (netdesk-native/), in a shell whose PATH contains the
MinGW tools, cargo, and Flutter:
powershell -ExecutionPolicy Bypass -File packaging\build-android.ps1
That single command:
- verifies / auto-provisions the toolchain (cargo-ndk, the rustup Android targets),
- detects the Android NDK at
C:\dev\android-ndk, - compiles
netdesk-frbfor arm64-v8a, armeabi-v7a, x86_64 with cargo-ndk and dropslibnetdesk_frb.sostraight intoui/android/app/src/main/jniLibs/<abi>/, - builds the release fat APK, the per-ABI split APKs, and the Play App Bundle (.aab).
Useful switches:
| Switch | Effect |
|---|---|
-Target apk|split|aab|rust|all |
Build only that deliverable (default all; rust = just the .so). |
-SkipRust |
Reuse the .so already in jniLibs, build Flutter only. |
-Abis arm64-v8a |
Restrict the ABIs (default all three). |
-GenerateKeystore |
Create the release keystore + key.properties first (see below). |
-MinSdk 21 |
Android API the .so links against (must be <= Flutter minSdk). |
-NdkHome <path> |
Override the NDK location (default C:\dev\android-ndk). |
Toolchain
| Component | Version / location used here |
|---|---|
| Rust | stable (cargo 1.99) on PATH |
| rustup targets | aarch64-linux-android, armv7-linux-androideabi, x86_64-linux-android |
| cargo-ndk | 4.1.2 (cargo install cargo-ndk) |
| Android NDK | r26d (26.3.11579264) at C:\dev\android-ndk (ANDROID_NDK_HOME) |
| Android SDK | %LOCALAPPDATA%\Android\Sdk |
| Flutter | C:\dev\flutter\bin\flutter.bat |
| JDK (keytool + Gradle) | Android Studio JBR (auto-detected) |
| nasm | on PATH (needed by openh264 for the x86_64 ABI) |
One-time setup (the build script also does the first three automatically):
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
cargo install cargo-ndk
# NDK: unzip the Android NDK r26+ to C:\dev\android-ndk (set ANDROID_NDK_HOME)
Recommended PATH (Git Bash form):
export PATH="/c/mingw-netdesk/mingw64/bin:$HOME/.cargo/bin:/c/dev/flutter/bin:$PATH"
What cargo-ndk runs
The Rust step is exactly:
cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 \
-o ui/android/app/src/main/jniLibs --platform 21 \
build --release --package netdesk-frb --lib
cargo-ndk sets the per-ABI clang/linker from the NDK, and the -o flag copies
each produced libnetdesk_frb.so into jniLibs/<abi>/. The .so are build
artifacts (git-ignored) and are regenerated on every run.
Release signing
Signing credentials live in ui/android/key.properties, which is git-ignored
(so are *.keystore / *.jks). A committable template is
ui/android/key.properties.example.
app/build.gradle.kts loads key.properties when present and signs the release
build with it; when it is absent the release build falls back to debug
signing so flutter run --release still works during development.
Create the upload keystore once (the password is read from the environment and is never written into a committed file):
$env:NETDESK_KEYSTORE_PASSWORD = '<your-strong-password>'
powershell -ExecutionPolicy Bypass -File packaging\build-android.ps1 -GenerateKeystore
This writes:
ui/android/app/netdesk-release.keystore- RSA 4096, aliasnetdesk, 10000-day validity,ui/android/key.properties- with the alias, store file, and the password you supplied (local only; keep it out of version control).
Back up the keystore and remember the password. If you lose either you can no longer push updates to an app already published under that key (unless it uses Play App Signing with a separate upload key you can reset).
Outputs
After a full build:
| Deliverable | Path (under ui/) |
|---|---|
| Fat APK (all ABIs) | build/app/outputs/flutter-apk/app-release.apk |
| Split APKs (per ABI) | build/app/outputs/flutter-apk/app-arm64-v8a-release.apk, -armeabi-v7a-, -x86_64- |
| Play App Bundle | build/app/outputs/bundle/release/app-release.aab |
The fat APK is convenient for side-loading / direct download; the split APKs are
smaller per device; the .aab is what Google Play requires.
Publishing to Google Play (AAB)
-
Produce the signed bundle:
powershell -ExecutionPolicy Bypass -File packaging\build-android.ps1 -Target aab(or run the default
alltarget). The bundle isui/build/app/outputs/bundle/release/app-release.aab. -
In the Play Console, create the app, then upload the
.aabunder a release track (internal testing first). -
Enrol in Play App Signing (recommended). Google then holds the app signing key; the keystore above becomes your upload key. If the upload key is ever compromised you can ask Google to reset it - which is why App Signing is preferred over shipping the raw signing key.
-
versionCode/versionNamecome fromui/pubspec.yaml(version: x.y.z+build). Bump the+buildnumber for every Play upload - the Console rejects a duplicateversionCode.
Troubleshooting
- "Android NDK not found" - the script could not find a complete NDK at
C:\dev\android-ndk(it checks forsource.properties+toolchains\llvm\prebuilt). Finish extracting the NDK, setANDROID_NDK_HOME, or pass-NdkHome <path>. Use-SkipRustto build only the Flutter layer against an already-built.so. - x86_64 ABI fails in
openh264- that ABI needsnasmonPATH. arm64-v8a and armeabi-v7a do not. Drop x86_64 with-Abis arm64-v8a,armeabi-v7aif you only ship to real devices. - Gradle wants to download an NDK -
app/build.gradle.ktspoints AGP atANDROID_NDK_HOMEwhen it is set, so the prebuilt (already stripped).soare packaged without a second NDK download. Make sure the env var is set (the build script sets it). UnsatisfiedLinkError: libnetdesk_frb.soat runtime - the.sofor the device's ABI is missing fromjniLibs. Re-run the Rust step (-Target rust) for that ABI, or confirm the FRB loader stem is stillnetdesk_frb.