Closed mzdk100 closed 4 months ago
You should create the Jvm
defining detach_thread_on_drop(false)
:
let jvm = JvmBuilder::new().detach_thread_on_drop(false).build().unwrap();
in order not to detach the main thread on Jvm
drop.
I modified src/lib.rs:
use android_activity::AndroidApp;
use j4rs::jni_sys::{JavaVM, jint, jobject};
use j4rs::JvmBuilder;
#[no_mangle]
fn android_main(app: AndroidApp) {
let jvm = JvmBuilder::new().detach_thread_on_drop(false).build().unwrap();
println!("{:?}", app);
}
const JNI_VERSION_1_6: jint = 0x00010006;
#[allow(non_snake_case)]
#[no_mangle]
pub extern fn jni_onload(env: *mut JavaVM, _reserved: jobject) -> jint {
j4rs::set_java_vm(env);
JNI_VERSION_1_6
}
But it still hasn't solved the problem:
...
04-29 23:44:21.356 12123 12278 I RustStdoutStderr: thread '<unnamed>' panicked at src\lib.rs:7:70:
04-29 23:44:21.356 12123 12278 I RustStdoutStderr: called `Result::unwrap()` on an `Err` value: GeneralError("Error { kind: PermissionDenied, message: \"Permission denied (os error 13)\" }")
04-29 23:44:21.356 12123 12278 I RustStdoutStderr: note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
04-29 23:44:21.357 12123 12279 E RustPanic: called `Result::unwrap()` on an `Err` value: GeneralError("Error { kind: PermissionDenied, message: \"Permission denied (os error 13)\" }")
...
I overlooked your question, sorry.
The main problem with this approach is that j4rs
cannot work without the j4rs jar file (currently j4rs-0.18.0-jar-with-dependencies.jar
). This means that the jar should be available in the classpath of the created JavaVM
.
Using the android_activity
, the JavaVM
is created by this crate and I am not familiar with it in order to tell if it is possible to add something in the classpath it creates.
You could have a look at this example which shows how you can use j4rs in Android.
I may have made a mistake, perhaps I should use 'jni-rs' crate directly.
Well, it depends what you want to do. Please correct me if I am wrong, but I guess things can get clunky if you decide to have everything in native code...
I am aware that someone could have a full android app without writing Java code, but from the other side, even the examples of android-activity
include at least some Java/Gradle setup...
I understand what you mean, but I would like to use only cargo to build Android applications. But currently, the cargo-apk crate cannot compile Java or specify a jar path, which is regrettable.
Meanwhile, I have asked the android-activity
devs whether it is possible to add any java classes or jars to the classpath of the JavaVM being created.
If the answer is positive, then I guess we can make j4rs
usable in this context too.
To the best of my knowledge, the AndroidApp struct of android-activity provides a method called vm_as_ptr. I tried using j4rs::set_java_vm (app.vm_as_ptr()); Writing this way also has no effect. I found that the jni_onload function required by j4rs has not been run at all.
Yes, I also saw this... the jni_onload
is called when the JVM is initialized by Android when we have a Java/Kotlin app. Here, as you also understood, we need to
JavaVM
for j4rs:
j4rs::set_java_vm (app.vm_as_ptr());
Jvm
like:
let mut jvm = Jvm::attach_thread();
jvm.detach_thread_on_drop(false);
However, we cannot get any further than that yet, because as mentioned earlier, we need the JavaVM
that is supplied by app.vm_as_ptr()
to have the j4rs-0.18.0-jar-with-dependencies.jar
in the classpath.
Understood, but I haven't found a place to set the jar path either.
But currently, the cargo-apk crate cannot compile Java or specify a jar path, which is regrettable.
Maybe xbuild
can? The two crates are in a bit of a disconnected state, but xbuild
allows you to include arbitrary files a bit more easily at least.
~As far as I'm aware Android has a default class search path inside the APK, so embedding it should be "as easy as" embedding it in the right folder.~
~This is after all what android-activity
requires for the game-activity
backend which provides a portion of Java code, but it's all set up via a gradle
project: https://github.com/rust-mobile/android-activity/tree/main/examples/agdk-mainloop~
EDIT: A more correct reply is in https://github.com/rust-mobile/android-activity/issues/159#issuecomment-2089124701. Android / gradle
"dex" your class and jar files into classes.dex
, if that file is in the APK root (unsure if cargo-apk
/xbuild
allow you to do that easily now), Android should be able to find the class file.
You can look into https://developer.android.com/tools/d8 to "dex" the file by hand.
I've quickly hacked together a feature on xbuild
to put the classes.dex
file in the root via the android: dexes: []
manifest property, and pushed a little repo to start testing it out. The precompiled .class
file is easily replaced with a .jar
file when invoking d8
: https://github.com/MarijnS95/android-support
Compared to cargo-apk, I tested that the xbuild tool is not very useful, at least in my Windows 10 environment, I have not successfully built Android applications. Compared to xbuild, a better option would be cargo-mobile2. At least the code repository for cargo-mobile2 is more active, but it is also complex and requires configuration of the Gradle environment.
I've quickly hacked together a feature on
xbuild
to put theclasses.dex
file in the root via theandroid: dexes: []
manifest property, and pushed a little repo to start testing it out. The precompiled.class
file is easily replaced with a.jar
file when invokingd8
: https://github.com/MarijnS95/android-support
Thanks very much for this @MarijnS95 ! However, the link returns a 404.
That's right, it shows page not found.
That tends to happen, the default visibility for new repositories is "private" and I hadn't noticed when uploading late at night. Should be fixed now.
Compared to cargo-apk, I tested that the xbuild tool is not very useful, at least in my Windows 10 environment, I have not successfully built Android applications. Compared to xbuild, a better option would be cargo-mobile2. At least the code repository for cargo-mobile2 is more active, but it is also complex and requires configuration of the Gradle environment.
Unfortunately the Rust Android ecosystem is plagued heavily by NIH syndrome, there are far too many crates and tools attempting to do the same thing, all failing or lacking in certain areas rather than bundling their strength in one coherent system/crate/tool/approach.
However, cargo-mobile2
is a completely different beast that serves a completely different purpose. cargo-apk
and xbuild
allow you to build applications natively (with the help of a varying number of system tools, and even leveraging gradle
when enabling AAB support in xbuild
). cargo-mobile(2)
generates a gradle
Android Studio project to continue your development from, rather than being able to build from an almost-bare-bones Cargo.toml
project.
I forked your repo @MarijnS95 and made some changes in order o bring it closer to using j4rs
.
I updated the README
with some more details on how to create the classes.dex
.
However, during the apk generation, I get the following error:
[2/3] Build rust
example
[628ms] [3/3] Create apk Error: Failed to collect all required libraries for/git/android-support-j4rs/target/x/debug/android/x64/cargo/x86_64-linux-android/debug/libexample.so
with[ "/.cache/x/Android.ndk/usr/lib/x86_64-linux-android", "/.cache/x/Android.ndk/usr/lib/x86_64-linux-android/21" ]
available libraries and[ "/git/android-support-j4rs/target/x/debug/android/x64/cargo/x86_64-linux-android/debug/deps" ]
shippable libraries
Can you maybe understand what I miss?
Actually, my goal is to use pure rust to build Android applications. Cargo-apk is the best build tool I have ever used, but unfortunately it has been marked as deprecated.
PS E:\repositories\rust\android-support> x run -p example --device adb:rsga9xzlhmbecelv
[1/3] Fetch precompiled artifacts
info: component 'rust-std' for target 'aarch64-linux-android' is up to date
Android.ndk.tar.zst [27s] ██████████████████████████████████████████████████████████ 66.73 MiB/66.73 MiB 📥 downloadedDownloading platforms;android-33
Extracting platforms;android-33
[1/3] Fetch precompiled artifacts [75627ms]
[2/3] Build rust example
Updating crates.io index
Downloaded ndk v0.9.0
Downloaded android-activity v0.6.0
Downloaded ndk-sys v0.6.0+11769913
Downloaded 3 crates (673.4 KB) in 3.11s
Compiling proc-macro2 v1.0.81
Compiling unicode-ident v1.0.12
Compiling equivalent v1.0.1
Compiling hashbrown v0.14.5
Compiling winnow v0.5.40
Compiling toml_datetime v0.6.5
Compiling thiserror v1.0.59
Compiling indexmap v2.2.6
Compiling quote v1.0.36
Compiling syn v2.0.60
Compiling once_cell v1.19.0
Compiling jobserver v0.1.31
Compiling jni-sys v0.3.0
Compiling toml_edit v0.21.1
Compiling cc v1.0.96
Compiling log v0.4.21
Compiling memchr v2.7.2
Compiling proc-macro-crate v3.1.0
Compiling libc v0.2.154
Compiling bytes v1.6.0
Compiling combine v4.6.7
Compiling android-activity v0.6.0
Compiling ndk-sys v0.6.0+11769913
Compiling thiserror-impl v1.0.59
Compiling num_enum_derive v0.7.2
Compiling cfg-if v1.0.0
Compiling bitflags v2.5.0
Compiling cesu8 v1.1.0
Compiling ndk-context v0.1.1
Compiling android-properties v0.2.2
Compiling num_enum v0.7.2
Compiling ndk v0.9.0
Compiling jni v0.21.1
Compiling android-support v0.1.0 (E:\repositories\rust\android-support)
warning: unused Result that must be used
--> src\lib.rs:22:5 |
22 | dbg!(r.v()); | ^^^^^^^^^^^ |
---|
= note: this Result
may be an Err
variant, which should be handled
= note: #[warn(unused_must_use)]
on by default
= note: this warning originates in the macro dbg
(in Nightly builds, run with -Z macro-backtrace for more info)
warning: android-support
(lib) generated 1 warning
Compiling example v0.1.0 (E:\repositories\rust\android-support\example)
error: linker clang
not found
|
= note: program not found
error: could not compile example
(lib) due to 1 previous error
PS E:\repositories\rust\android-support>
@mzdk100 , use x doctor
to see what dependencies you miss.
x run -p example --device adb:rsga9xzlhmbecelv
Output with errors:
28054 28102 I RustStdoutStderr: thread '<unnamed>' panicked at src\lib.rs:21:10:
28054 28102 I RustStdoutStderr: called `Result::unwrap()` on an `Err` value: JavaException
28054 28102 I RustStdoutStderr: note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
28054 28103 E RustPanic: called `Result::unwrap()` on an `Err` value: JavaException
--------- beginning of crash
28054 28103 E AndroidRuntime: FATAL EXCEPTION: Thread-5
28054 28103 E AndroidRuntime: Process: com.example.example, PID: 28054
28054 28103 E AndroidRuntime: java.lang.ClassNotFoundException: Didn't find class "com.example.example.Activity" on path
: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /system/lib64, /system_ext/lib
64]]
28054 28103 E AndroidRuntime: at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:259)
28054 28103 E AndroidRuntime: at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
28054 28103 E AndroidRuntime: at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
28054 28103 I Process : Process is going to kill itself!
28054 28103 I Process : java.lang.Exception
28054 28103 I Process : at android.os.Process.killProcess(Process.java:1330)
28054 28103 I Process : at com.android.internal.os.RuntimeInit$KillApplicationHandler.uncaughtException(RuntimeI
nit.java:195)
28054 28103 I Process : at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1073)
28054 28103 I Process : at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1068)
28054 28103 I Process : at java.lang.Thread.dispatchUncaughtException(Thread.java:2306)
28054 28103 I Process : Sending signal. PID: 28054 SIG: 9
@mzdk100 heya, sorry for leaving this hanging. https://github.com/MarijnS95/android-support had a bug where it relied on the JVM to provide the class, but as seen above that fails with java.lang.ClassNotFoundException: Didn't find class "com.example.example.Activity" on path : DexPathList[[directory "."],...]
. The search path for dex
files (containing Java classes) here is empty, while for an APK it should be something like DexPathList[[zip file "/data/app/~~somerandomstring==/com.example.example-morerandomness==/base.apk"],...]
.
Looking at how Android spawns Activity
s from an APK, it's simply taking the ClassCloader
from android.content.Context.getClassLoader()
and calling java.lang.ClassLoader.loadClass("the/class/we/Need")
on it. I've pushed a commit now that does that (via the NativeActivity
instance, which is a subclass of Context
), and it can finally find and call into the rust/android_support/Activity
class now!
Thank you for your work, but I have already resolved these issues. You can take a look at my work droid-wrap. It can easily and elegantly access the APIs of the Android ecosystem. In droid-wrap, I use InMemoryDexClassLoader to dynamically load classesfull code at:
fn load_rust_call_method_hook_class<'a>() -> &'a GlobalRef {
#[cfg(target_os = "android")]
const BYTECODE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/classes.dex"));
#[cfg(not(target_os = "android"))]
const BYTECODE: &[u8] = &[];
const LOADER_CLASS: &str = "dalvik/system/InMemoryDexClassLoader";
static INSTANCE: OnceLock<GlobalRef> = OnceLock::new();
INSTANCE.get_or_init(|| {
vm_attach!(mut env);
let byte_buffer = unsafe { env.new_direct_byte_buffer(BYTECODE.as_ptr() as *mut u8, BYTECODE.len()) }.unwrap();
let dex_class_loader = env
.new_object(
LOADER_CLASS,
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V",
&[
JValueGen::Object(&JObject::from(byte_buffer)),
JValueGen::Object(&JObject::null()),
],
).unwrap();
let class = env.new_string("rust/CallMethodHook").unwrap();
let class = env
.call_method(
&dex_class_loader,
"loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
&[(&class).into()],
)
.unwrap()
.l()
.unwrap();
let m = NativeMethod {
name: "invoke".into(),
sig: "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;".into(),
fn_ptr: rust_callback as *mut _,
};
env.register_native_methods(Into::<&JClass<'_>>::into(&class), &[m]).unwrap();
env.new_global_ref(&class).unwrap()
})
}
By using this method, it is not necessary to package .class into the apk, so it can be easily built using the cargo-apk tool.
@mzdk100 then this issue should have been closed if you already figured out how to fix this. Note that I cannot read your crate at all :wink:
By using this method, it is not necessary to package .class into the apk, so it can be easily built using the cargo-apk tool.
This isn't very extensible if users also want to embed and use managed Activities written in Java/Kotlin, that's why I'll be adding the mentioned functionality to build tools either way.
Yes, this issue can be closed, thanks.
@mzdk100 as the author of this issue you should also have the rights to close it.
I have created a new project:
File src/lib.rs:
File cargo.toml:
Then use cargo-apk to build:
Output:
May I ask if I missed anything?