100 lines
3.3 KiB
Rust
100 lines
3.3 KiB
Rust
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<Global<JClassLoader<'static>>> = 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::<JClassLoader>(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<JClass<'local>> {
|
|
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<JClass<'local>> {
|
|
resolve_class(env, name)
|
|
}
|
|
|
|
pub fn jstr_obj<'local>(env: &mut Env<'local>, text: impl AsRef<str>) -> Result<JObject<'local>> {
|
|
Ok(env.new_string(text)?.into())
|
|
}
|
|
|
|
pub fn new_object<'local>(
|
|
env: &mut Env<'local>,
|
|
class: &str,
|
|
sig: &str,
|
|
args: &[JValue],
|
|
) -> Result<JObject<'local>> {
|
|
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<JValueOwned<'local>> {
|
|
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<JValueOwned<'local>> {
|
|
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<JValueOwned<'local>> {
|
|
let sig = RuntimeFieldSignature::from_str(sig)?;
|
|
let class = resolve_class(env, class)?;
|
|
env.get_static_field(class, JNIString::new(field), sig.field_signature())
|
|
}
|