Jni开发知识点

JavaVM和JNIEnv

jni定义了两个关键的结构体,JavaVMJNIEnv,两者都拥有指向方法表的指针,在c++中,两者是拥有指向方法表和一系列jni方法的类,Android developer中说是类,实际开发中在jni.h中发现是结构体)。
javavm提供invocation interface方法,可以创建和销毁javavm,理论上每个进程可以有多个javavm,但是Android中只允许有一个。
jnienv提供大部分的jni方法,native方法的第一个参数就是jnienvjnienv使用个threadlocal存储,因此线程间无法共享。如果某一段代码中无法获得jnienv,可以共享javavm并调用GetEnv来获得当前线程的jnienv,如果当前线程没有,可以使用AttachCurrentThread创建。

注意:c和c++关于两者的声明不同,如果头文件中定义了#indef _cplusplus,那么要注意自己引用的时哪个版本。

线程

所有的线程都是linux线程,有kernal管理,可以自己创建线程,并通过JNI AttachCurrentThread or AttachCurrentThreadAsDaemon方法与jni关联,没有关联的话线程无法进行jni call。如果线程已经关联,那么调用AttachCurrentThread是空操作。当把一个线程Attach到javavm时,java中会创建一个thread,并添加到main threadGroup。android不会暂停正在执行native code的线程,只会在下一次执行jni call时暂停线程。通过jni关联的线程在退出前必须调用DetachCurrentThread,也可以使用pthread_key_create并构造新的析构函数来实现相同效果,在析构函数中调用该方法。使用pthread_setspecific存储jnienvthreadlocal,这样析构函数中传入jnienv参数。

引用

类的引用、field IDsmethod IDs 在类unload之前都是可用的。类的unload操作很少见,但并不是不可能。
注意:尽管jclass是类的引用,但jclass必须通过NewGlobalRef的操作来保护起来。

native code中,对class的操作较为耗时,比如获得成员变量,回调方法,这些操作都要先获得fieldIdmethodId,从性能角度看,最好将这些id缓存下来。考虑到java的类有可能会有unload,reload的过程,可以使用以下方式在每次加载类的时候缓存id

1
2
3
4
5
6
7
8
9
10
/*
* We use a class initializer to allow the native code to cache some
* field offsets. This native function looks up and caches interesting
* class/field/method IDs. Throws on failure.
*/
private static native void nativeInit();

static {
nativeInit();
}

这样,在一个类每次加载时都会调用nativeInit,在native层相应实现方法即可。

疑问:不同类的加载时线程问题

局部引用和全局引用

传递给native方法传递的参数和大部分jni返回的参数都是局部引用,这意味着这些引用在当前的native方法中可见。及时对象在本地方法返回后仍然存活,引用也是不可见的。获得全局引用的唯一方法是NewGlobalRefNewWeakGlobalRef.常用的模式是

1
2
3
jclass localClass = env->FindClass("MyClass");
//reinterpret_cast c++强制类型转换
jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));

jni方法接受局部和全局引用作为参数。
指向同一对象的多个引用可能值并不相等。比如,连续多次调用NewGlobalRef的结果可能不同,因此在jni中,不能用==来判断引用是否相等,而要使用IsSameObject。因此,native中,不能假设对象的引用是不变的。

jni中,不能过多的分配局部引用,如果创建过多局部引用,要手动DeleteLocalRef(一般情况这个操作由jni自己完成)。实际中最多保留十六个局部引用,如果要更多,使用EnsureLocalCapacity/PushLocalFrame

注意:jfieldIDs and jmethodIDs不是object引用,不能更改为GlobalRef。

如果调用AttachCurrentThread,运行中的代码不会自动释放局部引用知道线程结束,需要手动释放。

基本类型数组

此部分需要写代码进行验证

基本类型数组可以直接进行读写操作,因为他们是由c定义的。
有一组函数用于对基本类型数组操作Get<PrimitiveType>ArrayElements,这些函数有的返回指针,有的返回真实的元素,有的分配内存并进行拷贝。不管哪种方式,返回的原始的指针在释放前都是可见的。也就是说,如果数据没有被拷贝,数组对象就不会被释放。我们必须释放所有的数组。
是否进行拷贝是通过get系列函数的iscopy来确定的。
release调用时传入mode参数:
mode:
0:
源数据: 释放
copy数据:data is copied back. The buffer with the copy is freed.

