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

JNI_OnLoad-like callback to load APK classes with env.FindClass()? #169

Open
astonbitecode opened this issue Oct 29, 2024 · 10 comments
Open

Comments

@astonbitecode
Copy link

Hi,

I am having classloading issues when using android-activity and try to load classes of code that is being shipped within the same apk.
The system classes that are available by default (eg. java.lang.String) can be found and loaded with no issues. However, the custom classes cannot be found...

I wonder if the reason is what is described here:

You can get into trouble if you create a thread yourself (perhaps by calling pthread_create and then attaching it with AttachCurrentThread). Now there are no stack frames from your application. If you call FindClass from this thread, the JavaVM will start in the "system" class loader instead of the one associated with your application, so attempts to find app-specific classes will fail.

I see in android-activity README that indeed the rust applications do not run on application's main thread:

run an android_main() function in a separate thread from the Java main thread and marshal events (such as lifecycle events and input events) between Java and your native thread.

Can it be the reason?

I also tried to use the JNI_OnLoad in order to cache the JavaVM and use it later, as it is proposed, but it does not gets called either.

So, my question is, does android-activity support using jni to call classes shipped within the same apk? Do you have any pointers on how to achieve that?

I have this gitjub repo, where you can find my scenario if this can help.

Thanks!

@MarijnS95
Copy link
Member

I've answered the same question on your repository a while ago: astonbitecode/j4rs#102 (comment)

Does that help?

@MarijnS95
Copy link
Member

In fact it looks like your example/reproduction repository was a fork from my android-support repository where the linked solution was built and demonstrated. Seems like you just need to rebase on it to receive the solution within your tree.

@astonbitecode
Copy link
Author

As far as I understand, this answer implies implementing the Activity and using android.content.Context.getClassLoader() to load classes.
My question is whether we can have rust only implementation and use jni FindClass to load classes.

@MarijnS95
Copy link
Member

@astonbitecode I don't understand what "this answer implies implementing the Activity" is supposed to mean. You asked this question in the android-activity repository which already "implements" both GameActivity and NativeActivity, the latter of which is completely native ("Rust only") and supports interacting with Java through JNI, as shown in the proposed solution. Your linked repository also uses android-activity and has access to AndroidApp where the ClassLoader is pulled out of the Activity/Context:

https://github.com/MarijnS95/android-support/blob/cc03005d2f5b8c4036685d17ee986de5406e5e4f/src/lib.rs#L12-L25

While FindClass doesn't seem to be usable per the FAQ that you linked, it suggests that this uses ClassLoader under the hood which matches the solution above.


Separate from this, since you mentioned threading, I do wonder if FindClass has the app in DexPathList before android-activity spawns a user thread (especially since, according to the FAQ, ANativeActivity_onCreate() should have Java stackframes earlier on - except that those Java files don't reside in the app for NativeActivity - only for GameActivity). I.e. here:

/// This is the native entrypoint for our cdylib library that `ANativeActivity` will look for via `dlsym`
#[no_mangle]
extern "C" fn ANativeActivity_onCreate(
activity: *mut ndk_sys::ANativeActivity,
saved_state: *const libc::c_void,
saved_state_size: libc::size_t,
) {
abort_on_panic(|| {
let _join_log_forwarder = forward_stdio_to_logcat();
log::trace!(
"Creating: {:p}, saved_state = {:p}, save_state_size = {}",
activity,
saved_state,
saved_state_size
);
// Conceptually we associate a glue reference with the JVM main thread, and another
// reference with the Rust main thread
let jvm_glue = NativeActivityGlue::new(activity, saved_state, saved_state_size);
let rust_glue = jvm_glue.clone();
// Let us Send the NativeActivity pointer to the Rust main() thread without a wrapper type
let activity_ptr: libc::intptr_t = activity as _;
// Note: we drop the thread handle which will detach the thread
std::thread::spawn(move || {

@astonbitecode
Copy link
Author

I don't understand what "this answer implies implementing the Activity" is supposed to mean

Ok apparently I did not express myself correctly. I wanted to stress that my question is whether I can achieve classloading as described, without writing any java code myself.

I was thinking that using the android-activity I would be able to call jni using the classpath that includes the classes residing inside the apk itself. If this was possible, FindClass would locate and load the classes of the apk.

Maybe I am wrong, but I believe this would be possible if the android-activity entrypoint fn android_main(app: AndroidApp) is executed by the main Activity thread, instead on its own new native thread.

If this cannot be done, then I would propose to have a function similar to JNI_OnLoad that is called on the main thread before the android_main and give the ability to the rust application to load and cache any needed classes, as proposed by the first bullet here:

There are a few ways to work around this:
Do your FindClass lookups once, in JNI_OnLoad, and cache the class references for later use. Any FindClass calls made as part of executing JNI_OnLoad will use the class loader associated with the function that called System.loadLibrary (this is a special rule, provided to make library initialization more convenient). If your app code is loading the library, FindClass will use the correct class loader.

@wuwbobo2021
Copy link

wuwbobo2021 commented Nov 16, 2024

Excuse me, I'd like to provide a few clues here (maybe off-topic):

@MarijnS95
Copy link
Member

Ok apparently I did not express myself correctly. I wanted to stress that my question is whether I can achieve classloading as described, without writing any java code myself.

@astonbitecode you can, via JNI, without writing any Java code: this is demonstrated by the example I already linked.

The Java code in that repository is the class that we are loading from the APK in the first place.

Maybe I am wrong, but I believe this would be possible if the android-activity entrypoint fn android_main(app: AndroidApp) is executed by the main Activity thread, instead on its own new native thread.

That would not be possible since this function has to return: a thread needs to be spawned at some point if the user wishes to attach a looper and use it as a regular fn main() Rust function to drive their application from.

There are however a lot of refactors ongoing to detach this such that multi-activity support finally becomes possible. At this point we might consider an interim "must-return-from" OnLoad kind of function that is called from within a JNI stackframe, such that env.FindClass actually finds classes from the APK without having to defer to a ClassLoader from the Context.

@astonbitecode
Copy link
Author

Thanks.

I opened this issue with the hope that j4rs would be able to work somehow using the android-activity as is, or maybe without major tweaks.
In j4rs, FindClass is used to lookup and load classes and jni_sys is used instead of jni crate.

I hope this week I will find some time to implement support of using the Classloader of the Activity if FindClass fails. Some preliminary tests still give ClassNotFound exceptions, but I hope I will find a proper way to handle the situation.

@astonbitecode
Copy link
Author

Ok so, I was able to use j4rs with android-activity. Here is an example project for anyone interested.

Thanks for the help.

@MarijnS95, from my side, the issue can be closed. However I leave it up to you to close it, in case you want to use it to implement something similar to OnLoad as you mentioned in your comment.

@MarijnS95
Copy link
Member

I hope this week I will find some time to implement support of using the Classloader of the Activity if FindClass fails. Some preliminary tests still give ClassNotFound exceptions, but I hope I will find a proper way to handle the situation.

I think this is exactly what was already discussed and provided in a comment on your repo, but sounds like you resolved this in the end 👍


I think we could leave this issue open to track the potential of having a "OnLoad-like" callback where FindClass is able to resolve classes inside the APK, because of having the right Activity stackframes.

@MarijnS95 MarijnS95 changed the title Classloading possible issues JNI_OnLoad-like callback to load APK classes with env.FindClass()? Nov 23, 2024
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