JNI 开发是android NDK 开发的重要组成部分。 JNI 允许扩展android 底层代码,与android底层通信。
下面介绍一些本人对于JNI 开发的一些理解。
一 JNIEnv和JavaVM
JNI 开发离不开JNIEnv 和JavaVM。JavaVM是虚拟机在JNI层的代表,每一个虚拟机进程只有一个javaVM。也就是说, 对于我们的JNI 来说, JavaVM 是一个全局变量。
JNIEnv 是与线程有关的量,不同线程的JNIEnv彼此独立。
有些认为JNIEnv是Java调用其他语言(通常是C/C++)的环境。
JNI需要经常使使用指针javaVM 和JNIEnv 。但是在C与C++ 中的JavaVM 和JNIEnv 的定义和使用是不同的。
首先来看一下jni.h中javaVM 和JNIEnv的定义 。(androidNDK\android-ndk32-r10b-windows-x86_64\android-ndk-r10b\platforms\android-19\arch-arm\usr\include\jni.h)
可以看到
/*
* JNI invocation interface.
*/
struct JNIInvokeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
jint (*DestroyJavaVM)(JavaVM*);
jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
jint (*DetachCurrentThread)(JavaVM*);
jint (*GetEnv)(JavaVM*, void**, jint);
jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};
/*
* C++ version.
*/
struct _JavaVM {
const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
jint DestroyJavaVM()
{ return functions->DestroyJavaVM(this); }
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{ return functions->DetachCurrentThread(this); }
jint GetEnv(void** env, jint version)
{ return functions->GetEnv(this, env, version); }
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
对于
C,JavaVM 是个指向函数指针结构体的指针。 而对于C++,JavaVM 是一个 函数结构体。 JNIEnv 的定义与JavaVM 类似。
对于C语言,必须先对env和vm间接寻址。在调用方法时要将env 或者vm作为第一个参数。而C++则直接利用env和vm指针调用成员。
所以,JavaVM 和JNIEnv 的使用方法如下:
1. 对于C,
(*env)->方法名(env,参数列表)
(*vm)->方法名(vm,参数列表)
2. 对于C++
env->方法名(参数列表)
vm->方法名(参数列表)
C++中定义了__cplusplus,C语言中没有该定义。即:用__cplusplus来识别是C代码还是C++代码。
二 注册native函数
1. JNINativeMethod
JNINativeMethod是一个结构体。 Android系统中使用了一个映射表数据来注册和定义native函数的对应关系,并且描述了函数的参数和返回值。
而这个映射表的元素就是JNINativeMethod。
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
第一个变量name是Java文件中的native函数的名字,
第二个变量signature用字符串描述了native函数的参数和返回值
第三个变量是函数指针,指向了对应的C函数。
比如以下的结构体
static const JNINativeMethod nativeMethods[] =
{
{ "switchMode", "(IZ)Z",
(void *) Java_com_interfaces_EngineeringManager_switchMode },
{ "getSwitchMode", "(I)Z",
(void *) Java_com_interfaces_EngineeringManager_getSwitchMode },
};
函数 Java_com_interfaces_EngineeringManager_switchMode 对应于Java中的switchMode 方法。
其中第二列参数括号中描述了函数参数,括号后面代表返回值。
如"()V"就表示void Func();
"(II)V" 表示 void Func(int, int);
上面的(IZ)Z 表示 boolean switchMode(int, boolan);
具体的每一个字符的对应关系如下
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
[F
[B
[C
[D
[J
[Z
三 JNI_OnLoad()
实现JNI中本地函数的注册有两种方式,
1.采用默认的本地函数注册流程。
2.自己重写 JNI_Onload() 函数
当系统调用到System.loadLibrary()时,android的VM会执行C库中的JNI_Onload()函数。
JNI_OnLoad()函数的主要作用
1.设置JNI版本,默认是使用最老的版本。
2.获得初始的开发环境,JavaVM,JNIEnv*和jint result
static int register_EngineeringManager(
JNIEnv* env) {
jclass clazz = (*env)->FindClass(env, CLASS_PATCH);
if (clazz != NULL) {
if ((*env)->RegisterNatives(env, clazz, nativeMethods,
sizeof(nativeMethods) / sizeof(nativeMethods[0])) == JNI_OK) {
return JNI_OK;
}
}
return JNI_ERR;
}
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
LOGI("JNI_OnLoad");
jvm = vm;
JNIEnv* env = NULL;
jint result = JNI_ERR;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) == JNI_OK) {
if (NULL != env
&& register_EngineeringManager(
env) == JNI_OK) {
result = JNI_VERSION_1_6;
}
}
return result;
}
以上函数在JNI.h中都有定义。
jclass clazz = (*env)->FindClass(env, CLASS_PATCH);
Class_PATCH 是一个字符串, 对应Javaclass的路径。
#define CLASS_PATCH "com/interfaces/EngineeringManager"
EngineeringManager.java 中定义了nativemethods[] 中的Java 方法。
四 重要方法。
以下总结以下jni.h 中重要的方法, jni.h 定义了所有native 与java 交互的方法。
JavaVM* jvm 的方法。
JavaVM 的主要作用是获得JavaEnv。
1. jint (*GetEnv)(JavaVM*, void**, jint); 从java 虚拟机获得javaEnv。
第二个参数指向JavaEnv, 第三个参数为JNi 版本。
返回值小于0时说明没有得到JavaEnv。
2. jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*); 从当前线程获得javaEnv。
第三个参数指向线程名, 可以为空。
这两个方法经常一起使用, 当*GetEnv 方法得不到javaEnv 时, 从当前线程获得。
比如:
JavaVM* vm;
static JNIEnv *GetEnv()
{
int status;
JNIEnv *envnow = NULL;
status = g_JavaVM->GetEnv((void **)&envnow, JNI_VERSION_1_4);
if(status < 0)
{
status = g_JavaVM->AttachCurrentThread(&envnow, NULL);
if(status < 0)
{
return NULL;
}
}
return envnow;
}
JavaEnv 的方法
JNIEnv* env;
env 的主要作用其实也就是和java 层交互。
1. jclass (*FindClass)(JNIEnv*, const char*);
这个方法是要获得java 部分对应的类对象。 第二个参数是java类的路径。
比如:
jclass tmp = (*env)->FindClass(env, “com/interfaces/EngineeringInterfaces”);
temp 对应的是EngineeringInterfaces 类。
和此方法类似的还有方法 jclass (*GetObjectClass)(JNIEnv*, jobject);
GetObjectClass 是从java类的一个引用获得一个jclass 对象。
而FindClass 是从java类的路径获得一个jclass 对象。
2. jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
比如: jmethodID construction_id = (*env)->GetMethodID(env, tmp, "<init>", "()V");
获得对应类的构造函数的方法ID,“<init>” 表示构造函数, 第四个参数对应构造函数的参数和返回值。 此构造函数没有参数, 没有返回。
3. jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...);
在JNI 构造 对应 java 类的对象。省略号表示的是构造函数需要的参数。
例如
jobject newObject= (*env)->NewObject(env, tmp, construction_id );
就在JNI本地函数构造了EngineeringInterfaces 对象 。
下面就可以使用EngineeringInterfaces对象中的方法和变量了。
4. 首先 需要获得java 类中对应方法的jmethodID 和对应变量的jfieldID。
1)jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
这个方法是要获得类中方法的jmethodID。 第二个参数是类, 第三个参数对应于方法名。 第四个参数是方法的参数和返回值。
比如
mUpdateMethod = (*env)->GetMethodID(env, tmp, "dataChanged", "(ILjava/lang/String;)V");
mUpdateMethod 就对应于 EngineeringInterfaces.java 中的以下方法。
public void dataChanged(int , String );
注意, 找到的方法必须是public 的。
其实第二步获得构造函数也是用的这个方法。
2)方法: jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
用于获得Java 类中的对应变量的jfieldID, 第三个参数是变量名, 第三个参数是变量的类型
例如; jfieldID fieldID=(*env)->GetFieldID(env, tmp, "intField", I);
fieldID 对应于Java类中的 int intField;
5. 使用对应的方法
void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
这个方法的作用是调用java 对象中的方法。 其实也就是java 与native 通信的方法。调用的方法没有返回值
第二个参数是 其实就是java 对象,第三个参数是java 中调用方法的jmethodID。 以后省略的是传递给java 方法的参数。
比如: (*env)->CallVoidMethod(env, newObject, mUpdateMethod , method_id, update_data);
其实相当于调用方法:
dataChanged(method_id,update_data);
与此方法类似, 还有一个调用Java中静态方法方式
void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
类似还有
1)jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...); 方法返回boolean
2) jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...); 返回byte
3) jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...); 返回char
4) jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); 返回int
5) jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...) 返回long
6) jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...); 返回对象
6 获得对应的变量
jint (*GetIntField)(JNIEnv*, jobject, jfieldID);
第二个参数是对应的对象, 第三个参数是变量的jfieldID。
例如:jint intFied=(*env)->GetIntField(env,newObject, fieldID)
获得是intField 值
相应还有类似方法
jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);
jboolean (*GetBooleanField)(JNIEnv*, jobject, jfieldID);
jbyte (*GetByteField)(JNIEnv*, jobject, jfieldID);
jchar (*GetCharField)(JNIEnv*, jobject, jfieldID);
jshort (*GetShortField)(JNIEnv*, jobject, jfieldID);
jint (*GetIntField)(JNIEnv*, jobject, jfieldID);
jlong (*GetLongField)(JNIEnv*, jobject, jfieldID);
jfloat (*GetFloatField)(JNIEnv*, jobject, jfieldID) __NDK_FPABI__;
jdouble (*GetDoubleField)(JNIEnv*, jobject, jfieldID) __NDK_FPABI__;
7. 设置变量
void (*SetIntField)(JNIEnv*, jobject, jfieldID, jint);
第二个参数是对应的对象, 第三个参数是变量的jfieldID, 第四个参数为设置的值。
例如:(*env)->SetIntField(env,newObject, fieldID, jint result);
相当于 intField = result;
类似,还有相应的其他设置方法
void (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject);
void (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean);
void (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte);
void (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar);
void (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort);
void (*SetIntField)(JNIEnv*, jobject, jfieldID, jint);
void (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong);
void (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat) __NDK_FPABI__;
void (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble) __NDK_FPABI__;