Open OptimisticPeach opened 5 years ago
I definitely think that exposing the JNI would be a good thing. In terms of api, android_glue
should probably provide the JNIEnv *
. It could do this in a few ways:
jni::JNIEnv
– this is bad because it requires the JNI crate as a public dependency*mut c_void
– this is bad because it requires users to write unsafe code to use the JNIjni
feature that enables a function returning jni::JNIEnv
– this is bad because it makes it slightly less convenient, and still has jni
as a public dependency when the feature is enabled.I have no strong opinions here, but my preference is either the first or third ways.
A way to insert code called on the main thread at startup (In java, or preferably rust)
Why particularly do you need this? (As opposed to just writing it at the start of main()
)
Why particularly do you need this? (As opposed to just writing it at the start of main()) (Specifically) I use this to remove the navigation bars and decorations on the screen which can only be done from the main thread which NativeActivity is created on. I currently have a modified version of
cargo-apk
locally which I've added the following function inandroid_native_app_glue
:
// android_native_app_glue.h
/**
* This is the function that is run on startup in the main thread
* For example, to hide the navigation bars.
*/
extern void android_startup(ANativeActivity* activity, int focused);
// android_native_app_glue.c
static void onWindowFocusChanged(ANativeActivity* activity, int focused) {
LOGV("WindowFocusChanged: %p -- %d\n", activity, focused);
android_app_write_cmd((struct android_app*)activity->instance,
focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS);
android_startup(activity, focused);
}
which I then define in my rust code as follows:
use cargo_apk_injected_glue::ffi;
#[no_mangle]
pub extern fn android_startup(activity: *const ffi::ANativeActivity, _focused: i32) {
let env = unsafe { &mut *(*activity).env };
let functions = unsafe { &*env.functions };
let activity_class = (functions.FindClass)(env, "android/app/NativeActivity\0".as_ptr() as *const _);
let get_window = (functions.GetMethodID)(env, activity_class, "getWindow\0".as_ptr() as *const _, "()Landroid/view/Window;\0".as_ptr() as *const _);
let window_class = (functions.FindClass)(env, "android/view/Window\0".as_ptr() as *const _);
let get_decor_view = (functions.GetMethodID)(env, window_class, "getDecorView\0".as_ptr() as *const _, "()Landroid/view/View;\0".as_ptr() as *const _);
let view_class = (functions.FindClass)(env, "android/view/View\0".as_ptr() as *const _);
let set_system_ui_visibility = (functions.GetMethodID)(env, view_class, "setSystemUiVisibility\0".as_ptr() as *const _, "(I)V\0".as_ptr() as *const _);
let window = (functions.CallObjectMethod)(env, unsafe { (*activity).clazz }, get_window);
let decor_view = (functions.CallObjectMethod)(env, window, get_decor_view);
let flag_fullscreen_id = (functions.GetStaticFieldID)(env, view_class, "SYSTEM_UI_FLAG_FULLSCREEN\0".as_ptr() as *const _, "I\0".as_ptr() as *const _);
let flag_hide_navigation_id = (functions.GetStaticFieldID)(env, view_class, "SYSTEM_UI_FLAG_HIDE_NAVIGATION\0".as_ptr() as *const _, "I\0".as_ptr() as *const _);
let flag_immersive_sticky_id = (functions.GetStaticFieldID)(env, view_class, "SYSTEM_UI_FLAG_IMMERSIVE_STICKY\0".as_ptr() as *const _, "I\0".as_ptr() as *const _);
let flag_fullscreen = (functions.GetStaticIntField)(env, view_class, flag_fullscreen_id);
let flag_hide_navigation = (functions.GetStaticIntField)(env, view_class, flag_hide_navigation_id);
let flag_immersive_sticky = (functions.GetStaticIntField)(env, view_class, flag_immersive_sticky_id);
let flag = flag_fullscreen | flag_hide_navigation | flag_immersive_sticky;
(functions.CallVoidMethod)(env, decor_view, set_system_ui_visibility, flag);
}
(The code already there is a rust translation of what @philip-alldredge suggested in #234)
Obviously if I were to not include this it would crash the application with a linker error, so this is definitely not user-friendly, but perhaps a subscriber-style system would work well? Or even a Mutex<Option<fn(*const NativeActivity, i32)>>
globally for a single one.
We'll need to keep in mind that the JNIEnv provided by ANativeActivity cannot be shared with other threads. To create one for another thread requires using JavaVM and attaching a thread to it.
In @OptimisticPeach's case, code will need to be ran at startup and on subsequent window focus changes. That should be doable in main as long as something provides a way to get or crate a valid JNIEnv
for the current thread.
I think an high-level set of APIs that calls a closure with a a valid jni::JNIEnv would work well for most cases. Assuming that cost must be ran on the UI thread, there should be a way to run the closure on the UI thread using Activity.runOnUiThread.
My feeling is that most of this should eventually be in android-ndk
/android-ndk-sys
.
I've been working on android-ndk
. Although I haven't pushed it to Crates.io yet, I currently have the following:
extern crate jni;
/// An `ANativeActivity *`
struct NativeActivity { ... }
impl NativeActivity {
// other methods ...
pub fn vm(&self) -> jni::JavaVM { ... }
}
It's usable like let env = native_activity.vm().attach_current_thread(); ...
. Would this (together with an interface to android-ndk
from android-rs-glue) solve what you're looking for?
Yes, this would most likely solve what I'm looking for. I'll try this as soon as possible.
I believe this should have a simple answer and I'm just missing it, but how might I acquire a *const ANativeActivity
to create a NativeActivity
? I'm currently using my injected function talked about earlier, but even then I'm running into some errors due to threading.
Trying to use some kind of lazy_static
doesn't work because NonNull
isn't thread safe, and using a thread_local
doesn't work either because we call onWindowFocusChanged
on a separate thread in the C++ code.
Yes, this does require changes to android-rs-glue to provide access to these things. That's part of the plan of #228, which I'll rebase/fix once I feel like android-ndk
is in a usable enough state.
I think that ideally, you would get a struct android_app *
from android_glue, and android-ndk
would provide a nice wrapper for it that gives access to the ANativeActivity
, AConfiguration
, ANativeWindow
, saved state, etc.
That sounds wonderful, meaning that I can get a pointer to it from any thread!
Yes, this does require changes to android-rs-glue to provide access to these things. That's part of the plan of #228, which I'll get to rebasing/fixing soon.
The plan is that android_glue gives you a struct android_app *
, and android-ndk
provides a nice wrapper for it that gives access to the ANativeActivity
, AConfiguration
, ANativeWindow
, saved state, etc. I recently pushed a new version of android-ndk
that has most of this; the docs are here.
Currently it is difficult to interact with the JNI and NDK through rust. This issue is asking for:
This can be mostly covered by
android-ndk-rs
but a portion of it should probably be done here.This is closer to a goal than an issue.