-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Limitations of this library #1
Comments
I don't know how to disable the build script by some cargo feature, but it's possible to check enabled features and conditionally compile Java code in the build script. However, the current build script doesn't slow down compilation significantly. I have removed the |
Then let's use this plan: check enabled features and conditionally compile Java code in the build script. |
I have updated this library for your requirement. Still I wonder why you have came up with this idea. Actually, I wouldn't have built this crate without the need of the proxy feature. Waiting for your complaint about my proxy table implementation. I have changed the comment "Based on" to "Inspired by" in the build script, avoiding possible GPL "pollution" from The usage of JNI forced by Android means a lot of pain for me even in Rust (with the current ecosystem):
I think there should be another crate that locates itself between I have to admit that the dex embedding mechanism learnt from I would have been using Flutter (instead of Rust) if Dart had become more popular. Currently Dart is becoming less popular on TIOBE. It seems like you have started developing PS: can you help out with |
Yes, I am trying to build a use droid_wrap::android::{app::Activity, os::Bundle};
#[java_class("com/sscn/MainActivity", extends = Activity)]
struct MainActivity;
impl MainActivity {
#(java_method(override = true)]
fn on_create(&self, state: &Bundle) {
self.super().on_create(state);
println!("Hello world")
}
} During the build, a dex file containing the MainActivity class will be automatically generated. This work will gradually be carried out in 2025, but there are still many preparatory tasks to be done, such as I am trying to implement a Java syntax parser to automatically generate Rust code based on the droid-wrap library from Java source files. |
Feel free to migrate any parts of my code into your My suggestion: the usage of |
Thank you. |
Hi, I took a look at the My goal is to generate However, I've noticed that the Regardless, thank you for your support in my work. If possible, I would also appreciate it if you continue to follow the development of |
Macro implementations seem to be complicated, but I'm thinking about this: classes needed by the native code should eventually be converted to dex data for the runtime class loader. If D8 is invoked by some build dependency, then these macros need to communicate with that dependency. If the dex data should be generated at runtime, we need a crate similar to |
I don't intend to build dex in memory because this method is far more complicated than macro implementation. Moreover, there's a significant issue: Android systems cannot recognize some classes, such as |
I cannot understand it. I guess you mean that you cannot get the class object of "this method is far more complicated than macro implementation." I think the A comment from |
My intention is that the MainActivity defined in the Manifest is directly called by the system, in this case, our memory dex has not been loaded yet, so the system cannot find this class. I thought it would be simpler to build MainActivity using macros because my droid-wrap library already has a similar macro, which can be slightly modified to achieve this effect; secondly, my true intention is to build this class at compile time and write it into the apk file as a classes.dex file, thereby solving the problem I mentioned at the beginning. Macros can generate java class files at compile time, and then be packaged using cargo-apk2, resulting in an apk file that includes classes.dex, which is perfect. In fact, as you said, we should not use Rust to build MainActivity, I just used it as an example because I am solving the problem of building a Service (for example, MyService inherits from android.accessibilityservice.AccessibilityService, and then this MyService needs to be declared in the Manifest). |
@mzdk100 Would you like to check it: jni-rs/jni-rs#560 |
Would you please check jni-rs/jni-rs#558 (comment)? |
Thank you, I will take note and explore this issue. |
Note: the disadvantage of native implementations of Java abstract classes backed by dynamic proxies is its bad performance. The test result on OpenJDK 8 shows that the cost of using Java proxy is about 180% greater than direct JNI call (2.8x), and the cost of using This is bad for power saving on mobile devices, if such callbacks are invoked frequently. The possible advantage is about safety, but it seems like a minor issue which should be managed by the programmer: jni-rs/jni-rs#526. The Here's my test case, in which [package]
name = "mylib"
version = "0.1.0"
edition = "2021"
[dependencies]
jni-min-helper = "0.3.0"
[lib]
crate-type = ["cdylib"] use jni_min_helper::*;
use jni::JNIEnv;
use jni::objects::JClass;
use jni::sys::{jint, jobject};
#[no_mangle]
pub extern "system" fn Java_JniTest_plusOne<'local>(
_env: JNIEnv<'local>,
_class: JClass<'local>,
num: jint
) -> jint {
num + 1
}
#[no_mangle]
pub extern "system" fn Java_JniTest_getProxy<'local>(
ref mut env: JNIEnv<'local>,
_class: JClass<'local>,
) -> jobject {
let class_loader = JniClassLoader::app_loader().unwrap();
let intr = class_loader.load_class("JniTest$PlusOne").unwrap();
let proxy = JniProxy::build(
env,
Some(&class_loader),
[intr.as_class()],
|env, method, args| {
if method.get_method_name(env)? == "plusOne" {
(args[0].get_int(env)? + 1).new_jobject(env)
} else {
JniProxy::void(env)
}
}
).unwrap();
let proxy_local = env.new_local_ref(&proxy).unwrap();
let _ = proxy.forget();
proxy_local.into_raw()
}
#[no_mangle]
pub extern "system" fn JNI_OnLoad(jvm: jni::JavaVM, _: *mut ()) -> jint {
unsafe { jni_set_vm(&jvm) };
jni::sys::JNI_VERSION_1_2
}
Get method object let method_plus_one = {
let methods = env.call_method(&intr, "getMethods", "()[Ljava/lang/reflect/Method;", &[])
.get_object(env)
.unwrap();
let methods: &JObjectArray = methods.as_ref().into();
env.get_object_array_element(&methods, 0).global_ref(env).unwrap()
}; The main program: JniTest.javaimport java.lang.ClassLoader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
class JniTest {
public interface PlusOne {
int plusOne(int num);
}
private static native int plusOne(int num);
private static native PlusOne getProxy();
static {
System.loadLibrary("mylib");
}
public static void main(String[] args) {
long t1 = System.nanoTime();
int i = 0;
while (i < 1024*1024) {
i = JniTest.plusOne(i);
}
long t2 = System.nanoTime();
i = 0;
PlusOne proxy = (PlusOne) JniTest.getProxy();
while (i < 1024*1024) {
i = proxy.plusOne(i);
}
long t3 = System.nanoTime();
JavaHdl java_hdl = new JavaHdl();
Class[] intrs = { PlusOne.class };
PlusOne java_proxy = (PlusOne) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(), intrs, java_hdl);
i = 0;
while (i < 1024*1024) {
i = java_proxy.plusOne(i);
}
long t4 = System.nanoTime();
System.out.println("JNI Direct Call: " + (t2 - t1));
System.out.println("JNI Dynamic Proxy: " + (t3 - t2));
System.out.println("Java Dynamic Proxy: " + (t4 - t3));
}
}
class JavaHdl implements InvocationHandler {
public JavaHdl() {}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("plusOne")) {
return ((Integer) args[0]).intValue() + 1;
}
return null;
}
} |
My plan is to use a static proxy, which means that necessary '.class' will be automatically generated during Rust build, and then Java will call back the native method directly (your first case, jni direct call). This can achieve higher customization requirements and is not limited to interfaces. Regarding the performance issue you mentioned, I believe there is currently no better solution available. |
I am planning to use some of the convert functions in this library and do not intend to use proxies and BroadcastReceiver. In this case, I do not want to run the build script(build.rs) provided by jni-min-helper.
So I hope to add some features to achieve this, thank you.
The text was updated successfully, but these errors were encountered: