Android Native Integration
Integrate fiber-ffi into an Android app through JNI and the native C ABI
Fiber: Android Native Integration Guide
About fiber-ffi
fiber-ffi currently lives in a personal repository. This page uses it as an exploratory reference for Android native integration, not as an official Fiber Android SDK or support commitment.
This document uses fiber-ffi as a reference example to explain how the existing Rust Fiber node can be integrated into an Android app through JNI and the native C ABI. The repository provides a runnable demo:
The demo covers starting and stopping a node, reading NodeInfo, receiving native events, connecting peers, creating and sending invoices, and listing, creating, and closing channels.
Quick Start
First build the Android native library from the fiber-ffi repository root:
ANDROID_NDK_HOME=/path/to/android-ndk make build-androidThe current Makefile builds the following by default:
target = aarch64-linux-android
Android ABI = arm64-v8a
min Android API = 23
features = sqlite
page size = 16 KB max-page-sizeAfter the build completes, only two files need to be synced:
target/aarch64-linux-android/release/libfiber_ffi.so -> demos/android/app/src/main/jniLibs/arm64-v8a/libfiber_ffi.so
include/fiber_ffi.h -> demos/android/app/src/main/cpp/include/fiber_ffi.hThen build the demo:
cd demos/android
./gradlew clean assembleDebugYou can also open demos/android directly in Android Studio and run app. When building the demo from the command line, run cd demos/android && ./gradlew clean assembleDebug; on Windows, use gradlew.bat clean assembleDebug.
Integration Steps
Prepare Files
An Android project needs the following files. The header file is available at include/fiber_ffi.h:
app/src/main/jniLibs/arm64-v8a/libfiber_ffi.so
app/src/main/cpp/include/fiber_ffi.h
app/src/main/assets/fiber_config.ymlfiber_config.yml must include both fiber and ckb services. The demo uses a testnet configuration, which can be referenced directly at demos/android/app/src/main/assets/fiber_config.yml.
chain affects the invoice currency. The demo uses a testnet configuration, so when creating an invoice, the currency should be the testnet value Fibt (the FFI enum is FIBER_INVOICE_CURRENCY_FIBT). If the configuration is switched to mainnet or devnet, the currency must also be changed to Fibb or Fibd; otherwise newInvoice will be rejected by Fiber RPC. For the demo, fixing one network and its corresponding currency is enough. A production app should choose dynamically based on the current network.
JNI Bridge
fiber_bridge.cpp is the Android adapter layer: Java/Kotlin calls FiberRuntime, and the bridge calls the C ABI in fiber_ffi.h. The app side usually only uses FiberRuntime; it should not directly handle FiberHandle *, Fiber*Options, or char **out_json.
A bridge is needed because fiber-ffi exports a C ABI, while Android business code is better expressed with ordinary Java types. The bridge converts String, boolean, amounts, and other parameters into the format required by C, holds a single FiberHandle *, and converts returned JSON or error text into Java strings. When adding new capabilities, prefer adding methods to FiberRuntime and reusing the conversion and error handling patterns in fiber_bridge.cpp.
Pay special attention to memory boundaries:
char *values returned by FFI are allocated bylibfiber_ffi.so; the bridge must copy them into JavaStringvalues and then callfiber_string_free.- Java string pointers obtained with
GetStringUTFCharsmust be released withReleaseStringUTFCharsbefore the current JNI call ends. event_jsonin event callbacks is only valid during the callback. Copy it immediately before entering Java.fiber_stopconsumes the handle. After Stop, the old handle must not be passed back to FFI.
CMake must let fiber_bridge find the header file and link fiber_ffi. The demo's app/src/main/cpp/CMakeLists.txt already handles this.
Load Native Libraries
The Java/Kotlin layer must load fiber_ffi first, then fiber_bridge:
System.loadLibrary("fiber_ffi");
System.loadLibrary("fiber_bridge");Prepare Data Directories
Before startup, copy the configuration from assets to a normal file path and prepare the data directory:
config_path = filesDir/fiber_config.yml
database_prefix = filesDir/fiber-dataThe CKB private key is stored at:
filesDir/fiber-data/ckb/keyThe demo writes this file through SetCKBKey: the Java layer normalizes the user-entered 32-byte hex private key and writes it to ckb/key, and the JNI layer sets FIBER_SECRET_KEY_PASSWORD before startup.
Distinguish the "plaintext file at import time" from the "persistent file after Fiber starts." Fiber's default logic reads FIBER_SECRET_KEY_PASSWORD when loading the CKB private key. If it finds that ckb/key is still parseable plaintext hex, it encrypts it with this password and overwrites the file in place. Later it decrypts the file with the same password. Therefore, after the first SetCKBKey and before the first successful Start, ckb/key may briefly remain plaintext. After a successful start, the file should have been migrated to encrypted contents.
If writing ckb/key manually, the contents should be a 32-byte hex string without the 0x prefix. The demo automatically removes a 0x prefix from user input. The plaintext migration logic in the Fiber source rejects key files with a 0x prefix.
A production app should use its own wallet or key management process, and must not use the fixed FIBER_SECRET_KEY_PASSWORD from the demo. This password and the encrypted ckb/key are both required to recover the ability to sign funds. If either one is lost, the CKB private key cannot be recovered from the encrypted file.
Start and Stop
Start the node by calling:
FiberRuntime.start(context)After startup succeeds, you can continue with:
FiberRuntime.nodeInfo()Stop the node by calling:
FiberRuntime.stop()Do not start multiple nodes in the same process. For repeated Start calls, you can return already running directly. After Stop, clear the native handle. The old handle must not be used again.
Calling Features
The current demo wraps the following Java methods:
| Method | Purpose |
|---|---|
start(context) | Start the node |
stop() | Stop the node |
nodeInfo() | Read node information |
listPeers() | List peers |
connectPeer(...) | Connect a peer |
listChannels() | List channels |
createChannel(...) | Create a channel |
shutdownChannel(...) | Close a channel |
newInvoice(...) | Create an invoice |
sendPayment(...) | Pay an invoice |
Pay attention to amount units:
- The demo's
createChannel(...)accepts a decimal CKB string for display, such as1.5. The Java/JNI bridge converts it to shannons before filling the FFIFiberU128. - The demo's
newInvoice(...)accepts an integer shannons string and does not accept decimal CKB. - Amount fields actually passed into FFI always use integer smallest units: CKB uses shannons, where 1 CKB = 100000000 shannons; UDT uses the integer smallest unit of that UDT.
- If UDT channels or UDT invoices are added later, pass UDT parameters such as
funding_udt_type_script_json/udt_type_script_json, and passamountas the UDT's raw integer amount. Do not reuse the CKBamount * 100000000conversion.
More APIs can be extended following the JNI Bridge template above.
Handling Events
Pass the native event callback during startup. The Java layer listens like this:
FiberRuntime.addNativeEventListener(eventJson -> {
// Parse eventJson, then refresh UI or local state.
});Event contents are JSON strings. Business code should parse the JSON first, then dispatch based on the kind field. Common kind values include:
NetworkStarted
NetworkStopped
PeerConnected
PeerDisConnected
ChannelCreated
ChannelReady
ChannelClosed
PreimageCreatedThe list above is only a set of common event examples, not a complete enum. When consuming events, use the kind field in the callback JSON as the source of truth. If an event kind is not currently relevant or is added in the future, it can be logged and ignored.
Do not update UI directly from the native callback thread. Switch to the main thread after receiving events.
Lifecycle
Recommended handling:
- Do not execute potentially blocking native calls directly on the UI thread.
- Start, Stop, and calls on the same handle must be serialized.
- Execute Stop when the user logs out, switches accounts, or explicitly closes the node.
- When switching network, account, or data directory, call Stop first, then restart the app process.
- Use different
database_prefixvalues for different users, accounts, and networks. - The Android demo includes a minimal
FiberNodeServiceforeground service to demonstrate letting a service help keep the process after the node starts. Background execution strategy, notification presentation, power consumption, and restrictions across system versions must be designed and verified by production app developers.
The current document mentions the foreground service only as a demo reminder. It does not treat it as a complete persistent background node solution.
Build
- The current demo packages only
arm64-v8a, andapp/build.gradle.ktsalso configures only this ABI. To support more ABIs, build the corresponding targets separately and put each.sointo the corresponding directory. - Android builds currently enable only the
sqlitefeature. If you want to enablewatchtower, change the feature tosqlite,watchtowerand re-verify size, linking, and runtime behavior. - Some Android 15+ devices use a 16 KB page size. The current Rust
.soand JNI bridge both add-Wl,-z,max-page-size=16384; do not remove it.
Demo Operations
SetCKBKey: enter the CKB private key.Start: start the node.NodeInfo: read the node address and pubkey.Peers: view or connect peers.Invoice: create an invoice or pay an invoice.Channels: list, create, or close channels.Stop: stop the node.
Q&A
System.loadLibrary("fiber_ffi") fails
Check whether libfiber_ffi.so is under app/src/main/jniLibs/<ABI>/, whether the device ABI is included in abiFilters, whether the Rust target matches the Android ABI, and whether the .so has been relinked for 16 KB page size.
Loading fiber_bridge fails
Load fiber_ffi first, then fiber_bridge. Also check whether IMPORTED_LOCATION in CMake points to the .so for ${ANDROID_ABI}, and whether fiber_bridge correctly links fiber_ffi and the Android log library.
Startup fails and says the configuration file is unreadable
Do not pass the assets path directly to FFI. First copy fiber_config.yml from assets to filesDir, then pass the real file path.
Startup fails and says the CKB private key is not set
The demo requires running SetCKBKey first. The key is a 32-byte hex string, with or without a 0x prefix. The demo normalizes it and writes it to filesDir/fiber-data/ckb/key. During the first successful Start, Fiber uses FIBER_SECRET_KEY_PASSWORD to migrate this plaintext key into an encrypted file. Later starts must continue using the same password to decrypt it.