Skip to content
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

使用场景 #1

Closed
louis2610 opened this issue Oct 13, 2024 · 17 comments
Closed

使用场景 #1

louis2610 opened this issue Oct 13, 2024 · 17 comments

Comments

@louis2610
Copy link

这个库提供给Ruster写android应用吗

@mzdk100
Copy link
Owner

mzdk100 commented Oct 13, 2024

是的

@louis2610
Copy link
Author

是的

那为什么不直接用原生写高效点,通过JNI频繁交互会有一定的开销吧?

@mzdk100
Copy link
Owner

mzdk100 commented Oct 13, 2024

这个是提供了一个与安卓java api等效的一种实现方式。因为一些功能需要从java层来调用安卓的api才能实现。例如照相机、传感器这些。同时也给java程序员提供一个方便,让他们从java转入rust更得心应手。

@wuwbobo2021
Copy link

wuwbobo2021 commented Nov 14, 2024

你好。我正在完善这个 USB 串口库:https://github.com/wuwbobo2021/android-usbser-rs,想为 USB 设备权限请求实现同步 API 和热插拔消息处理(kevinmehall/nusb#86),需要注册 BroadcastReceiver。可以参照 https://github.com/project-robius/robius-authenticationhttps://github.com/Scavanger/libusb_android_helper 的方法,用 Java 代码实现;是否也可使用 droid_wrap_utils 中的 bind_proxy_handler() ,完全通过 JNI 实现这一点?有点纠结。

@mzdk100
Copy link
Owner

mzdk100 commented Nov 14, 2024

理论上是可以的,不过我需要去进一步确认。您可以单独开个issue来讨论。

@wuwbobo2021
Copy link

wuwbobo2021 commented Nov 14, 2024

粗看了一下 droid_wrap_utils 实现代码,发现这正是自己需要的核心支持库(暂时不需要完整的 droid_wrap)。只要引入该库,就不再需要自己准备 build.rs、调用 DexClassLoader 。不过,它应该有一个与 slint-ui/slint#4936 (已解决)类似的问题。
我完全可以将 droid_wrap_utils 的文档注释翻译为英文,以获得 nusb 之类跨平台项目的信任。如果你们的项目主要面向国内用户,那就另当别论。

@wuwbobo2021
Copy link

wuwbobo2021 commented Nov 14, 2024

我发现自己的 Android 旧版本支持 https://github.com/wuwbobo2021/android-rust-java-test 似乎有点毛病:没有使用 Context.getCodeCacheDir()。请教一下,通过 Context.getDir() 得到的目录应该不属于 DexClassLoader 文档所说的 "不提供保护应用程序免受代码注入攻击所需的访问控制" 的“外部存储”位置吧?通过 Context.getClassLoader() 得到 Android 8/9 下创建 DexClassLoader 所需的 parent_dex_loader (变量名有误,应改为 parent_class_loader)是否正确?

@mzdk100
Copy link
Owner

mzdk100 commented Nov 14, 2024

可以翻译成英文版本,你是国内用户还是?这个库暂时没有和任何项目关联,对于sdk庞大的问题,所以我单独提供了droid-wrap-derive和droid-wrap-utils这两个库来解决,您完全可以把他引入到自己的项目中,主仓库只是提供了常见的api。其实本来打算使用类似的bindgen来自动从java源文件来解析api的,但是还没找到一个好用的java语法解析器。如果你需要翻译文档注释,你也可以提PR。

@mzdk100
Copy link
Owner

mzdk100 commented Nov 15, 2024

get_dir是安全的,他的访问权限是APP私有的,即使APP被移动到外部存储介质。对于你第二个问题我没有理解你的意思,你可以使用英语表达一下吗?

@wuwbobo2021
Copy link

英文说明就在这里。简单来说,对于 Android 8.0 / 9.0,构建 InMemoryDexClassLoaderparent_class_loader 参数不能为 null。您可以在老旧手机或者模拟器上测试。

droid-wrap-derive 看起来真的非常好,我就来试一试。

@wuwbobo2021
Copy link

请问该项目的目标之一“内存自动管理”是通过 GlobalRef 实现的吗?

