JNI笔记

手动创建jni工程

可以参考 https://www.jianshu.com/p/2ff3f94328f6

加载Native 库

1
2
System.loadLibrary("native-lib");//
System.load("C:\\native-lib.so");//需要指定详细路径

JNI_OnLoad

调用System.loadLibrary()函数时, 内部就会去查找so中的 JNI_OnLoad 函数,如果存在此函数则调用。JNI_OnLoad会:告诉 VM 此 native 组件使用的 JNI 版本。对应了Java版本,android中只支持JNI_VERSION_1_2 、JNI_VERSION_1_4、JNI_VERSION_1_6

1
2
3
4
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
return JNI_VERSION_1_6;
}

JNI_OnUnload

虚拟机释放该C库时,调用JNI_OnUnload()函数

1
2
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved){
}

jni数据类型

Java类型 本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组

静态注册

参数说明:

JNIEXPORT :声明jni标志(固定)

jint: 方法返回值

JNICALL :表示要jni要调用方法的标志(固定)

Java_com_hj_jni_MainActivitytest :(具体方法名 格式: Java 包名 类名 方法名)

JNIEnv: 由Jvm传入与线程相关的变量。定义了JNI系统操作、java交互等方法。(固定)

jobject: 表示当前调用对象,即 this , 如果是静态的native方法,则获得jclass (固定)

jintArray b_, jobjectArray:方法具体传的参数

类Unix系统中JNICALL JNIEXPORT 可以省略不加

extern “C”的主要作用就是为了能够正确实现C++代码调用其他C语言代码

1
2
3
4
extern "C"
extern jint a;//使用其它文件的变量
JNIEXPORT jint JNICALL
Java_com_hj_jni_MainActivity_test(JNIEnv *env, jobject thiz, jintArray b_, jobjectArray a_) {}

动态注册

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
//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/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,sizeof(mMethods)/sizeof(JNINativeMethod));
if(r != JNI_OK )
{
return -1;
}
return JNI_VERSION_1_6;
}

获取参数

基本数据类型

jboolean jbyte jchar jshort jint jlong jfloat jdouble 这些直接拿来用即可

1
2
//创建java字符串
jstring s=env->NewStringUTF("哈哈");

##String获取

字符串对比 strcmp(const char __lhs, const char __rhs) 字符串对比 若str1=str2,则返回零;若str1str2,则返回正数

GetStringUTFChars 第二个参数isCopy 表示是否原始字符串java.lang.String的一份拷贝, 在Java规定字符串不可变的规则 所以返回肯定是true 一般传NULL即可。

1
2
3
4
5
.......(JNIEnv *env, jobject thiz, jstring a) {
const char* str = env->GetStringUTFChars(a, NULL);
env->ReleaseStringUTFChars(a,str);//释放字符串
}

获取基本数据类型数组

0
原始数据: 对象数组将不会被限制.
拷贝数据: 数据将会拷贝回原始数据, 同时释放拷贝数据.
JNI_COMMIT
原始数据: 什么都不作.
拷贝数据: 数据将会拷贝回原始数据, 不释放拷贝数据.
JNI_ABORT
原始数据: 对象数组将不会被限制, 之前的数据操作有效..
拷贝数据: 释放拷贝数据, 之前的任何数据操作会丢弃.

1
2
3
4
5
6
7
8
9
10
11
12
//传入参数 JNIEnv *env, jobject thiz, jintArray b_
//2、获得基本数据类型数组
int32_t int_length = env->GetArrayLength(b_);
//LOGE("int 数组长度:%d",int_length);
//对应的有 GetBoolean 、GetFloat等
jint *b = env->GetIntArrayElements(b_, NULL);
for (int i = 0; i < int_length; i++) {
LOGE("int 数据有:%d",b[i]);
}
env->ReleaseIntArrayElements(b_, b, 0);//释放 第三个参数是模式 0是把修改的后的值刷新java数组并释放c数组 1只刷新java数组不释放c数组 2只释放c数组

获取其它类型数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//传入参数 JNIEnv *env, jobject thiz, jobjectArray a_
int32_t str_length = env->GetArrayLength(a_);
//获得数组的长度
for (int i = 0; i < str_length; ++i) {
//jobject jobject1=env->GetObjectArrayElement(a_, i);
// jclass clazz = env->GetObjectClass(jobject1); 可以通过反射来获取java对象信息
jstring str = static_cast<jstring>(env->GetObjectArrayElement(a_, i));
const char* c_str = env->GetStringUTFChars(str, NULL);
LOGE("字符串有:%s",c_str);
//使用完释放
env->ReleaseStringUTFChars(str,c_str);
}

