JNI的开发中,Java层的方法和C/C++层的函数之间的对应关系是通过注册来实现的,要不然它怎么知道java的方法到了c/c++去找哪个对应的方法呢?JNI的方法注册分为静态注册和动态注册。
3.1 静态注册
静态注册使用Java_PACKAGENAME_CLASSNAME_METHODNAME 来进行与java方法的匹配
一般步骤如下:
1)编写java类,假如是JniTest.java
2)在命令行下输入 javac JniTest.java 生成JniTest.class文件
3) 在 JniTest.class 目录下 通过 javah xxx.JniTest(全类名)生成 xxx_JniTest.h 头文件
4)编写xxx_JniTest.c 源文件,并拷贝xxx_JniTest.h 下的函数,并实现这些函数,且在其中添加jni.h头文件;
5)编写 cmakelist.txt 文件,编译生成动态/静态链接库
例如,下面就是静态注册的cpp文件的大概的模板:
#include <jni.h>
#include <string>
#include <iostream>
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_ndk_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_ndk_MainActivity_sayHello(
JNIEnv *env,
jclass clazz,
jstring str) {
const char* helloText = env->GetStringUTFChars(str, JNI_FALSE);
std::string str1 = "I receive your words:";
std::string result = str1 + helloText;
env->ReleaseStringUTFChars(str, helloText);
return env->NewStringUTF(result.c_str());
}
extern "C" JNIEXPORT jstring JNICALL是固定的,其中extern "c"的是意思是告诉编译器按照C语言的方式去编译函数,这是为了C/C++混合编程,JNICALL是表示这是一个被JNI调用的函数,Java+方法的全路径名就是静态注册的方法名。
JNI的方法有两个固定参数:
1. 如果java层是普通方法,前面的两个参数固定为(JNIEnv *env, jobject thiz)
2. 如果java层是静态方法,前面的两个参数固定为 (JNIEnv *env, jclass clazz)
JNIEnv表示是JNI的上下文环境,它里面封装了很多JNI的方法,jobject表示的java层调用的这个对象的this指针,jclass表示的java的类,因为静态方法不属于某个对象,而是属于类。
3.2. 动态注册
我们在上面代码中使用的 Java_PACKAGENAME_CLASSNAME_METHODNAME 来进行与java方法的匹配,这
种方式我们称之为静态注册。而动态注册则意味着方法名可以不用这么长了,在android aosp源码中就大量的使用了动态注册的形式
//Java:
native void dynamicNative();
native String dynamicNative(int i);
//C++:
void dynamicNative1(JNIEnv *env, jobject jobj){
LOGE("dynamicNative1 动态注册");
}
jstring dynamicNative2(JNIEnv *env, jobject jobj,jint i){
return env->NewStringUTF("我是动态注册的dynamicNative2方法");
}
//需要动态注册的方法数组
static const JNINativeMethod mMethods[] = {
{"dynamicNative","()V", (void *)dynamicNative1},
{"dynamicNative", "(I)Ljava/lang/String;", (jstring *)dynamicNative2}
};
//需要动态注册native方法的类名
static const char* mClassName = "com/dongnao/jnitest/MainActivity";
jint JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv* env = NULL;
//获得 JniEnv
int r = vm->GetEnv((void**) &env, JNI_VERSION_1_4);
if( r != JNI_OK){
return -1;
}
jclass mainActivityCls = env->FindClass( mClassName);
// 注册 如果小于0则注册失败
r = env->RegisterNatives(mainActivityCls,mMethods,2);
if(r != JNI_OK ) {
return -1;
}
return JNI_VERSION_1_4;
}
JNI_OnLoad方法是在我们调用System.loadLibrary的时候调用的,这相当于在加载so库的时候,把我们的java方法和JNI层的方法做了一个映射,这样就不需要写一串很长的方法名了,使用也灵活。
3.3 system.load()/system.loadLibrary() 区别
最后说一下 system.load()/system.loadLibrary()的区别
System.load
System.load 参数必须为库文件的绝对路径,可以是任意路径,例如: System.load("C:\Documents and
Settings\TestJNI.dll"); //Windows
System.load("/usr/lib/TestJNI.so"); //Linux
System.loadLibrary
System.loadLibrary 参数为库文件名,不包含库文件的扩展名。
System.loadLibrary ("TestJNI"); //加载Windows下的TestJNI.dll本地库
System.loadLibrary ("TestJNI"); //加载Linux下的libTestJNI.so本地库