Android JNI GetMethodID()失败

时间:2017-10-04 00:05:55

标签: java android c cordova java-native-interface

我正在尝试构建Cordova插件以支持Ionic Cordova应用程序中使用的C库。到目前为止,我的代码的JavaScript→Java和Java→C部分工作。我可以在手机上运行的Android Studio中成功调试C代码。但是,我的C库有一个需要在堆栈中传递的回调方法(C→Java→JavaScript),并且我在使JNI方法正常工作时遇到问题。到目前为止,这是我的代码:

AgentMgrService.Java

package com.example;

import ...

public class AgentMgrService {

    private static final String TAG = "AgentMgrService";
    private boolean libLoaded = false;
    private static Context mContext;

    public CallbackContext jsCallback;

    // C-function interface
    public static native void startAgentMgr(String agentMgrConfig);
    public static native void stopAgentMgr();

    // load library
    static {
        System.loadLibrary("lib_agentmgr");
    }
    public AgentMgrService(Context context) {
        mContext = context;
    }

    public void startMobileAgentMgr(String agentmgrConfig) throws RemoteException {
        startAgentMgr(agentmgrConfig);

    public void testMe() {
        Log.d(TAG, "testMe!");
    }

    public String toString() {
        Log.d(TAG, "This is a string!");
        return "This is a string!";
    }

}

AgentMgrJni.c

#include ...

static JavaVM* _jamgr_appVm = NULL;
static jobject _jamgr_appObj = NULL;

void
Java_com_example_AgentMgrService_startAgentMgr(
        JNIEnv* env,
        jobject thiz,
        jstring config_data)
{
    if (_jamgr_appObj == NULL) {
      _jamgr_appObj = (*env)->NewGlobalRef(env, thiz);
    }

    //... Stuff happens here ...

    jni_callback();

}

int
jni_callback()
{
    JNIEnv* env = NULL;
    jint retval = 0;
    jmethodID mid = NULL;
    jclass cls = NULL;

    retval = (*_jamgr_appVm)->GetEnv(_jamgr_appVm, (void**) &env, JNI_VERSION_1_6);

    cls = (*env)->GetObjectClass(env, _jamgr_appObj);


    //Try the toString() method
    mid = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;");
    jobject strObj = (*env)->CallObjectMethod(env, _jamgr_appObj, mid);
    const char* str = (*env)->GetStringUTFChars(env, strObj, NULL);
    printf("\nCalling class is: %s\n", str);
    //this prints "class com.example.AgentMgrService"

    mid = (*env)->GetMethodID(env, cls, "testMe", "()V");
    //this returns NULL and thus the below call fails
    (*env)->CallVoidMethod(env, _jamgr_appObj, mid, jstr);

    return retval;
}

运行上面的代码时,一切行为都很好,直到第一个GetMethodID()。致电toString()时,我会将样板"class com.example.AgentMgrService"作为回复。但是等等,我超载了toString()!此外,尝试获取testMe()返回NULL,因此无法找到该方法。所以我在正确的类中,实际上可以从C中调用一些Java方法,但不是我定义的方法?我还没有试过制作任何静态但我不确定这是否会有所帮助。

2 个答案:

答案 0 :(得分:1)

您的问题的答案在于您的原生方法是否是静态的。

在JNI中,如果你有

public static native void startAgentMgr(String agentMgrConfig);
public static native void stopAgentMgr();

在java方面,当你调用这个方法时,this对象将是一个CLASS而不是一个INSTANCE。毕竟,该方法是静态的,它没有this

但是,如果您将其更改为(请注意缺少static关键字):

public native void startAgentMgr(String agentMgrConfig);
public native void stopAgentMgr();

然后当您运行代码时,this参数将是调用此方法的对象的实例。

示例:

package com.example.brandon.test;

import android.content.Context;
import android.os.RemoteException;
import android.util.Log;

public class AgentMgrService {
    private static final String TAG = "AgentMgrService";

