0%

Android JNI开发步骤一

1. JNI中获取JavaVM和 JNIEnv

JavaVM是虚拟机在JNI中的表示,一个虚拟机中只有一个JavaVM对象,这个对象是线程共享的。
JNIEnv类型是一个指向全部JNI方法的指针。该指针只在创建它的线程有效,不能跨线程传递。多线程无法共享。
使用JNI_OnLoad方法,这个方法需要自己实现。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
jint JNI_OnLoad(JavaVM *vm,void *reserved){
LOGE("JNI_Onload in 1");
JNIEnv *env = NULL;
int result = -1;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
LOGE("JNI_Onload in 2");
return JNI_ERR;
}
result = register_method(env);//动态注册本地方法
savedVm = vm;//保存JavaVM ,供其他地方使用。
LOGE("JNI_Onload in 3 , result is %d", result);
return JNI_VERSION_1_6;
}

这个方法是在加载相应的.so包的时候,系统主动调用的,即:

1
2
3
static {
System.loadLibrary("native-lib");
}

这句代码调用时,JNI_OnLoad方法就会被调用。
每一个.so文件中,只能包含一个JNI_OnLoad方法,也就是在当前.so包含的文件中,只能有一个.c文件中实现这个JNI_OnLoad方法。
如上面的做法,我们可以在头文件中声明extern JavaVM *savedVm;,然后其他文件include这个头文件后,就可以使用这个JavaVM。

很多情况下,我们需要在其他文件中的一个多线程中使用JNIEnv,比如在线程中使用FindClass方法,这个时候,我们就可以通过JavaVM对象来获取JNIEnv,如下:

1
2
3
4
5
6
JNIEnv *env ;
if (savedVm->AttachCurrentThread(&env, 0) != 0)
{
LOGI("Failed to attach current thread");
return;
}

AttachCurrentThread表示当前线程与JVM进行关联,这样才能获取到JNIEnv对象;
这样我们就可以在多线程中使用JNIEnv对象,然后使用FindClass方法。当然,使用完了需要解除当前线程和虚拟机的关联,savedVm->DetachCurrentThread();,不然会报异常。
还可以在应用退出时,卸载当前虚拟机.

1
jint DestroyJavaVM(JavaVM* vm);

2、动态注册JNI方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
jint JNI_OnLoad(JavaVM *vm,void *reserved){
JNIEnv *env = NULL;
int result = -1;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
LOGE("JNI_Onload in 2");
return JNI_ERR;
}
**result = register_method(env);//动态注册本地方法**
savedVm = vm;//保存JavaVM ,供其他地方使用。
LOGE("JNI_Onload in 3 , result is %d", result);
return JNI_VERSION_1_6;
}
//这是本地方法的一个集合
JNINativeMethod methods[] = {
{"helloMethod","(Ljava/lang/String;)Ljava/lang/String;",(void*)helloMethod},
{"nativeCallJava","()V",(void*)nativeCallJava},
{"nativeCallJavaInThread","()V",(void*)nativeCallJavaInThread}
};
jint register_method(JNIEnv *env){
//开始注册方法
int result = registerNativeMethods(env,"com/xxxx/jni/MainActivity",methods, sizeof(methods) / sizeof(methods[0]));
return result;
}

jint registerNativeMethods(JNIEnv* env, const char *class_name, JNINativeMethod *methods, int num_methods) {
int result = 0;
//先根据类名找到类,这个类一般是用来存放所有的native方法
jclass clazz = env->FindClass(class_name);
if(clazz == NULL){
return JNI_FALSE;
}
//调用JNI方法,注册方法
result = env->RegisterNatives(clazz, methods, num_methods);
if(result < 0){
return JNI_FALSE;
}
return result;
}

JNINativeMethod结构体如下:

1
2
3
4
5
typedef struct {  
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;

第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了Java中函数的参数和返回值
第三个变量fnPtr是函数指针,指向native函数。前面都要接 (void *)
第一个变量与第三个变量是对应的,一个是java层方法名,对应着第三个参数>的native方法名字
关于第二个变量,表示的参数和返回值表示,可以看如下:

基本数据类型的转换
java native signature
char jchar C
byte jbyte B
short jshort S
int jint I
long jlong L
float jfloat F
double jdouble D
boolean jboolean Z
void void V
引用数据类型的转换
java native signature
所有对象 jobject L+classname +;
Class jclass Ljava/lang/Class;
String jstring Ljava/lang/String;
Throwable jthrowable Ljava/lang/Throwable;
Object[] jobjectArray [L+classname +;
byte[] jbyteArray [B
char[] jcharArray [C
double[] jdoubleArray [D
float[] jfloatArray [F
int[] jintArray [I
short[] jshortArray [S
long[] jlongArray [J
boolean[] jbooleanArray [Z

从上表可一看出,数组的JNI层数据类型需要以“Array”结尾,签名格式的开头都会有“[”。除了数组以外,其他的引用数据类型的签名格式都会以“;”结尾。

当然,我们也可以在应用退出时卸载本地方法。JNI_OnLoad方法是在动态库被加载时调用,而JNI_OnUnload则是在本地库被卸载时调用。所以这两个函数就是一个本地库最重要的两个生命周期方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int unRegisterNative(JNIEnv *env) {
jclass clazz = env->FindClass(“类名”);
if (clazz == NULL) {
return false;
}
return env->UnregisterNatives(clazz) ;
}

void JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return;
}
int ret = unRegisterNative(env);
}