C/CPP反射Java对象

方法 描述
jclass FindClass(JNIEnv env, const char name) 返回 Java 类
jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, …); 构造新 Java 对象。方法 ID指示应调用的构造函数方法。该 ID 必须通过调用 GetMethodID() 获得,且调用时的方法名必须为“ < init >”
jclass GetObjectClass(JNIEnv *env, jobject obj) 返回 Java 类
jfieldID GetFieldID(JNIEnv env, jclass clazz, const char name, const char *sig) 返回 Java 类对象属性的ID name 属性名 sig 属性签名
jmethodID GetMethodID(JNIEnv env, jclass clazz, const char name, const char *sig) 返回 Java 类对象方法的ID name 方法名 sig 方法签名
jfieldID GetStaticFieldID(JNIEnv env, jclass clazz, const char name, const char *sig) 返回 Java 类对象静态属性的ID name 属性名 sig 属性签名
jmethodID GetStaticMethodID(JNIEnv env, jclass clazz, const char name, const char *sig) 返回 Java 类对象静态方法的ID name 方法名 sig 方法签名



获取java传过来的对象信息

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
Java_com_hj_jni_MainActivity_globalReference(JNIEnv *env, jobject thiz, jstring a, jobject b) {
jclass clazz = env->GetObjectClass(b);
//参数 class定义 属性名 属性类型
jfieldID iId = env->GetFieldID(clazz,"i","I");
jint i=env->GetIntField(b,iId);
LOGE("获取到i=%d",i);
//参数 class定义 方法名 方法参数定义
jmethodID getIId = env->GetMethodID(clazz,"getI","()I");
jint i2=env->CallIntMethod(b,getIId);
LOGE("获取到i2=%d",i2);
jmethodID setIId = env->GetMethodID(clazz,"setI","(I)V");
jint setI=9;
env->CallVoidMethod(b,setIId,setI);
jmethodID printId = env->GetStaticMethodID(clazz,"print","()V");
env->CallStaticVoidMethod(clazz,printId);
jstring s=env->NewStringUTF("哈哈");
env->DeleteLocalRef(s);//删除局部引用
env->DeleteLocalRef(clazz);
}

创建java对象

1
2
3
4
5
6
7
8
9
10
11
12
extern "C"
JNIEXPORT jobject JNICALL
Java_com_hj_jni_MainActivity_newObject(JNIEnv *env, jobject thiz) {
jclass beanClass=env->FindClass("com/hj/jni/Bean");
//创建构造方法
jmethodID constuct=env->GetMethodID(beanClass,"<init>","()V");
//生成对象
jobject beanJobject=env->NewObject(beanClass,constuct);
jfieldID iId = env->GetFieldID(beanClass,"i","I");
env->SetIntField(beanJobject,iId,3);
return beanJobject;
}

引用

方法 描述
jobject NewGlobalRef(JNIEnv *env, jobject obj); 创建全局引用 obj:全局或局部引用 系统内存不足则返回 NULL
void DeleteLocalRef(JNIEnv *env, jobject localRef) 删除 localRef 所指向的局部引用 localRef:局部引用
void DeleteGlobalRef(JNIEnv *env, jobject globalRef) 删除 globalRef 所指向的全局引用globalRef:全局引用

局部引用

大多数JNI函数会创建局部引用。NewObject/FindClass/NewStringUTF 等等都是局部引用。

局部引用只有在创建它的本地方法返回前有效,本地方法返回后,局部引用会被自动释放。

因此无法跨线程、跨方法使用。

释放一个局部引用有两种方式:

1、本地方法执行完毕后VM自动释放;

2、通过DeleteLocalRef手动释放;

VM会自动释放局部引用,为什么还需要手动释放呢?

因为局部引用会阻止它所引用的对象被GC回收。

1
2
3
4
5
6
7
8
9
10
11
12
extern "C"
JNIEXPORT jstring JNICALL
xxx(JNIEnv *env, jobject instance) {
//错误
//不能在本地方法中把局部引用存储在静态变量中缓存起来供下一次调用时使用。
// 第二次执行 str依然有值,但是其引用的 “C++字符串” 已经被释放
static jstring str;
if(str == NULL){
str = env->NewStringUTF("C++字符串");
}
return str;
}

