Native android plugin get UnityPlayer activity and context

Has anyone here worked with native android plugin for Unity?

Was wondering if there was a way to get UnityPlayer Activity and Context from JNI_OnLoad on the native side, instead of passing from Unity C# as following:

var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
var context = activity.Call<AndroidJavaObject>("getApplicationContext");

My equivalent conversion on native side looks like:

jclass  unityPlayer = LoadJClass(env, "com/unity3d/player/UnityPlayer");
CheckExceptionInJava(env);
jfieldID fid = env->GetStaticFieldID(unityPlayer, "currentActivity", "Landroid/app/Activity");
CheckExceptionInJava(env);
jobject activity = env->GetStaticObjectField(unityPlayer,fid);
jmethodID mid = env->GetMethodID((jclass)activity, "getApplicationContext", "()Landroid/content/Context");
jobject context = env->CallObjectMethod(activity,mid);
CheckExceptionInJava(env);

However, at runtime getting: java.lang.NoSuchFieldError: no type “Landroid/app/Activity” found and so no field “currentActivity” could be found in class “Lcom/unity3d/player/UnityPlayer;” or its superclasses

Aren’t signatures for class types supposed to include a semicolon at the end?
Like: Landroid/app/Activity**;**
Same when searching for getApplicationContext method.

Right, thanks for pointing that out.

The bigger issue that I faced was getting methodID for “getApplicationContext” from the activity object. And indeed upon scouring through UnityPlayerActivity.java and UnityPlayer.class I couldn’t find the symbol via javap util. So I ended up getting this from ActivityThread class (probably what Unity does internally as well). Let me know if there’s better way for this.

jclass  unityPlayer = LoadJClass(env,"com/unity3d/player/UnityPlayer");
CheckExceptionInJava(env);
jfieldID fid = env->GetStaticFieldID(unityPlayer,"currentActivity","Landroid/app/Activity;");
CheckExceptionInJava(env);
jobject activity = env->GetStaticObjectField(unityPlayer,fid);
CheckExceptionInJava(env);
jclass activityThreadCls = env->FindClass("android/app/ActivityThread");
CheckExceptionInJava(env);
jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadCls,"currentActivityThread", "()Landroid/app/ActivityThread;");
jobject activityThreadObj = env->CallStaticObjectMethod(activityThreadCls, currentActivityThread);
CheckExceptionInJava(env);
jmethodID getApplication = env->GetMethodID(activityThreadCls, "getApplication", "()Landroid/app/Application;");
jobject context = env->CallObjectMethod(activityThreadObj, getApplication);
CheckExceptionInJava(env);

@nfynt-zap were you able to successfully use JNI_OnLoad ? If so, would you mind showing a code snippet? And are you doing it straight from a c++ file in your unity project or building a library first then adding that to your unity project?

I keep getting the error “multiple definition of `JNI_OnLoad’” when building.

It seems it is also used in Initialize.cpp, but that seems to be part of the Unity source.

I would appreciate any help here, thanks :slight_smile:

There is JNI_OnLoad in Unity code, so if you drop c++ file in the project and have stripping enabled, you get a clash.
Maybe disabling stripping would help, but the recommended way is to prelink your library.

I was able to get the AAssetManager without needing any c++. Hacked together some stuff and came up with this. It can be called from a native plugin. This circumvents the need for a custom activity or JNI_OnLoad. Maybe it is useful for someone in the future :

static IntPtr SetupAndroidJNICallback()
    {

        //starting point here : https://stackoverflow.com/questions/58980171/using-aassetmanager-fromjava-within-plugin-not-directly-called-from-java-vm-cal?rq=1

        AndroidJNI.AttachCurrentThread();

        IntPtr unity_player = AndroidJNI.FindClass("com/unity3d/player/UnityPlayer"); //TODO : this might need to be changed if the unity project uses a custom activity
        IntPtr static_activity_id = AndroidJNI.GetStaticFieldID(unity_player, "currentActivity", "Landroid/app/Activity;");
        IntPtr unity_activity = AndroidJNI.GetStaticObjectField(unity_player, static_activity_id);
        IntPtr get_assets_id = AndroidJNI.GetMethodID(AndroidJNI.GetObjectClass(unity_activity), "getAssets", "()Landroid/content/res/AssetManager;");
        IntPtr java_asset_manager = AndroidJNI.CallObjectMethod(unity_activity, get_assets_id, null);
        Debug.Log("java_asset_manager : " + java_asset_manager);
        IntPtr g_JavaAssetManager = AndroidJNI.NewGlobalRef(java_asset_manager); //HAS TO BE GARBAGE COLLECTED using Delete if desired
 
        //hacked together from android sources found here :
        //retrieved from : https://android.googlesource.com/platform/frameworks/base.git/+/android-4.2.2_r1/core/jni/android_util_AssetManager.cpp
        //AND here : https://android.googlesource.com/platform/frameworks/base/+/master/native/android/asset_manager.cpp
        //AND UPDATED VERSION HERE : https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/jni/android_util_AssetManager.cpp
 
        IntPtr assetManager = AndroidJNI.FindClass("android/content/res/AssetManager");
        IntPtr gAssetManagerOffsetsMObject = AndroidJNI.GetFieldID(assetManager, "mObject", "J");
 
        IntPtr am = (IntPtr)AndroidJNI.GetLongField(g_JavaAssetManager, gAssetManagerOffsetsMObject);
        Debug.Log("am : " + am); //make sure it isn't 0 (NULL), if so check for NULL in earlier variables

 
        return am;
    }

It can then be used directly with methods like AAssetManager_open.