use jni::Env; use jni::errors::Result; use jni::objects::{JClass, JClassLoader, JObject, JValue, JValueOwned}; use jni::refs::{Global, LoaderContext}; use jni::signature::{RuntimeFieldSignature, RuntimeMethodSignature}; use jni::strings::JNIString; use std::sync::OnceLock; static CLASS_LOADER: OnceLock>> = OnceLock::new(); pub fn remember_class_loader(env: &mut Env, context: &JObject) -> Result<()> { if CLASS_LOADER.get().is_some() { return Ok(()); } let class_obj = call_method(env, context, "getClass", "()Ljava/lang/Class;", &[])?.l()?; let loader_obj = call_method( env, &class_obj, "getClassLoader", "()Ljava/lang/ClassLoader;", &[], )? .l()?; let loader = env.cast_local::(loader_obj)?; let global = env.new_global_ref(&loader)?; // Lost the race with another entry point calling this concurrently -- // both loaders name the same app, so either one is fine and there is // nothing to reconcile. let _ = CLASS_LOADER.set(global); Ok(()) } /// Resolves `name` (slash-separated, e.g. `androidx/core/app/NotificationCompat`) /// through the cached app classloader when one has been remembered, and /// through the ordinary default otherwise -- which is every call made /// before any entry point has run, and is also correct for a main-thread /// caller, so there is no case this makes worse. fn resolve_class<'local>(env: &mut Env<'local>, name: &str) -> Result> { match CLASS_LOADER.get() { Some(loader) => { let binary_name = name.replace('/', "."); LoaderContext::Loader(loader).load_class(env, JNIString::new(&binary_name), true) } None => env.find_class(JNIString::new(name)), } } pub fn find_class<'local>(env: &mut Env<'local>, name: &str) -> Result> { resolve_class(env, name) } pub fn jstr_obj<'local>(env: &mut Env<'local>, text: impl AsRef) -> Result> { Ok(env.new_string(text)?.into()) } pub fn new_object<'local>( env: &mut Env<'local>, class: &str, sig: &str, args: &[JValue], ) -> Result> { let sig = RuntimeMethodSignature::from_str(sig)?; let class = resolve_class(env, class)?; env.new_object(class, sig.method_signature(), args) } pub fn call_method<'local>( env: &mut Env<'local>, obj: &JObject, method: &str, sig: &str, args: &[JValue], ) -> Result> { let sig = RuntimeMethodSignature::from_str(sig)?; env.call_method(obj, JNIString::new(method), sig.method_signature(), args) } pub fn call_static_method<'local>( env: &mut Env<'local>, class: &str, method: &str, sig: &str, args: &[JValue], ) -> Result> { let sig = RuntimeMethodSignature::from_str(sig)?; let class = resolve_class(env, class)?; env.call_static_method(class, JNIString::new(method), sig.method_signature(), args) } pub fn get_static_field<'local>( env: &mut Env<'local>, class: &str, field: &str, sig: &str, ) -> Result> { let sig = RuntimeFieldSignature::from_str(sig)?; let class = resolve_class(env, class)?; env.get_static_field(class, JNIString::new(field), sig.field_signature()) }