JNI_COMMIT
源数据: 无操作
copy数据:data is copied back. The buffer with the copy is not freed.

JNI_ABORT
源数据: the array object is un-pinned. Earlier writes are not aborted.
copy数据:the buffer with the copy is freed; any changes to it are lost.

JNI_COMMIT不会释放元数据,需要使用其他参数再次调用release

关于isCopy,检查是否进行了拷贝很有用,如果对数组进行了修改,这可以让我们确定自己是否需要调用带有JNI_COMMIT的release方法。

Region Calls
如果仅仅是为了将数据拷贝出来或者拷贝进去,有更方便的方法:
先看以前的方法:

1
2
3
4
5
jbyte* data = env->GetByteArrayElements(array, NULL);
if (data != NULL) {
memcpy(buffer, data, len);
env->ReleaseByteArrayElements(array, data, JNI_ABORT);
}

方便的方法,还能避免忘记release,还会避免多余的拷贝
env->GetByteArrayRegion(array, 0, len, buffer);

异常
很多jni方法会抛出异常。比如CallObjectMethod,这时需要check异常,因为该函数的返回值可能不可用。android还不支持c++的异常,jni的Throw和throwNew会在当前线程抛出一个java异常。native方法中,通过ExceptionCheckExceptionOccurred捕获异常。通过ExceptionClear清理异常。

关于JNIEnv

c style:
JNIEnv是一个名为JNINativeInterface的结构体,里面包含了一系列方法的指针用于jni的交互,如

1
2
3
4
5
6
7
8
9
10
11
12
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;

jint (*GetVersion)(JNIEnv *);

jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
jsize);
jclass (*FindClass)(JNIEnv*, const char*);
.....

c++ style:将c的JNINativeInterface结构体进行封装,变为名为_JNIEnv的结构体,并对JNINativeInterface的方法进行封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* C++ object wrapper.
*
* This is usually overlaid on a C struct whose first element is a
* JNINativeInterface*. We rely somewhat on compiler behavior.
*/
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;

#if defined(__cplusplus)

jint GetVersion()
{ return functions->GetVersion(this); }

jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
jsize bufLen)
{ return functions->DefineClass(this, name, loader, buf, bufLen); }

jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
.....

关于JavaVM

c style:

1
2
3
4
5
6
7
8
9
10
11
12
//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++ style:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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); }
};

关于引用数据类型

c style:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* Reference types, in C.
*/
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;

可见,在c中,由无类型指针void*引申出一系列的jobject

c++ style:

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
/*
* Reference types, in C++
*/
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};

typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;

在c++中,jobject是一个类

基本数据类型:

1
2
3
4
5
6
7
8
9
10
11
12
/* Primitive types that match up with Java equivalents. */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */

/* "cardinal indices and sizes" */
typedef jint jsize;

可见,jni的基本类型与c中基本类型一样,所以可以直接转换,且不用考虑释放问题。

方法的类型签名:

jni使用java虚拟机的类型签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Type Signature  				Java Type
Z boolean
B byte
C char
S short
I int
J long
F float
D double
Lfully-qualified-class; fully-qualified-class
[type type[]
(arg-types)ret-type method type


long f (int n, String s, int[] arr);
的签名是:
(ILjava/lang/String;[I)J

关于native向java回调

在回调时经常会碰到多个不同的方法的类型签名,为避免重复代码和方便,可以使用以下方法。避免直接传递基本类型,全部使用jobject作为参数,基本类型使用其封装类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void callBackJavaUniversal(JavaVM *gJavaVM, jobject targetObj, string     nameStr, string sigStr,jobject param1, jobject param2) {
char *name = (char *) nameStr.data();
char *sig = (char *) sigStr.data();
JNIEnv *env = 0;
if (gJavaVM != NULL) {
gJavaVM->AttachCurrentThread(&env, 0);
}
if (env == NULL){
return;
}
jclass javaClass = env->GetObjectClass(targetObj);
jmethodID javaCallback = env->GetMethodID(javaClass, name, sig);
if (param1 == NULL) {
env->CallVoidMethod(targetObj, javaCallback);
} else if (param2 == NULL){
env->CallVoidMethod(targetObj, javaCallback, param1);
} else {
env->CallVoidMethod(targetObj, javaCallback, param1, param2);
}
env->DeleteLocalRef(javaClass);
}