    // load library
    static {
        System.loadLibrary("lib_agentmgr");
    }

    // C-function interface
    public native void startAgentMgr(String agentMgrConfig);
    public native void stopAgentMgr();

    public AgentMgrService(Context context) {

    }

    public void startMobileAgentMgr(String agentmgrConfig) throws RemoteException {
        startAgentMgr(agentmgrConfig);
    }

    public void testMe() {
        Log.d(TAG, "testMe!");
    }

    @Override
    public String toString() {
        Log.d(TAG, "This is a string!");
        return "This is a string!";
    }
}

原生代码:

#include <jni.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

static JavaVM* _jamgr_appVm = NULL;
static jobject _jamgr_appObj = NULL;

int jni_callback();

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* aReserved)
{
    _jamgr_appVm = vm;
    return JNI_VERSION_1_4;
}

JNIEXPORT void JNICALL
Java_com_example_brandon_test_AgentMgrService_startAgentMgr(
        JNIEnv* env,
        jobject thiz,
        jstring config_data)
{
    if (_jamgr_appVm == NULL)
    {
        (*env)->GetJavaVM(env, *_jamgr_appVm);
    }

    if (_jamgr_appObj == NULL) {
        _jamgr_appObj = (*env)->NewGlobalRef(env, thiz);
    }

    jni_callback();

}

JNIEXPORT void JNICALL
Java_com_example_brandon_test_AgentMgrService_stopAgentMgr(
        JNIEnv* env,
        jobject thiz,
        jstring config_data)
{
    (*env)->DeleteGlobalRef(env, _jamgr_appObj);
    _jamgr_appObj = NULL;
}

int jni_callback()
{
    JNIEnv* env = NULL;
    jint retval = (*_jamgr_appVm)->GetEnv(_jamgr_appVm, (void**) &env, JNI_VERSION_1_4);

    if (retval == JNI_OK)
    {
        jclass cls = (*env)->GetObjectClass(env, _jamgr_appObj);
        if (cls)
        {
            jmethodID  mid = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;");
            if (mid)
            {
                jobject strObj = (*env)->CallObjectMethod(env, _jamgr_appObj, mid);

                if (strObj)
                {
                    const char *str = (*env)->GetStringUTFChars(env, strObj, NULL);
                    printf("\nCalling class is: %s\n", str);
                    (*env)->ReleaseStringUTFChars(env, strObj, str);
                    strObj = NULL;
                }


                mid = (*env)->GetMethodID(env, cls, "testMe", "()V");

                if (mid)
                {
                    (*env)->CallVoidMethod(env, _jamgr_appObj, mid);
                }
            }
        }

    }

    return retval;
}

这将做你想要的,因为JNI方法在Java端不是静态的。但是,如果您将它们设为静态,那么getMethodID将失败,因为它是一个类,而不是AgentMgrJni的实例。

另请注意,我通过ReleasingUTFChars修复了内存泄漏..以及其他错误处理问题。我还在停止函数中调用DeleteGlobalRef ..

答案 1 :(得分:0)

const char* str = (*env)->GetStringUTFChars(env, strObj, NULL);
printf("\nCalling class is: %s\n", str);
//this prints "class com.example.AgentMgrService"

打印调用_jamgr_appObj.toString()的结果。这是"This is a string!"。肯定是 "class com.example.AgentMgrService",无论它打印什么,肯定不是'呼叫类'。你在这里寻找getClass().toString()吗?

否则这不是真正的代码。这看起来更有可能。

//this returns NULL

它返回零。 methodIDs不是指针。

// ... and thus the below call fails
(*env)->CallVoidMethod(env, _jamgr_appObj, mid, jstr);

为什么你不只是传递原始的thiz对象,而JNIEnv*直接传递给jni_callback()对我来说是一个谜。您当然不需要GlobalRef或致电String.toString()

此代码中缺少错误检查。 每次 JNI调用后都必须检查其结果,如果错误,则必须使用异常检查或打印或抛出方法之一。而不是像错误那样继续进行。