这里说一说自己在 android-usbser-rs 中踩过的两个坑:

  1. 以本地代码为主体的情景下,JObject 导致本地引用帧溢出/内存泄漏:安卓 8 之前会导致崩溃,安卓 8 (以及 PC)上导致内存占用不断膨胀。即使高级封装函数最终返回 GlobalRef,其内部实现中临时创建并使用的 JObject 也会产生这个风险。https://developer.android.google.cn/training/articles/perf-jni: "在 Android 8.0 之前的 Android 版本中, 局部引用的数量受到特定于版本的限制。从 Android 8.0 开始, Android 支持无限的局部引用。"。我的解决方法是使用 AutoLocal。其实 with_local_frame() 应该也行,不过它涉及入栈出栈操作。

  2. JavaException 发生时,jni-rs 不会自动清除该异常。" 请看 JNIEnv::exception_clear() 文档:"Clear an exception in the process of being thrown. If this is never called, the exception will continue being thrown when control is returned to Java." 也就是说, 在以 Rust 为主的代码中,某 JNI 操作调用的 Java 代码抛出 JavaException 异常后,随后的本不应该产生 Java 异常的操作也可能继续接收到异常。

PS:jni-rs 的初衷应该是为 Java 下调用 Rust 代码提供方便,而不是相反。

@wuwbobo2021
Copy link

wuwbobo2021 commented Nov 15, 2024

其实这两个问题都不是 Android 下才有的。请在 PC 下运行以下测试程序:

[package]
name = "jni-env-test"
version = "0.1.0"
edition = "2021"

[dependencies]
jni = { version = "0.21.1", features = ["invocation"] }
use jni::{
    errors::Error,
    objects::{GlobalRef, JClass, JObject, JObjectArray, JValueGen, ReleaseMode},
    sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort, jsize},
    AttachGuard, JNIEnv, JavaVM, NativeMethod,
};

fn main() {
    let args = jni::InitArgsBuilder::new()
        .version(jni::JNIVersion::V8)
        .build()
        .unwrap();
    let vm = JavaVM::new(args).unwrap();
    let mut env = vm.attach_current_thread().unwrap();
    
    loop {
        let java_bool = wrapper_bool_value(false, &mut env);
        drop(java_bool);
    }
}

pub fn wrapper_bool_value(value: bool, env: &mut JNIEnv) -> GlobalRef {
    let obj = env
        .new_object("java/lang/Boolean", "(Z)V", &[(value as jboolean).into()])
        .unwrap();
    env.new_global_ref(&obj).unwrap()
}

查看 jni-env-test 内存占用。不断膨胀!

测试第二个问题:

fn main() {
    let args = jni::InitArgsBuilder::new()
        .version(jni::JNIVersion::V8)
        .build()
        .unwrap();
    let vm = JavaVM::new(args).unwrap();
    let mut env = vm.attach_current_thread().unwrap();
    println!("{:?}", java_parse_int(&mut env, "3"));
    println!("{:?}", java_parse_int(&mut env, "s"));
    println!("{:?}", java_parse_int(&mut env, "5"));
}

fn java_parse_int(env: &mut JNIEnv, s: &str) -> Result<i32, Error> {
    let java_string = env.new_string(s).unwrap();
    env.call_static_method(
        "java/lang/Integer",
        "parseInt",
        "(Ljava/lang/String;)I",
        &[(&java_string).into()]
    )
    .map_err(|e| { env.exception_clear().unwrap(); e })
    .and_then(|o| o.i())
}

输出:

Ok(3)
Err(JavaException)
Ok(5)

注释 .map_err(|e| { env.exception_clear().unwrap(); e }) 后:

Ok(3)
Err(JavaException)
thread 'main' panicked at src/main.rs:21:41:
called `Result::unwrap()` on an `Err` value: JavaException
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Exception in thread "Thread-0" java.lang.NumberFormatException: For input string: "s"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
        at java.lang.Integer.parseInt(Integer.java:580)
        at java.lang.Integer.parseInt(Integer.java:615)

@wuwbobo2021
Copy link

刚刚调试了一下,弄清楚了第一个问题:loop 循环中每次都重新获取 env (重新绑定当前线程到 JVM)就应该不会产生内存泄漏了,env 离开作用域后会自动解绑,本地引用可被删除。(如果使用的是 attach_current_thread_permanently(),则问题依旧)

@mzdk100
Copy link
Owner

mzdk100 commented Nov 15, 2024

英文说明就在这里。简单来说,对于 Android 8.0 / 9.0,构建 InMemoryDexClassLoaderparent_class_loader 参数不能为 null。您可以在老旧手机或者模拟器上测试。

droid-wrap-derive 看起来真的非常好,我就来试一试。

