From 5f6b3686849ed5617bebba136dc29ac253e71937 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Thu, 8 Oct 2026 19:56:12 +0200 Subject: [PATCH] fix(droidplug): Resolve Adapter through LoaderContext in init --- README.md | 2 ++ src/droidplug/jni/mod.rs | 9 ++++++--- tests/android/rust/src/lib.rs | 22 +++++++++++++++++----- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 15255307..e22779b8 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index 15426e1c..0a813e22 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -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, @@ -57,7 +61,6 @@ fn init_inner(env: &mut Env) -> crate::Result<()> { }; use objects::*; - let loader = jni::objects::LoaderContext::default(); ::lookup_class(env, &loader)?; ::lookup_class(env, &loader)?; ::lookup_class(env, &loader)?; diff --git a/tests/android/rust/src/lib.rs b/tests/android/rust/src/lib.rs index eb99433c..72daca49 100644 --- a/tests/android/rust/src/lib.rs +++ b/tests/android/rust/src/lib.rs @@ -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; @@ -85,19 +85,31 @@ fn run_test(env: &mut Env, test_name: &str, f: impl std::future::Future(); + 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::(); } // ── Test JNI exports ────────────────────────────────────────────────