Search code examples
androidandroid-ndkjava-native-interfacenative-activity

"No implementation found for" using NativeActivity


I have a project that I am working on that uses a NativeActivity, along with the android_native_app_glue files to connect to the Java side. I need to access a few non NDK classes through Java and have figured out how to make the calls from C to Java, however I am having issues with the reverse. When calling from Java to C I get "No implementation found for etc...", I have checked most of the pitfalls for this kind of call and believe everything is correct. Here is the Activity class.

package com.JNITest;

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

public class JniNativeActivity extends NativeActivity
{
    private static String TAG = "JniNativeActivity";

    public static native void nativeJniCall();

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

    public void testCall()
    {
        Log.v(TAG, "Calling native");
        nativeJniCall();
    }
}

Since this is a NativeActivity using the glue code there is no JNI_OnLoad called, the entry point is android_main (which the glue code has setup to run in its own thread).

void android_main(struct android_app* state)
{
    test_call(state->activity);
    etc...
}

...

void test_call(ANativeActivity *activity)
{
    JNIEnv *jni;

    activity->vm->AttachCurrentThread(&jni, NULL);

    jclass activityClass=jni->GetObjectClass(activity->clazz);
    jmethodID testCallId=jni->GetMethodID(activityClass, "testCall", "()V");

    jni->CallVoidMethod(activity->clazz, testCallId);
}

extern "C"
{
    JNIEXPORT void JNICALL Java_com_JNITest_JniNativeActivity_nativeJniCall(JNIEnv *, jclass)
    {
        LOGI("nativeJniCall");
    }
}

from logcat

...
waiting for debugger to settle...
waiting for debugger to settle...
debugger has settled (1388)
Creating JniNativeActivity
Creating: 0xb420b100
Config: mcc=0 mnc=0 lang=en cnt=US orien=2 touch=3 dens=576 keys=2 nav=1 keysHid=1 navHid=0 sdk=22 size=2 long=1 modetype=1 modenight=1
Start: 0xb420b100
removeObsoleteFile: deleting file=161_task.xml
Calling native
No implementation found for void com.JNITest.JniNativeActivity.nativeJniCall() (tried Java_com_JNITest_JniNativeActivity_nativeJniCall and Java_com_JNITest_JniNativeActivity_nativeJniCall__)
activityState=10
Resume: 0xb420b100
activityState=11
Adding window Window{2b21e24c u0 com.JNITest/com.JNITest.JniNativeActivity} at 2 of 8 (before Window{3238fdec u0 Starting com.JNITest})
InputQueueCreated: 0xb420b100 -- 0xb420b880
APP_CMD_INPUT_CHANGED
Attaching input queue to looper
NativeWindowCreated: 0xb420b100 -- 0xb417ae08
APP_CMD_INIT_WINDOW
Waiting for host to establish connection
WindowFocusChanged: 0xb420b100 -- 1
Accepting new connection from local client
Accepted new client connection: PID = 3945, fd = 39
Requesting connection from host
Sending initiation request to host
Accepting new connection from host service
Accepted new host connection: fd = 40
Connecting local socket 39 to host socket 40
HostConnection::get() New Host Connection established 0xb4272e50, tid 3976
Displayed com.JNITest/.JniNativeActivity: +5s618ms

The fact that anything runs implies that the library is loaded, it is also in the log

...
=thread-created,id="9",group-id="i1"[New Thread 3958]
=thread-created,id="10",group-id="i1"[New Thread 3959]
=thread-created,id="11",group-id="i1"[New Thread 3960]
Loaded 'D:\projects\JNITest\JNITest\JNITest.Android.NativeActivity\x86\Debug\libJNITest.so'
=thread-created,id="12",group-id="i1"[New Thread 3976]
[Switching to Thread 3976]
=thread-created,id="13",group-id="i1"[New Thread 3975]
Loaded 'gralloc.donatello.so'
...

I have verified its name with javah

extern "C" {
#endif
/*
 * Class:     com_JNITest_JniNativeActivity
 * Method:    nativeJniCall
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_JNITest_JniNativeActivity_nativeJniCall
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif

I have also checked the library externs and the function is there

...
         U glRotatef
         U glShadeModel
         U glTranslatef
         U glVertexPointer
         U glViewport
00007004 D indices
00004b70 T Java_com_JNITest_JniNativeActivity_nativeJniCall
         U malloc
         U memcpy
         U memset
         U pipe
...

I had this issue in another project and created a test app (where the code above is from) so it is not some random unique config problem. I am at a bit of a loss on this one, I am assuming it has some issue with the NativeActivity and glue code (likely the threading) but not sure how to work around it. Any help would be appreciated.


Solution

  • Ok well I found the issue. Seems in this case since the System.loadlibrary is not used with the glue code then the standard lookup mechanism does not work. You have to call RegisterNatives manually (from a valid JNIEnv). So for this case you need to do the following:

    void nativeJniCall(JNIEnv *, jclass)
    {
        LOGV("nativeJniCall");
    }
    
    void android_main(struct android_app* state)
    {
        test_call(state->activity);
        etc...
    }
    
    ...
    
    void test_call(ANativeActivity *activity)
    {
        JNIEnv *jni;
    
        activity->vm->AttachCurrentThread(&jni, NULL);
    
        jclass activityClass=jni->GetObjectClass(activity->clazz);
        jmethodID testCallId=jni->GetMethodID(activityClass, "testCall", "()V");
    
        JNINativeMethod methodTable[]=
        {
            {"nativeJniCall", "()V", (void *)nativeJniCall}
        };
    
        int methodTableSize=sizeof(methodTable)/sizeof(methodTable[0]);
    
        jni->RegisterNatives(activityClass, methodTable, methodTableSize);
    
        jni->CallVoidMethod(activity->clazz, testCallId);
    }
    

    For production you would cache all the jclass/jmethoidIDs (remember to use NewGlobalRef/DeleteGlobalRef) and only register the the native functions with the class once, but this serves as an example. Hope it saves someone else some time.