How do I load my own Java class in C on Android?

11,080

Solution 1

The subtlety of my question, and why the documentation linked to by Klaimmore and Winston does not quite resolve the issue, stems from the fact that I am writing an app using the NativeActivity class. This means that there is never a Java stack with a local class loader available to me. There is no JNI_OnLoad call, there is no Java method calling into my native function, and there is no other way (that I am aware of) of getting ahold of a local ClassLoader object. Unfortunately, most of the Android JNI documentation is simply not written with NativeActivity in mind.

However, there is a straightforward solution to this problem that can make your life much easier when writing apps using NativeActivity. Simply subclass NativeActivity in Java. This allows you to write and access arbitrary Java code from the beginning of your NativeActivity's instantiation, while still doing everything else in C as NativeActivity allows. It also allows you to set up your JNI calls from C in the manner described in those documents. Doing this looks something like the following:

package com.example.my.package;

import android.app.NativeActivity;
import android.util.Log;

public class MyNativeActivity extends NativeActivity {
  static {
    System.loadLibrary("my_ndk_lib");  
  }

  private static String TAG = "MyNativeActivity";

  public MyNativeActivity() {
    super();
    Log.v(TAG, "Creating MyNativeActivity");
  }

  public static void MyUsefulJavaFunction() {
    doSomethingAwesome();
  }
}

And in your C library:

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
  JNIEnv* env;
  if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK)
    return -1;

  globalMyNativeActivityClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/example/my/package/MyNativeActivity"));

  return JNI_VERSION_1_6;
}

Then at some point in C, you can do:

// Some file.c
void doSomethingAwesomeInJava() {
  JavaVM *vm = AndroidGetJavaVM(); // This returns a valid JavaVM object
  JNIEnv* env;
  (*vm)->AttachCurrentThread(vm, &env, 0);

  jmethodID myUsefulJavaFunction = (*env)->GetStaticMethodID(env, globalMyNativeActivityClass, "MyUsefulJavaFunction", "()V");
  (*env)->CallStaticVoidMethod(env, theActivityClass, myUsefulJavaFunction);

  (*env)->DeleteLocalRef(env, myUsefulJavaFunction);

  (*vm)->DetachCurrentThread(vm);
}

And that is the way I found to incorporate my own new Java classes with a NativeActivity app. Hopefully this will be useful to someone besides me.

Solution 2

Here is what I have abstracted from this link as mentioned by Klaimmore.

There are a few ways to work around this:

Do your FindClass lookups once, in JNI_OnLoad, and cache the class references for later use. Any FindClass calls made as part of executing JNI_OnLoad will use the class loader associated with the function that called System.loadLibrary (this is a special rule, provided to make library initialization more convenient). If your app code is loading the library, FindClass will use the correct class loader.*

Pass an instance of the class into the functions that need it, by declaring your native method to take a Class argument and then passing Foo.class in.

Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly. This requires some effort.

Share:
11,080

Related videos on Youtube

itfische
Author by

itfische

Updated on June 04, 2022

Comments

  • itfische
    itfische almost 2 years

    I am trying to call some Java code that I wrote from C using the Android NDK. The application is a NativeActivity application. I have to access some functionality that is only available in Java, and the functionality requires you to subclass another class, so I can't just directly do the calls from C. Thus, I have Java code like this:

    // src/com/example/my/package/SubClass.java
    package com.example.my.package;
    
    import android.foo.TheSuperClass;
    
    public class SubClass extends TheSuperClass {
      public SubClass() {
        setImportantProperty(true);
      }
    }
    

    I also have C code like this:

    // Some file.c
    void setThatImportantJavaProperty() {
      JavaVM *vm = AndroidGetJavaVM(); // This returns a valid JavaVM object
      JNIEnv* env;
      (*vm)->AttachCurrentThread(vm, &env, 0);
    
      jclass theSubClass = (*env)->FindClass(env, "com/example/my/package/SubClass");
      jthrowable exception = (*env)->ExceptionOccurred(env);
      if (exception) {
        (*env)->ExceptionDescribe(env);
        // This gives me: "java.lang.NoClassDefFoundError: [generic]".
        // Also, theSubClass is null, so the next line causes a segfault.
      }
      jmethodID theSubClassConstructor = (*env)->GetMethodID(env, theSubClass, "<init>", "()V");
      jobject theSubClassObject = (*env)->NewObject(env, theSubClass, theSubClassConstructor);
    
      (*env)->DeleteLocalRef(env, theSubClass);
      (*env)->DeleteLocalRef(env, theSubClassConstructor);
      (*env)->DeleteLocalRef(env, theSubClassObject);
    
      (*vm)->DetachCurrentThread(vm);
    
    }
    

    As the inline comments say, running this gives me a "java.lang.NoClassDefFoundError: [generic]" error. When I unpack my apk, it shows me the classes.dex file, which appears to have my class in it. My guess is that there is some subtlety that I am missing regarding classpaths, but I am unable to resolve it thus far.

    Incidentally, I am able to make similar calls to standard Android libraries from C without a problem (in the same C function above). Specifically, I tested calling Log.v and that works and prints the output correctly.

    It seems that all the examples that I am finding only show how to call normal Java libraries from C, not Java libraries you have written yourself, so I haven't even found an example project to compare against.

  • user207421
    user207421 about 12 years
    myUsefulJavaFunction is a jmethodID. A jmethodID is not a LocalRef and should not be deleted.
  • Alex Cohn
    Alex Cohn over 11 years
    This workaround is important also in other cases, like this one!
  • Cross_
    Cross_ about 10 years
    That's a limited workaround. What if you have more than one custom class? Maybe it is possible to get to that ClassLoader from JNI_OnLoad that is mentioned in the google docs.
  • C0D3LIC1OU5
    C0D3LIC1OU5 over 8 years
    AndroidGetJavaVM() throws Error:(99) undefined reference to AndroidGetJavaVM for me.