Skip to content

[Android] JNI

Zly edited this page Oct 28, 2024 · 7 revisions

Documentation and info

UPL

JNI

Signature of Java functions

CDATA - a section of element content that is marked for the parser to interpret as only character data, not markup.

Sentry - Can use as an example for JNI related stuff.


You can modify internal Java code with help of UPL by injecting the code in the places where $${TemplateName}$$ exists, for example gameActivityClassAdditions in GameActivity.java.template file.


If needed you can always wrap Android features into the preprocessor to cut it out from other platform builds:

#if PLATFORM_ANDROID
...
#endif

Calling C++ function from Java

For example: In <ModuleName>_UPL.xml, for GameActivity.java.template you can add:

<gameActivityClassAdditions>
    <insert>
        <![CDATA[
        private static native void onLocalNotificationsPermissionResult(boolean isGranted);
        ]]>
    </insert>
</gameActivityClassAdditions>

And in C++:

#include <jni.h>
#include "Android/AndroidJNI.h"
#include "Android/AndroidApplication.h"

...

extern "C" {
    JNI_METHOD void Java_com_epicgames_unreal_GameActivity_onLocalNotificationsPermissionResult(JNIEnv* jenv, jobject self, jboolean bIsGranted)
    {
        if (bIsGranted == JNI_TRUE)
        {
            UE_LOG(LogTemp, Log, TEXT("Granted."));
        }
        else
        {
            UE_LOG(LogTemp, Log, TEXT("Denied."));
        }
    }
}
  • The Java_com_epicgames_unreal_GameActivity_ of the C++ function above reflects the path where GameActivity.java is located, it's important.
  • <ModuleName>_UPL.xml needs to be placed in the very top level directory of your Plugin/GameModule, where <ModuleName>.Build.cs is placed.

Calling custom Java function from C++

First, add a new function into, for example, GameActivity.java (GameActivity.java.template):

<gameActivityClassAdditions>
    <insert>
        <![CDATA[
        public boolean AndroidThunkJava_SomeTestJavaFunction(int number)
        {
            if(number == 69){
                return true;
            }
            return false;
        }
        ]]>
    </insert>
</gameActivityClassAdditions>

Then fetch the said function and wrap it into something to easily execute it: .h

#include <jni.h>
#include "Android/AndroidJNI.h"

class FAndroidCustomHelperFunctions
{
public:
    static void FindClassesAndMethods(JNIEnv* Env);

    static bool AndroidThunkCpp_SomeTestJavaFunction(const int number);

private:
    static jmethodID AndroidThunkJava_SomeTestJavaFunction;
}

.cpp

#include "Android/AndroidJNI.h"
#include "Android/AndroidApplication.h"

// For Env can potentially use `AndroidJavaEnv::GetJavaEnv();` or `FAndroidApplication::GetJavaEnv();`

void FAndroidCustomHelperFunctions::FindClassesAndMethods(){
    JNIEnv* Env = AndroidJavaEnv::GetJavaEnv();

    AndroidThunkJava_SomeTestJavaFunction = FJavaWrapper::FindMethod(
        Env,
        FJavaWrapper::GameActivityClassID,                  // Class to find method from
        "AndroidThunkJava_SomeTestJavaFunction", "(I)Z",    // Method name and its Signature
        false /*unused arg*/
    );
}

bool FAndroidCustomHelperFunctions::AndroidThunkCpp_SomeTestJavaFunction(const int number)
{
    JNIEnv* Env = AndroidJavaEnv::GetJavaEnv();

    bool result = FJavaWrapper::CallBooleanMethod(
        Env,
        FJavaWrapper::GameActivityThis, // Class to use
        FAndroidCustomHelperFunctions::AndroidThunkJava_SomeTestJavaFunction, // Method to call
        number // Args... (any amount)
    );

    return result;
}

ProGuard (Might be outdated, but will see)

UE 4 used code obfuscation system called ProGuard on it’s java layer, because of that functions name are modified and C++ references fails due to that name change. You need to add exception to your function in ProGuard configuration, you can find engine directory \Engine\Build\Android\Java\proguard-project.txt. All other linking function that engine used are already there too which shows you a example

Can edit ProGuard via UPL, example from a Sentry's plugin source:

<proguardAdditions>
    <insert>
        -dontwarn io.sentry.unreal.**
        -keep class io.sentry.** { *; }
        -keep interface io.sentry.** { *; }
    </insert>
</proguardAdditions>