Custom java called from c++ causes ClassNotFoundException

I’m currently trying to extend the java part of a game i’m developing for Android. We need to access photo gallery and camera from the device. So we have added a few java classes to Build/Android/src/…/ folder to call from c++.

However, when we tried to access the classes, we are receiving a not found error on jni.
We created a simple java class, and tried to call it from an emptry project with also no luck.

We need help for solving this problem… :slight_smile:

I added a few lines to proguard-project.txt to keep the class if it was being removed, yet it was not. I can see that the class exists in dex file.

There is also another problem. We cannot add any files under Build/Android folder (custom proguard file, build file, extended manifest, etc), as it crashes the UnrealBuildTool with a “pak file not found error”, or sometimes, “file already exists”… but that is a different problem

So… here are the codes that you can use to re-create the circumstances:
proguard-project.txt (C:\Program Files\Epic Games\4.13\Engine\Build\Android\Java)

-keep class com.markakod.hitme.Tools {
  public *;
}

This is our simple test class:

package com.markakod.hitme;

import android.content.Context;
import android.widget.Toast;

public class Tools {
    
    public static void openPhotoGallery(Context context, String fileLocation){
        Toast.makeText(context, fileLocation, Toast.LENGTH_LONG).show();
    }

    public static void openCamera(Context context, String fileLocation){
        Toast.makeText(context, fileLocation, Toast.LENGTH_LONG).show();
    }
    
}

And this is the code we are using to call the class (exception occurs in FindClass line):

#if PLATFORM_ANDROID
#include "Android/AndroidApplication.h"
#endif

void UCaller::CallPhotoGallery(FString Location)
{
	UE_LOG(HITMELOG, Error, TEXT("[UCaller::CallPhotoGallery] For %s"), *Location);

	CreateFolder();

	FString CurrentLocation = LocalImageFolder() + "/" + Location;
	FString ClassName = "[Lcom/markakod/hitme/Tools;";
	FString MethodName = "openPhotoGallery";
	FString MethodSignature = "(Landroid/content/Context;Ljava/lang/String;)V";

#if PLATFORM_ANDROID

	UE_LOG(HITMELOG, Error, TEXT("[UCaller::CallPhotoGallery] Get java environment..."));
	JNIEnv* Env = FAndroidApplication::GetJavaEnv(false);
	FAndroidApplication::CheckJavaException();

	UE_LOG(HITMELOG, Error, TEXT("[UCaller::CallPhotoGallery] Get main activity"));
	jobject Activity = FAndroidApplication::GetGameActivityThis();
	FAndroidApplication::CheckJavaException();

	UE_LOG(HITMELOG, Error, TEXT("[UCaller::CallPhotoGallery] Get class %s"), *ClassName);
	jclass Class = Env->FindClass(TCHAR_TO_ANSI(*ClassName));
	FAndroidApplication::CheckJavaException();

	if (Class != NULL) {
		UE_LOG(HITMELOG, Error, TEXT("[UCaller::CallPhotoGallery] Get static method %s"), *MethodName);
		jmethodID Method = Env->GetStaticMethodID(Class, TCHAR_TO_ANSI(*MethodName), TCHAR_TO_ANSI(*MethodSignature));
		FAndroidApplication::CheckJavaException();

		if (Method != NULL) {
			UE_LOG(HITMELOG, Error, TEXT("[UCaller::CallPhotoGallery] Create string from %s"), *CurrentLocation);
			jstring jLocation = Env->NewStringUTF(TCHAR_TO_ANSI(*CurrentLocation));

			UE_LOG(HITMELOG, Error, TEXT("[UCaller::CallPhotoGallery] Call static method %s"), *MethodName);
			Env->CallStaticVoidMethod(Class, Method, Activity, jLocation);
			FAndroidApplication::CheckJavaException();

			UE_LOG(HITMELOG, Error, TEXT("[UCaller::CallPhotoGallery] Done..."));
			Env->DeleteLocalRef(jLocation);
		}
	}
#endif
}

From the crash log…

12-28 10:53:08.001 13360 13380 W System.err: java.lang.ClassNotFoundException: Didn't find class "com.markakod.hitme.Tools" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
12-28 10:53:08.001 13360 13380 W System.err:    at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
12-28 10:53:08.001 13360 13380 W System.err:    at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
12-28 10:53:08.001 13360 13380 W System.err:    at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
12-28 10:53:08.001 13360 13380 W System.err:    Suppressed: java.lang.ClassNotFoundException: com.markakod.hitme.Tools
12-28 10:53:08.001 13360 13380 W System.err:            at java.lang.Class.classForName(Native Method)
12-28 10:53:08.001 13360 13380 W System.err:            at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
12-28 10:53:08.001 13360 13380 W System.err:            at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
12-28 10:53:08.001 13360 13380 W System.err:            at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
12-28 10:53:08.001 13360 13380 W System.err:            ... 1 more
12-28 10:53:08.001 13360 13380 W System.err:    Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available

And at last, this is from the dexdump of classes.dex:

dexdump.exe classes.dex | findstr Tools
  Class descriptor  : 'Lcom/markakod/hitme/Tools;'
    #0              : (in Lcom/markakod/hitme/Tools;)
        0x0000 - 0x0004 reg=0 this Lcom/markakod/hitme/Tools;
    #1              : (in Lcom/markakod/hitme/Tools;)
    #2              : (in Lcom/markakod/hitme/Tools;)
  source_file_idx   : 14003 (Tools.java)

Decompiling the apk proves again that the Tools class exists.

.class public Lcom/markakod/hitme/Tools;
.super Ljava/lang/Object;
.source "Tools.java"

and the static method signature we are using is true

.method public static openPhotoGallery(Landroid/content/Context;Ljava/lang/String;)V

as christmas is over, may someone from staff check this one out? :slight_smile:

i’ve upgraded to 4.14, yet the same error occurs.

While looking what was going on, I found out that the environment reference that was returning from vm was not the main activity’s environment.
It was somewhat a bogus system enviroment that can never be able to access the dex classes. I must get the environment from the active context, which it is not possible outside of JNI_Onload…

While checking the source for Android on UE4 I found out that Epic choose a way to get class loader class from the main activity while JNI_OnLoad is first executed and they keep a reference to it plus to the methodid for FindClass.

I am so blind… Epic already thought what I was trying to do and implemented a static method. This is what my code is now:

JNIEnv* Env = FAndroidApplication::GetJavaEnv();
jclass FoundClass = FAndroidApplication::FindJavaClass("com/markakod/hitme/Tools");
FAndroidApplication::CheckJavaException();

Android/AndroidApplication.h has already a method for calling classes and works as expected.
So simple

Yet now I have another problem (which can be solved on java class)…
I always thought that blueprint was working on GUI thread. Yet calling a blueprintable method shows me that JNI is working on another thread. So I need to run my java code in an activity’s runOnUiThread runnable class…

Hi, you seems to know a lot about android side of unreal!

Could you please help me with this?
Get the Android intent extra data on Unreal app launch