全局引用

全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效 。

由 NewGlobalRef 函数创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extern "C"
JNIEXPORT jstring JNICALL
xxx(JNIEnv *env, jobject instance) {
//正确
static jstring globalStr;
if(globalStr == NULL){
jstring str = env->NewStringUTF("C++字符串");
//删除全局引用调用 DeleteGlobalRef
globalStr = static_cast<jstring>(env->NewGlobalRef(str));
//可以释放,因为有了一个全局引用使用str,局部str也不会使用了
env->DeleteLocalRef(str);
}
return globalStr;
}

弱引用

与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止GC回收它所指向的VM内部的对象 。

在对Class进行弱引用是非常合适(FindClass),因为Class一般直到程序进程结束才会卸载。

在使用弱引用时,必须先检查缓存过的弱引用是指向活动的对象,还是指向一个已经被GC的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
extern "C"
JNIEXPORT jclass JNICALL
xxx(JNIEnv *env, jobject instance) {
static jclass globalClazz = NULL;
//对于弱引用 如果引用的对象被回收返回 true,否则为false
//对于局部和全局引用则判断是否引用java的null对象
jboolean isEqual = env->IsSameObject(globalClazz, NULL);
if (globalClazz == NULL || isEqual) {
jclass clazz = env->GetObjectClass(instance);
//删除使用 DeleteWeakGlobalRef
globalClazz = static_cast<jclass>(env->NewWeakGlobalRef(clazz));
env->DeleteLocalRef(clazz);
}
return globalClazz;
}

线程

JNI 接口指针 (JNIEnv) 仅在当前线程中有效。如果另一个线程需要访问 Java 虚拟机,则该线程首先必须调用 AttachCurrentThread() 以将自身连接到虚拟机并且获得 JNI 接口指针。连接到虚拟机之后,本地线程的工作方式就与在本地方法内运行的普通 Java 线程一样了。本地线程保持与虚拟机的连接,直到调用 DetachCurrentThread() 时才断开连接。

方法 说明
AttachCurrentThread(JNIEnv* p _ env, void thr_args) 将当前线程连接到 Java 虚拟机 成功时返回“0”;失败则返回负数。
DetachCurrentThread(JavaVM *vm) 断开当前线程与 Java 虚拟机之间的连接
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
39
40
41
42
43
44
45
46
47
48
JavaVM* _vm = 0;
jobject _instance = 0;
jint JNI_OnLoad(JavaVM* vm, void* reserved){
_vm = vm;
return JNI_VERSION_1_4;
}
void *task(void *args){
JNIEnv *env;
//将本地当前线程附加到jvm,并获得jnienv
//成功则返回0
_vm->AttachCurrentThread(&env,0);
jclass clazz = env->GetObjectClass(_instance);
//获得具体的静态方法 参数3:方法签名
//如果不会填 可以使用javap
jmethodID staticMethod = env->GetStaticMethodID(clazz,"staticMethod","(Ljava/lang/String;IZ)V");
//调用静态方法
jstring staticStr= env->NewStringUTF("C++调用静态方法");
env->CallStaticVoidMethod(clazz,staticMethod,staticStr,1,1);
//获得构造方法
jmethodID constructMethod = env->GetMethodID(clazz,"<init>","()V");
//创建对象
jobject helper = env->NewObject(clazz,constructMethod);
jmethodID instanceMethod = env->GetMethodID(clazz,"instanceMethod","(Ljava/lang/String;IZ)V");
jstring instanceStr= env->NewStringUTF("C++调用实例方法");
env->CallVoidMethod(helper,instanceMethod,instanceStr,2,0);
//释放
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(staticStr);
env->DeleteLocalRef(instanceStr);
env->DeleteLocalRef(helper);
//分离
_vm->DetachCurrentThread();
return 0;
}
//Helper 类方法
extern "C"
JNIEXPORT void JNICALL
Java_com_dongnao_jnitest_Helper_nativeThread(JNIEnv *env, jobject instance) {
pthread_t pid;
//这里注意释放
_instance = env->NewGlobalRef(instance);
pthread_create(&pid,0,task,0);
}