Building FlutterTap
July 29, 2026 · View on GitHub
Requirements
- Android Studio (or just the Android SDK/NDK + a JDK, see below)
- JDK 17 or 21. Not newer: Gradle 8.11.1 cannot parse the version string of
JDK 22+ and fails with
IllegalArgumentException: <version>before compiling anything. If your system defaultjavais newer, point Gradle at Android Studio's bundled JBR, e.g.export JAVA_HOME="C:/Program Files/Android/Android Studio/jbr". - NDK
27.2.12479018(or newer; setndkVersioninmodule/build.gradle.ktsif you use a different one). Bothllvm-stripandllvm-readelffrom the NDK toolchain are used by the packaging script. - CMake
3.22.1(bundled with the Android SDK) - Git (the two native dependencies are git submodules)
Two things a fresh clone hits immediately
Point Gradle at the SDK. local.properties is gitignored (it holds a machine-specific
path), so a clone has none and the build stops with "SDK location not found". Either export
ANDROID_HOME=/path/to/Android/Sdk or create local.properties with sdk.dir=/path/to/Android/Sdk.
On Windows, clone into a short path. Capstone has deeply nested directories
(suite/synctools/tablegen/...) and Windows' 260-character limit is easy to exceed: cloning into
something like C:\Users\you\Documents\projects\... fails with "Filename too long" while the
submodules are being fetched. Clone into e.g. C:\dev\fluttertap, or enable long paths with
git config --global core.longpaths true.
First-time setup
git clone --recurse-submodules <repo-url> FlutterTap
cd FlutterTap
# If you cloned without --recurse-submodules:
git submodule update --init --recursive
Create local.properties pointing at your SDK if Android Studio didn't do it
for you:
sdk.dir=/path/to/Android/Sdk
Building the native module
./gradlew :module:externalNativeBuildRelease
Produces libfluttertap.so for arm64-v8a and x86_64 under
module/build/intermediates/cxx/.../obj/<abi>/.
Building the manager app
./gradlew :manager-app:assembleDebug # or assembleRelease
APK output: manager-app/build/outputs/apk/.
Release signing
assembleRelease is signed only if the release keystore is configured; otherwise it produces an
unsigned APK, which Android will refuse to install. That is deliberate — a clone should build
without needing the maintainer's private key.
Credentials are read from gradle.properties in your GRADLE_USER_HOME (~/.gradle/gradle.properties),
never from this repository:
FLUTTERTAP_STORE_FILE=/absolute/path/to/your-release.jks
FLUTTERTAP_STORE_PASSWORD=...
FLUTTERTAP_KEY_ALIAS=fluttertap
FLUTTERTAP_KEY_PASSWORD=...
To create a keystore:
keytool -genkeypair -keystore your-release.jks -alias fluttertap \
-keyalg RSA -keysize 4096 -validity 10000 -dname "CN=Your Name"
Keep the .jks outside the repository (.gitignore blocks *.jks/*.keystore, but the safest
place is simply elsewhere on disk), and back up both the keystore and its password. Losing them means
never being able to ship an update Android accepts as the same app — the only way out is changing the
applicationId.
Verify what you built:
apksigner verify --verbose --print-certs manager-app/build/outputs/apk/release/manager-app-release.apk
Expect Verifies with v3 true and v1/v2 false. That is correct, not a defect: v1 (JAR signing)
only matters below API 24 and v2 below API 28, so AGP skips both for minSdk 29.
Note that a release-signed APK cannot be installed over a debug-signed one — Android rejects the
signature change. Uninstall the old build first. Configuration in /data/adb/fluttertap/ survives the
uninstall, so target apps and proxy settings are preserved.
Packaging a flashable module zip
./scripts/build_module_zip.sh
Builds the native module (both ABIs), strips debug symbols with the NDK's
llvm-strip, and produces dist/FlutterTap-<version>.zip with the layout
Magisk/KernelSU/APatch expect:
FlutterTap-<version>.zip
├── module.prop
├── customize.sh
├── uninstall.sh
├── action.sh
├── licenses/
│ ├── FlutterTap-LICENSE-MIT.txt
│ ├── Dobby-LICENSE-Apache-2.0.txt
│ ├── Capstone-LICENSE-BSD-3.txt
│ └── Capstone-LICENSE-LLVM.txt
└── zygisk/
├── arm64-v8a.so
└── x86_64.so
The script also fails the build if llvm-readelf finds any TLS relocation
in either .so. That is a guard against reintroducing thread_local, which
the Zygisk Next Linker handles differently from the system linker -- see
docs/ARCHITECTURE.md. Use pthread thread-specific data instead.
licenses/ is shipped because Dobby (Apache-2.0) and Capstone (BSD-3) are
statically linked into the .so files, and both licenses require their terms
to accompany binary redistribution. See THIRD_PARTY.md.
Notes on architecture support
Only arm64-v8a and x86_64 are built. The original Frida script's own
address-resolution logic only has branches for Process.arch == 'arm64' and
'x64' -- there's no 32-bit path to port in the first place, and essentially
every Android 10+ device is 64-bit, so this isn't a real-world limitation.
x86_64 covers Android Studio's emulator images.
Third-party dependencies
Both capstone and dobby are git submodules under
module/src/main/cpp/third_party/. dobby is pinned to a specific commit
(not upstream's default branch) because of build breakage in later commits --
see docs/ARCHITECTURE.md for the full explanation and the two local patches
applied on top of it.