Gitea runs on an aarch64 host, and Flutter cannot build an Android APK on a Linux arm64 host at all: the SDK ships no arm64-host gen_snapshot (flutter/flutter#189724, #75864, open as of July 2026). So CI for this repo was failing for two independent reasons, and fixing the runner's container network would only have exposed the second. An x86_64 runner is a requirement here, not a preference. Registers at android-stuff org scope rather than repo scope, so any Android repo in the org can use the same runner, and labels it by capability (flutter-android) rather than by hostname, so the workflow does not encode which machine happens to provide it. Host mode rather than a container: the machine already has a proven Flutter, JDK and Android SDK/NDK, so jobs build with the same toolchain that produced the verified release and skip a per-run NDK download. The trade is isolation, which the header states plainly — workflows run as that user on that machine. Refuses to register unless the toolchain it would advertise is actually present, so a missing SDK fails at setup with a list rather than inside every later job. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
iperf3-gui
A Flutter-based Android GUI wrapper for iperf3, the popular network performance measurement tool.
Features
- Client-mode network throughput testing
- Configurable server, port, duration and parallel streams
- Live iperf3 output, streamed line by line while the test runs
- Stop a running test early
- Copy the transcript to the clipboard
- iperf3 3.19 compiled into the app as a native library — no external binary
Architecture
lib/
├── domain/
│ ├── models/ # TestConfiguration, Iperf3Result
├── data/
│ └── services/ # Iperf3Service (MethodChannel → JNI)
├── ui/
│ └── features/
│ └── test_run/ # TestRunScreen, TestRunViewModel
└── main.dart # App entry point
android/app/src/main/
├── kotlin/…/IperfRunner.kt # MethodChannel handler, runs tests off the platform thread
└── cpp/iperf_jni.c # JNI bridge into the iperf3 API
See docs/README.md for the data flow, the native API surface and the design decisions behind them.
Why JNI instead of executing a binary
Android SELinux in Enforcing mode blocks execve() from the untrusted_app
domain, so shipping an iperf3 ELF in assets and running it via Process.start()
fails with "Permission denied". iperf3 is therefore compiled as a shared library
(libiperf3jni.so) and called in-process over JNI.
assets/iperf3/ still holds the pre-compiled binaries from that earlier
approach. They are not declared in pubspec.yaml, so they are not packaged into
the APK, and nothing in the app reads them.
Building
Prerequisites:
- Flutter 3.47+
- Android SDK with platform android-36
- JDK 17
- NDK r28+ and CMake 3.22.1 (for the native build)
flutter pub get
flutter analyze
flutter test
flutter build apk --debug
adb install -r build/app/outputs/flutter-apk/app-debug.apk
Testing against a local server
iperf3 -s -p 5201 # on a machine on the same network
ip -4 addr show # note its address
Enter that address in the app, then press GO.
Dependencies
- Dart:
provider - Native: iperf3 3.19 + cJSON, built by CMake for
armeabi-v7aandarm64-v8a
License
MIT — bundled iperf3 3.19 is BSD-3-Clause licensed.