这个先记录一下,会进行修复。

@mzdk100
Copy link
Owner

mzdk100 commented Nov 15, 2024

GlobalRef的原理也是在变量离开作用域就不在引用了,但是java的GC可能不会立即释放对象。包括您提到的attach_current_thread_permanently()会导致内存泄漏,你应该可以试试attach_current_thread(),目前droid-wrap是没有使用attach_current_thread_permanently()的。

@mzdk100 mzdk100 closed this as completed Nov 15, 2024
@mzdk100
Copy link
Owner

mzdk100 commented Nov 15, 2024

暂时先关闭这个thread,如果有什么问题可以重新开个issue。

@wuwbobo2021
Copy link

见鬼,我又搞错了,在 Android 6.0 测试机上得到的结果不一样。实在没有永久绑定线程,但还是崩溃了。

[package]
name = "android-simple-test"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
jni = "0.21"
ndk-context = "0.1"
android-activity = { version = "0.6", features = ["native-activity"] }

[lib]
name = "android_simple_test"
crate-type = ["cdylib"]
path = "main.rs"

[package.metadata.android]
package = "com.example.android_simple_test"
build_targets = [ "aarch64-linux-android" ]

[package.metadata.android.sdk]
min_sdk_version = 16
target_sdk_version = 30
#[no_mangle]
fn android_main(_: android_activity::AndroidApp) {
    let mut i: i32 = 0;
    loop {
    	let _ = with_jni_env_ctx(|env, _| {
    		let _ = env.new_string(format!("test {}", i)).unwrap();
            Ok(())
    	});
    	i = i.wrapping_add(1);
        if i == 1000_000 {
            println!("Success!");
        }
    }
}

use jni::errors::Error;
fn with_jni_env_ctx<R>(
    f: impl FnOnce(&mut jni::JNIEnv, &jni::objects::JObject<'static>) -> Result<R, Error>,
) -> Result<R, Error> {
    let ctx = ndk_context::android_context();
    let jvm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?;
    let context = unsafe { jni::objects::JObject::from_raw(ctx.context().cast()) };
    let mut env = jvm.attach_current_thread()?;
    f(&mut env, &context)
}

调试信息:

11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115] JNI ERROR (app bug): local reference table overflow (max=512)
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115] local reference table dump:
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]   Last 10 entries (of 512):
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]       511: 0x12d511a0 java.lang.String "test 511"
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]       510: 0x12d51180 java.lang.String "test 510"
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]       509: 0x12d51160 java.lang.String "test 509"
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]       508: 0x12d51140 java.lang.String "test 508"
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]       507: 0x12d51120 java.lang.String "test 507"
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]       506: 0x12d51100 java.lang.String "test 506"
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]       505: 0x12d510e0 java.lang.String "test 505"
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]       504: 0x12d510c0 java.lang.String "test 504"
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]       503: 0x12d510a0 java.lang.String "test 503"
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]       502: 0x12d51080 java.lang.String "test 502"
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]   Summary:
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115]       512 of java.lang.String (512 unique instances)
11-16 01:49:21.761 16408 16422 F art     : art/runtime/indirect_reference_table.cc:115] 
11-16 01:49:21.765 16408 16421 I RustStdoutStderr: referenceTable GDEF length=814 1
11-16 01:49:21.765 16408 16421 I RustStdoutStderr: referenceTable GSUB length=11364 1
11-16 01:49:21.765 16408 16421 I RustStdoutStderr: referenceTable GPOS length=47302 1
11-16 01:49:21.778   856  1849 D audio_hw_primary: start_output_stream: exit
11-16 01:49:21.813 16408 16422 F art     : art/runtime/barrier.cc:90] Check failed: count_ == 0 (count_=-1, 0=0) Attempted to destroy barrier with non zero count
11-16 01:49:21.813 16408 16422 F art     : art/runtime/runtime.cc:366] Runtime aborting --- recursively, so no thread-specific detail!
11-16 01:49:21.813 16408 16422 F art     : art/runtime/runtime.cc:366] 
--------- beginning of crash
11-16 01:49:21.813 16408 16422 F libc    : Fatal signal 6 (SIGABRT), code -6 in tid 16422 (android_main)

修改代码:

    		let _ = env.new_string(format!("test {}", i))
                .map(|o| env.auto_local(o))
                .unwrap();

调试信息:

11-16 01:52:30.840 16646 16659 I RustStdoutStderr: Success!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants