Download

Custom java method called from C++ causes ClassNotFoundException

i’ve asked this on answers, yet nobody answered for days, so I’m asking from the forum. If someone knows how to solve the problem, please help, stuck badly at here.

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 [FONT=Courier New]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.

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: [FONT=Courier New]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"
 #include "Android/AndroidJNI.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"

Have you tried using a plugin and UPL ? Check my plugins:https://forums.unrealengine.com/showthread.php?127487-FREE-AdColony-AppLovin-Chartboost-UnityAds-Vungle-Sharing-OneSignal-Facebook-Everyplay&p=617273#post617273 and see how i am calling java functions.

Add the following functions to the UPL/APL FILE.

public void AndroidThunkJava_openPhotoGallery(String fileLocation){

 }

public void AndroidThunkJava_openCamera(String fileLocation){

 }

And then see how i call similar methods from c++.

Problem is, some parts of the game is written in java by our android team, not me.
I cannot simply move the code myself, it requires too much time… or teach them how to embed java code to UE4 classes, they have no idea how ue works and knowledge of c++.

If I’m unable to solve this in a few days, writing a plugin is the only remaining option…

Currently I’m trying to access [FONT=Courier New]java/lang/ClassLoader from unreal source (JNI_OnLoad).
If I’m correct, the class loader reference is released after JNI_OnLoad ends. So that might be my problem.

Maybe you can make .jar/.aar file from the android studio with all functionality you have and include them in the plugin. So you don’t have to rewrite much of your code. I don’t know what scale has you project though.

Hmm… good idea, might work, will give it a try…

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 environment that can never be able to access the dex classes. I have to get the environment from the active context, which 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();

[FONT=Courier New]“Android/AndroidApplication.h” has already a method for calling classes and works as expected.
So simple :slight_smile:

Yet now I have another problem…
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…

You can use GameActivity.Get() to access the activity, so you can do something like this in your JNI Java code:


GameActivity.Get().runOnUIThread(new runnable()
  {
      public void run()
      {
          .. do stuff
      }
  });

Take a look in MessageBox01.java for an example.

Thanks for the tip :slight_smile: