Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ by the consuming application.

There is now a build script at `./scripts/build-java.sh` for building the java portion of the library on linux or macOS. This can also be used as a guide for manual building if need be.

Call `btleplug::platform::init` from a thread whose context class loader sees the application's classes. Threads that entered native code from Java already do. A thread attached from native code must first set its context class loader to the application's, such as `Context.getClassLoader()`.

If your app uses Proguard/R8 with `minifyEnabled true`, you must add keep rules for btleplug's
Java classes. All of btleplug's Java code (including vendored jni-utils classes) is only accessed
via JNI from native code, so R8 will treat it as dead code and strip it without these rules.
Expand Down
9 changes: 6 additions & 3 deletions src/droidplug/jni/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ fn init_inner(env: &mut Env) -> crate::Result<()> {
// Seed the JavaVM singleton so JavaVM::singleton() works from any thread.
env.get_java_vm()?;
{
let adapter_class =
env.find_class(jni_str!("com/nonpolynomial/btleplug/android/impl/Adapter"))?;
let loader = jni::objects::LoaderContext::default();
let adapter_class = loader.load_class(
env,
jni_str!("com.nonpolynomial.btleplug.android.impl.Adapter"),
false,
)?;
unsafe {
env.register_native_methods(
&adapter_class,
Expand Down Expand Up @@ -57,7 +61,6 @@ fn init_inner(env: &mut Env) -> crate::Result<()> {
};
use objects::*;

let loader = jni::objects::LoaderContext::default();
<JPeripheral as Reference>::lookup_class(env, &loader)?;
<JScanFilterClass as Reference>::lookup_class(env, &loader)?;
<JNotConnectedException as Reference>::lookup_class(env, &loader)?;
Expand Down
22 changes: 17 additions & 5 deletions tests/android/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub fn find_descriptor(
);
}

use jni::objects::JClass;
use jni::objects::{JClass, JObject, JThread};
use jni::{Env, EnvUnowned, jni_str};
use jni::errors::ThrowRuntimeExAndDefault;
use std::sync::OnceLock;
Expand Down Expand Up @@ -85,19 +85,31 @@ fn run_test(env: &mut Env, test_name: &str, f: impl std::future::Future<Output =
}
}

/// Initialize btleplug's Android/JNI layer. Must be called once before any tests.
/// Runs `platform::init` on a natively attached thread with the app's context class loader.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_nonpolynomial_btleplug_test_NativeTests_initBtleplug(
mut env: EnvUnowned,
_class: JClass,
this: JObject,
) {
android_logger::init_once(
android_logger::Config::default()
.with_max_level(log::LevelFilter::Debug)
.with_tag("btleplug-test"),
);
env.with_env(|env| btleplug::platform::init(env))
.resolve::<ThrowRuntimeExAndDefault>();
env.with_env(|env| -> btleplug::Result<()> {
let loader = env.get_object_class(&this)?.get_class_loader(env)?;
let loader = env.new_global_ref(loader)?;
let vm = env.get_java_vm()?;
std::thread::spawn(move || {
vm.attach_current_thread(|env| -> btleplug::Result<()> {
JThread::current_thread(env)?.set_context_class_loader(env, &loader)?;
btleplug::platform::init(env)
})
})
.join()
.expect("btleplug init thread panicked")
})
.resolve::<ThrowRuntimeExAndDefault>();
}

// ── Test JNI exports ────────────────────────────────────────────────
Expand Down