Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
blec
A cross platform BLE (Bluetooth Low Energy) client for rust.
On Linux, macOS, Windows and iOS this is a thin layer over btleplug; on Android it uses its own backend.
For a Tauri app use tauri-plugin-blec instead, which wraps this crate
in a plugin with a JavaScript API and re-exports the whole rust API. For a Dioxus app use
dioxus-blec, which adds hooks and carries the android permissions.
Usage
use ;
use ;
use OnDisconnectHandler;
const CHARACTERISTIC_UUID: Uuid = uuid!;
async
blec::init() creates the handler once; blec::get_handler() returns it from anywhere afterwards.
Mock backend
The mock cargo feature (desktop only) replaces the platform backend with an in-process
simulation (blec::mock) that an app or a test can script: devices appear and disappear, links
drop, operations fail, hang or answer slowly, notifications arrive. See the handler tests in
src/handler/tests.
Android internals
Android has no pure-rust path to GATT: BluetoothGattCallback and ScanCallback are abstract
classes, and java.lang.reflect.Proxy only implements interfaces. So the android backend is
Kotlin, in android/lib, an ordinary android library module: the bluetooth permissions in its
manifest plus the code that drives the platform bluetooth stack. Rust talks to it over a small JNI
bridge (src/android/bridge.rs).
The module is meant to be built into the app by the app's own gradle build, like any other
module. tauri-plugin-blec and dioxus-blec do that for you: their android/ directories are
symlinks to crates/blec/android/lib, which tauri (android_path) and dx (#[manganis::ffi])
pick up, so the classes end up in the apk and blec finds them in the app's class loader at
startup. Nothing to set up on the app side. A custom host adds
crates/blec/android/lib to its gradle build the same way (include(":blec") with the
directory as projectDir, implementation(project(":blec"))). The module's consumer-rules.pro
keeps the JNI entry points through the app's R8 pass.
The embedded-dex feature
For a host without a gradle build, the cargo feature embedded-dex embeds the same Kotlin as a
prebuilt classes.dex (src/android/classes.dex, committed) and loads it with an
InMemoryDexClassLoader when com.plugin.blec.Bridge is not in the app. The app's class loader
is always tried first, so an app that does build the module never loads code at runtime even if
some dependency turned the feature on.
Two things to know about the dex path:
- Dynamic code loading can be blocked. Hardened builds (for example the GrapheneOS "dynamic
code loading from memory" toggle) refuse
InMemoryDexClassLoader;blec::init()then fails withError::Androidexplaining it. This is why the gradle module is the default. - The dex is loaded with the boot class loader as parent, not the app's: class loading is
parent-first, and with the app's loader the app's own kotlin stdlib (a Tauri app has one) would
shadow the shrunk copy R8 optimized the Kotlin against, which shows up as
IllegalAccessErroron stdlib internals. ExportedJava_*symbols never resolve for a dex-loaded class either, so the natives are bound withRegisterNatives(also on the gradle path, where it is simply the more robust choice).
Rebuild the dex after changing anything under android/lib/src and commit the result; CI fails
when the committed dex does not match the sources:
The build shrinks the kotlin stdlib into the same dex with R8 and fails if the result spilled
into a classes2.dex, which InMemoryDexClassLoader(ByteBuffer, ClassLoader) cannot load.
Either way
blecneeds aJavaVMand aContext.blec::init()takes both fromndk-context, which Dioxus initializes but Tauri (tao 0.35) does not. A host withoutndk-contextcallsblec::android::init_with(env, context)first, from any thread with aJNIEnv;tauri-plugin-blecdoes that fromwry::prelude::dispatchonce the app is ready.- A main
Loopermust run.BluetoothLeScannerand the gatt callbacks post to it. Any normal android app has one; a headless process has to run one itself. - Runtime permissions need an
Activity, which nobody here owns.blectracks the current one throughApplication.ActivityLifecycleCallbacks, and aContextthat is already anActivity(whattauri-plugin-blechands toinit_with) counts as well. A host whose activity is not covered by either can pass it in withblec::android::set_activity. There is noonRequestPermissionsResultto hook into, so the result of a request is read back off the permissions themselves once the activity is resumed after the dialog closed.
For contributors: crates/tauri-plugin-blec/android and crates/dioxus-blec/android are git
symlinks. On Windows clone with git config core.symlinks true (needs developer mode or admin),
otherwise they check out as text files. Published crates are unaffected: cargo package stores
the linked files as regular files.
License
MIT or Apache-2.0, at your option.