在上一篇博客 JNI与NDK开发(二)——JNI基本概念 与 Java对C或C++函数的简单调用 我们介绍了 Java调用C的基本方法,这种方法虽然足够简单,但是写法太死板,多个Native方法时,大量基础代码重复。
Android 和 JNI推荐我们使用的方法是:
主要步骤:
1、在Java层定义,native关键字的方法;
2、通过RegisterNative手工映射Java的native方法和C/C++函数;
上图是cpp写的对应Java的Native函数 和 手工映射Java与C/C++的代码。下面具体介绍一下:
①、定义宏,将Native的class文件路径,用JNI_CLASS_PATH替代,便于后面调用和维护修改;
②、用C/C++代码,实现Java中的Native方法对应调用的具体业务逻辑。这里除了函数名称可以随便写(是的随便写,只要符合C/C++的函数名称规则就好),其他跟第一种JNI调用方法没有任何区别。
注意:C/C++的函数,必须要写到④和⑧前面,否则会报函数未定义;
③、函数名称,跟Java中Native没有半毛钱关系,跟包名类名也无关,随便写的。Java中Native方法和这里的函数,是通过④和⑧这两块代码,手动映射建立联系。这样可以更灵活,不用受制于JNI原有的命名规则的限制;
④、匹配Java的Native方法名和C/C++的函数名称,并通过Signature描述符,表示函数参数的结构和返回类型;
⑤、Java的Native方法名;
⑥、Signature描述符,表示函数参数的结构和返回类型(后续会详细讲,怎么写Signature,很简单,就是标识符);
⑦、C/C++的函数名称;
⑧、真正进行映射的代码,前面步骤基本都是为此准备参数。这里基本是固定写法,只需要写一次后续拷贝就好。(如果每次都要自己写,那就没有什么优势可言了);
JNIEnv *env = NULL;
vm->GetEnv((void**)&env, JNI_VERSION_1_6);
通过JavaVM获取JNIEnv 对象,JNIEnv 是负责进行Java与C/C++方法函数映射,这一点上一篇讲过了。GetEnv的第一个参数,是输出,第二个是使用JNI的版本。
env->RegisterNatives(classz,g_methods,sizeof(g_methods)/sizeof(g_methods[0]))
获取到JNIEnv 对象 env,可以调用其方法RegisterNatives,进行Native方法和C/C++函数进行关联。
classz: 表示Java中native方法所在的class对象;
g_methods:组织Java的native方法 与 C/C++函数进行匹配;
sizeof(g_methods)/sizeof(g_methods[0]):计算g_methods中匹配的个数;
注意:JNI_OnLoad 这个是固定的函数名,不能改变。否则编译不报错,但运行会崩溃。
⑨、JNI_VERSION_1_6 这里表示使用JNI的那个版本。JNI_VERSION_1_6 应该是最新版本吧,JNI_VERSION_1_4这里也是可以用的,应该是向下兼容的。(这里有点不太清楚,没看到啥资料讲。若是你知道,请留言告诉我,谢谢啦!)
以下便于拷贝,附代码:
#include <jni.h>
#include <string>
#define JNI_CLASS_PATH "com/example/jnifrst/jnifrst/MainActivity"
extern "C" JNIEXPORT jstring JNICALL
my_jni_recommend(
JNIEnv* env,
jobject instance){
return env->NewStringUTF("This is the recommended method -- One!");
}
extern "C" JNIEXPORT jstring JNICALL
my_jni_recommend_two(
JNIEnv* env,
jobject instance){
return env->NewStringUTF("This is the recommended method -- Two!");
}
static JNINativeMethod g_methods[] = {
{"jniRecommend","()Ljava/lang/String;",(void*)my_jni_recommend},
{"jniRecommend2","()Ljava/lang/String;",(void*)my_jni_recommend_two},
};
jint JNI_OnLoad(JavaVM *vm,void *reserved){
JNIEnv *env = NULL;
vm->GetEnv((void**)&env, JNI_VERSION_1_6);
jclass classz = env->FindClass(JNI_CLASS_PATH);
env->RegisterNatives(classz,g_methods,sizeof(g_methods)/sizeof(g_methods[0]));
return JNI_VERSION_1_6;
}
以上就是官方推荐的JNI的调用方式。我看很多Android源码,基本都是基于这种方式进行JNI的调用。其中,有很多是规定写法。这些不是我创造的,我只是把我知道的,尽量表达清楚(整天不说话,文章也不会写,慢慢会不会变成自闭?)。
真的,多看源码好处多多。不过看多了,就开始怀疑人生了!
下一篇,我预计把这篇中用到的 Signature 说清楚,其实这个也很简单。固定的格式,固定的标签表示方式。期待,这几天系统无bug。解系统bug,真的力不从心。我原本只是写应用的而已…