简介
JNI是Java Native Interface(Java本地接口)的缩写,JNI不是Android专有,而是从Java继承而来。Android作为一种嵌入式操作系统,大量个驱动、硬件相关的功能底层功能都必须在native层实现,JNI的作用和重要性大大增强。
使用场景
- 使用Native层API
- 复用已有的C/C++库
- 性能要求高
- 跨平台
- 安全
helloworld
定义:实现一个jni函数,输入int数组,输出逗号拼接的字符串
- java类中声明native方法
package com.jack.hellojniworld; public class MainActivity extends AppCompatActivity { // Used to load the 'hello_jni_world-lib' library on application startup. static { System.loadLibrary("hello_jni_world"); } public native String jniIntArrayToString(int[] intParams); }
- Terminal中使用命令生成头文件,并把头文件拷贝至cpp目录
~~~/Users/jack.zhou/soft/project/HelloJniWorld jackzhoudeMacBook-Pro:HelloJniWorld jack.zhou$ cd app/src/main/java/ jackzhoudeMacBook-Pro:java jack.zhou$ javah -jni com.jack.hellojniworld.MainActivity jackzhoudeMacBook-Pro:java jack.zhou$ ls com com_jack_hellojniworld_MainActivity.h jackzhoudeMacBook-Pro:java jack.zhou$ cat com_jack_hellojniworld_MainActivity.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_jack_hellojniworld_MainActivity */ #ifndef _Included_com_jack_hellojniworld_MainActivity #define _Included_com_jack_hellojniworld_MainActivity #ifdef __cplusplus extern "C" { #endif /* * Class: com_jack_hellojniworld_MainActivity * Method: jniIntArrayToString * Signature: ([I)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_jack_hellojniworld_MainActivity_jniIntArrayToString (JNIEnv *, jobject, jintArray); #ifdef __cplusplus } #endif #endif
- 在cpp下新建com_jack_hellojniworld_MainActivity.cpp,添加实现逻辑
#include "com_jack_hellojniworld_MainActivity.h" #include "string" using namespace std; /** * [1,2,3] -> 1,2,3 * * @param env JNIEnv * @param intArrayParams 输入的int数组 * @return 用逗号拼接的字符串 */ extern "C" jstring Java_com_jack_hellojniworld_MainActivity_jniIntArrayToString(JNIEnv *env, jobject, jintArray intArrayParams) { string ret; int paramCount = env->GetArrayLength(intArrayParams); jint *paramValues = env->GetIntArrayElements(intArrayParams, nullptr); for (int i = 0; i < paramCount; ++i) { jint *curParamValue = (paramValues + i); ret += to_string(*curParamValue); if (i != paramCount - 1) { ret += ","; } } env->ReleaseIntArrayElements(intArrayParams, paramValues, 0); return env->NewStringUTF(ret.c_str()); }
- 总结
- JNI定义了一套自己的数据类型,包括原始类型和引用类型,定义在jni.h。
/* 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 */ /*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;
- JNIEnv中定义了一套与native层数据交互的函数
struct _JNIEnv { /* do not rename this; it does not seem to be entirely opaque */ const struct JNINativeInterface* functions; 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); } ... }
- JNI定义了一套自己的数据类型,包括原始类型和引用类型,定义在jni.h。
Native层操作Java对象
1.定义java类
public class JniInvokeTestClass {
private String mStringField = "defaultStr";
private static String sStringStaticField = "defaultStaticStr";
public JniInvokeTestClass() {
Log.d(MainActivity.TAG, "non param constructor invoked!");
}
public JniInvokeTestClass(String stringField) {
Log.d(MainActivity.TAG, "param constructor invoked! param:" + stringField);
this.mStringField = stringField;
}
public void print() {
Log.d(MainActivity.TAG, "print invoke!mStringField:" + mStringField);
}
public static void staticPrint() {
Log.d(MainActivity.TAG, "printStringStaticField invoke!sStringStaticField:" + sStringStaticField);
}
public void setStringField(String mStringField) {
this.mStringField = mStringField;
}
public static void setStringStaticField(String sStringStaticField) {
JniInvokeTestClass.sStringStaticField = sStringStaticField;
}
}
- java层调用
public void testJniObjectToObject() {
Log.d(TAG, "testJniObjectToObject start");
JniInvokeTestClass jniInvokeTestClass = new JniInvokeTestClass("java common field");
JniInvokeTestClass.setStringStaticField("java static field");
JniInvokeTestClass ret = jniObjectToObject(jniInvokeTestClass);
Log.d(TAG, "testJniObjectToObject ret is same object? "+(jniInvokeTestClass == ret));
}
public native JniInvokeTestClass jniObjectToObject(JniInvokeTestClass params);
- native层调用对象
/**
*
* @param env JNIEnv
* @param object 调用native方法的对象,实际上是MainActivity
* @param param JniInvokeTestClass对象
*/
extern "C" jobject
Java_com_jack_hellojniworld_MainActivity_jniObjectToObject(JNIEnv *env, jobject object,
jobject param) {
jclass jniInvokeTestClassClazz = env->GetObjectClass(param);
//获取字段ID
jfieldID stringFieldId = env->GetFieldID(jniInvokeTestClassClazz, "mStringField",
"Ljava/lang/String;");
jfieldID stringStaticFieldId = env->GetStaticFieldID(jniInvokeTestClassClazz,
"sStringStaticField",
"Ljava/lang/String;");
//获取方法ID
jmethodID printMethodId = env->GetMethodID(jniInvokeTestClassClazz, "print", "()V");
jmethodID staticPrintMethodId = env->GetStaticMethodID(jniInvokeTestClassClazz, "staticPrint",
"()V");
//获取字段值
jstring stringFieldValue = static_cast<jstring>(env->GetObjectField(param, stringFieldId));
jstring staticStringFieldValue = static_cast<jstring>(env->GetStaticObjectField(
jniInvokeTestClassClazz, stringStaticFieldId));
LOGD("receive param field value...");
logd(env, "stringFieldValue:%s", stringFieldValue);
logd(env, "staticStringFieldValue:%s", staticStringFieldValue);
//设置字段值
env->SetObjectField(param, stringFieldId, env->NewStringUTF("jni common field"));
env->SetStaticObjectField(jniInvokeTestClassClazz, stringStaticFieldId,
env->NewStringUTF("jni static field"));
LOGD("modify field value done!");
//调用方法
env->CallVoidMethod(param, printMethodId);
env->CallStaticVoidMethod(jniInvokeTestClassClazz, staticPrintMethodId);
//构造新实例,并打印
LOGD("constructor new object!");
jmethodID constructorMethodId = env->GetMethodID(jniInvokeTestClassClazz, "<init>", "()V");
jobject nJniInvokeTestObject = env->NewObject(jniInvokeTestClassClazz, constructorMethodId);
env->CallVoidMethod(nJniInvokeTestObject, printMethodId);
env->CallStaticVoidMethod(jniInvokeTestClassClazz, staticPrintMethodId);
return nJniInvokeTestObject;
}
4.运行
-
访问字段/方法,需要先通过名字和签名获取对象的FieldID和MethodID,再通过Get<field_type>Field/Set<field_type>Field/GetStatic<field_type>Field/SetStatic<field_type>Field读取和修改属性,通过Call<Return_Type>Method/CallStatic<Return_Type>Method调用方法。-访问字段/方法,需要先通过名字和签名获取对象的FieldID和MethodID,再通过Get<field_type>Field/Set<field_type>Field/GetStatic<field_type>Field/SetStatic<field_type>Field读取和修改属性,通过Call<Return_Type>Method/CallStatic<Return_Type>Method调用方法。
-
签名
boolean字段签名:Z;
int数组字段签名:[I;
对象类型字段签名:Ljava/lang/String;
(I)V: 一个整型参数,无返回值
([ILjava/lang/String)Z: 参数1整型数值,参数2字符类型,返回boolean
异常处理
JniEnv定义的异常处理函数:
jboolean ExceptionCheck();//检查是否发生异常
jthrowable ExceptionOccurred()//返回异常(和java层Throwable不相同,java层try-catch不能捕获)或者null
void ExceptionDescribe();//logcat日志中打印异常信息
void ExceptionClear();//清除异常,表明异常已被处理
jint Throw(jthrowable obj);
jint ThrowNew(jclass clazz, const char* message)//抛出java异常
demo
jboolean Java_com_jack_hellojniworld_MainActivity_jniHandleException(JNIEnv *env, jobject object) {
LOGD("jniHandleException!");
jclass clazz = env->FindClass("java/lang/XString");
if(env->ExceptionCheck() == JNI_TRUE){
LOGD("ExceptionOccurred!");
env->ExceptionDescribe();
//clear之后标志异常已被处理
//不清除,JNI会正常返回,java层可以try-catch
env->ExceptionClear();
return JNI_FALSE;
}
return JNI_TRUE;
}
void Java_com_jack_hellojniworld_MainActivity_jniThrowException(JNIEnv *env, jobject object) {
LOGD("jniThrowException!");
jclass clazz = env->FindClass("java/lang/XString");
jthrowable throwable = env->ExceptionOccurred();
if(throwable != nullptr){
LOGD("ExceptionOccurred!");
//不能直接抛出,会直接crash,外层java无法try-catch
//需要重新创建并抛出java层异常
//env->Throw(throwable);
env->ExceptionClear();
jclass throwClazz = env->FindClass("java/lang/RuntimeException");
env->ThrowNew(throwClazz, "jni ThrowNew");
}
}
JNI中的引用
JNI中有三种应用:1.LocalReference(局部引用) 2.GlobalReference(全局引用)3.WeakGlobalReference弱全局引用。JNI中创建的对象同样由GC负责回收,和Java层创建的对象具有一样的生命周期。
-
局部引用
大部分JNI函数返回的局部引用,局部引用不能在后续的调用中被缓存和重用,一旦native函数返回,局部引用即被释放。存在局部引用表,本地函数执行期间生产的java对象会被JNIEnv隐式存放在表中,保证函数执行期间不会被回收。本地引用存在数量限制,需要注意控制对象规模,必要时提前释放局部引用void DeleteLocalRef(jobject object) jint EnsureLocalCapacity(JNIEnv *env, jint capacity);
-
全局引用
存在全局引用表,其中的java对象不会被回收,可以手动创建全局引用实现对象复用。jobject NewGlobalRef(jobject obj); void DeleteGlobalRef(jobject object);
-
弱全局应用
如果对象只有弱全局引用,下次垃圾回收将会被回收。jweak NewWeakGlobalRef(jobject obj); void DeleteWeakGlobalRef(jweak weak); jboolean IsSameObject(jobject ref1, jobject ref2)
其中IsSameObject只能用于判断弱全局引用是否有效;手动删除或者缓存局部引用,会出现JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0x15。
测试demo
extern "C" void
Java_com_jack_hellojniworld_MainActivity_jniTestJniRef(JNIEnv *env, jobject object) {
//测试本地引用移除
// jstring str = env->NewStringUTF("local_ref_str");
// env->DeleteLocalRef(str);
// str = nullptr;
// LOGD("jniTestJniRef sleeping...");
// sleep(15);
// //使用被删除的引用直接报error. 暂时未找到判断引用无效的办法,需要手动置null
// //JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0x15
// const char * chars = env->GetStringUTFChars(str, nullptr);
// LOGD("jniTestJniRef chars:%s", chars);
//缓存本地引用测试
// if (cachedString == nullptr) {
// cachedString = env->NewStringUTF("local_ref_str");
// LOGD("jniTestJniRef set cache...");
// } else {
// //JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0x15
// //不支持IsSameObject测试
// if (env->IsSameObject(cachedString, nullptr) != JNI_TRUE) {
// const char *chars = env->GetStringUTFChars(static_cast<jstring>(cachedWeakString),
// nullptr);
// LOGD("jniTestJniRef from cache...s%s", chars);
// } else {
// LOGD("jniTestJniRef from cache...not valid!");
// }
// };
//缓存全局引用测试
// if (cachedString == nullptr) {
// jstring jGlobalStr = env->NewStringUTF("local_ref_str_to_global_ref");
// cachedString = static_cast<jstring>(env->NewGlobalRef(jGlobalStr));
// LOGD("jniTestJniRef set cache...");
// } else {
// const char *chars = env->GetStringUTFChars(cachedString, nullptr);
// LOGD("jniTestJniRef from cache...s%s", chars);
// }
//全局弱应用测试
//profile手动gc,会被回收
// if (cachedWeakString == nullptr) {
// jstring jWeakGlobalStr = env->NewStringUTF("local_ref_str_to_weak_global_ref");
// cachedWeakString = env->NewWeakGlobalRef(jWeakGlobalStr);
// LOGD("jniTestJniRef set cache...");
// } else {
// if (env->IsSameObject(cachedWeakString, nullptr) != JNI_TRUE) {
// const char *chars = env->GetStringUTFChars(static_cast<jstring>(cachedWeakString),
// nullptr);
// LOGD("jniTestJniRef from cache...s%s", chars);
// } else {
// LOGD("jniTestJniRef from cache...not valid!");
// }
// }
}