概述
JNI是一種本地編程接口。它允許運(yùn)行在JAVA虛擬機(jī)中的JAVA代碼和用其他編程語言,諸如C語言、C++、匯編,寫的應(yīng)用和庫之間的交互操作。
JNI數(shù)據(jù)類型
JNIEXPORT 和 JNICALL,定義在
jni_md.h頭文件中。JNIEXPORT:
在 Windows 中,定義為
__declspec(dllexport)。因?yàn)閃indows編譯 dll 動態(tài)庫規(guī)定,如果動態(tài)庫中的函數(shù)要被外部調(diào)用,需要在函數(shù)聲明中添加此標(biāo)識,表示將該函數(shù)導(dǎo)出在外部可以調(diào)用。在 Linux/Unix/Mac os/Android 這種 Like Unix系統(tǒng)中,定義為
__attribute__ ((visibility ("default")))GCC 有個visibility屬性, 該屬性是說, 啟用這個屬性:
- 當(dāng)-fvisibility=hidden時
動態(tài)庫中的函數(shù)默認(rèn)是被隱藏的即 hidden. 除非顯示聲明為
__attribute__((visibility("default"))).
- 當(dāng)-fvisibility=default時
動態(tài)庫中的函數(shù)默認(rèn)是可見的.除非顯示聲明為
__attribute__((visibility("hidden"))).JNICALL:
在類Unix中無定義,在Windows中定義為:
_stdcall,一種函數(shù)調(diào)用約定類Unix系統(tǒng)中這兩個宏可以省略不加。
| 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位浮點(diǎn)型 |
| double | jdouble | C/C++64位浮點(diǎn)型 |
| Object | jobject | 任何Java對象,或者沒有對應(yīng)java類型的對象 |
| Class | jclass | Class對象 |
| String | jstring | 字符串對象 |
| Object[] | jobjectArray | 任何對象的數(shù)組 |
| boolean[] | jbooleanArray | 布爾型數(shù)組 |
| byte[] | jbyteArray | 比特型數(shù)組 |
| char[] | jcharArray | 字符型數(shù)組 |
| short[] | jshortArray | 短整型數(shù)組 |
| int[] | jintArray | 整型數(shù)組 |
| long[] | jlongArray | 長整型數(shù)組 |
| float[] | jfloatArray | 浮點(diǎn)型數(shù)組 |
| double[] | jdoubleArray | 雙浮點(diǎn)型數(shù)組 |
C/C++中獲取java的數(shù)組時:
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_jnitest_MainActivity_test(JNIEnv *env, jobject instance, jobjectArray a_,jintArray b_) {
//1、 獲得字符串?dāng)?shù)組
//獲得數(shù)組長度
int32_t str_length = env->GetArrayLength(a_);
LOGE("字符串 數(shù)組長度:%d",str_length);
//獲得字符串?dāng)?shù)組的數(shù)據(jù)
for (int i = 0; i < str_length; ++i) {
jstring str = static_cast<jstring>(env->GetObjectArrayElement(a_, i));
const char* c_str = env->GetStringUTFChars(str, 0);
LOGE("字符串有:%s",c_str);
//使用完釋放
env->ReleaseStringUTFChars(str,c_str);
}
//2、獲得基本數(shù)據(jù)類型數(shù)組
int32_t int_length = env->GetArrayLength(b_);
LOGE("int 數(shù)組長度:%d",int_length);
//對應(yīng)的有 GetBoolean 、GetFloat等
jint *b = env->GetIntArrayElements(b_, 0);
for (int i = 0; i < int_length; i++) {
LOGE("int 數(shù)據(jù)有:%d",b[i]);
}
env->ReleaseIntArrayElements(b_, b, 0);
return env->NewStringUTF("222");
}
C/C++反射Java
反射方法
在C/C++中反射創(chuàng)建Java的對象,調(diào)用Java的方法
package com.example.jnitest;
import android.util.Log;
public class Helper {
private static final String TAG = "Helper";
//private和public 對jni開發(fā)來說沒任何區(qū)別 都能反射調(diào)用
public void instanceMethod(String a,int b,boolean c){
Log.e(TAG,"instanceMethod a=" +a +" b="+b+" c="+c );
}
public static void staticMethod(String a,int b,boolean c){
Log.e(TAG,"staticMethod a=" +a +" b="+b+" c="+c);
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_jnitest_MainActivity_invokeHelper(JNIEnv *env, jobject instance) {
jclass clazz = env->FindClass("com/example/jnitest/Helper");
//獲得具體的靜態(tài)方法 參數(shù)3:簽名(下方說明)
//如果不會填 可以使用javap
jmethodID staticMethod = env->GetStaticMethodID(clazz,"staticMethod","(Ljava/lang/String;IZ)V");
//調(diào)用靜態(tài)方法
jstring staticStr= env->NewStringUTF("C++調(diào)用靜態(tài)方法");
env->CallStaticVoidMethod(clazz,staticMethod,staticStr,1,1);
//獲得構(gòu)造方法 <init>:構(gòu)造方法寫法
jmethodID constructMethod = env->GetMethodID(clazz,"<init>","()V");
//創(chuàng)建對象
jobject helper = env->NewObject(clazz,constructMethod);
jmethodID instanceMethod = env->GetMethodID(clazz,"instanceMethod","(Ljava/lang/String;IZ)V");
jstring instanceStr= env->NewStringUTF("C++調(diào)用實(shí)例方法");
env->CallVoidMethod(helper,instanceMethod,instanceStr,2,0);
//釋放
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(staticStr);
env->DeleteLocalRef(instanceStr);
env->DeleteLocalRef(helper);
}
基本數(shù)據(jù)類型的簽名采用一系列大寫字母來表示, 如下表所示:
| Java類型 | 簽名 |
|---|---|
| boolean | Z |
| short | S |
| float | F |
| byte | B |
| int | I |
| double | D |
| char | C |
| long | J |
| void | V |
| 引用類型 | L + 全限定名 + ; |
| 數(shù)組 | [+類型簽名 |
可以使用javap來獲取反射方法時的簽名
#cd 進(jìn)入 class所在的目錄 執(zhí)行: javap -s 全限定名,查看輸出的 descriptor
xx\app\build\intermediates\classes\debug>javap -s com.example.jnitest.Helper
Compiled from "Helper.java"
public class com.example.jnitest.Helper {
public com.example.jnitest.Helper();
descriptor: ()V
public void instanceMethod(java.lang.String, int, boolean);
descriptor: (Ljava/lang/String;IZ)V
public static void staticMethod(java.lang.String, int, boolean);
descriptor: (Ljava/lang/String;IZ)V
}
反射屬性
int a = 10;
static String b = "java字符串";
private static final String TAG = "Helper";
public void testReflect() {
Log.e(TAG,"修改前 : a = " +a +" b="+b);
reflectHelper();
Log.e(TAG,"修改后 : a = " +a +" b="+b);
}
public native void reflectHelper();
extern "C"
JNIEXPORT void JNICALL
Java_com_example_jnitest_Helper_reflectHelper(JNIEnv *env, jobject instance) {
//instance 就是 helper
jclass clazz = env->GetObjectClass(instance);
//獲得int a的標(biāo)示
jfieldID a = env->GetFieldID(clazz,"a","I");
int avalue = env->GetIntField(instance,a);
LOGE("獲得java屬性a:%d",avalue);
//修改屬性值
env->SetIntField(instance,a,100);
jfieldID b = env->GetStaticFieldID(clazz,"b","Ljava.lang.String;");
//獲取值
jstring bstr = static_cast<jstring>(env->GetStaticObjectField(clazz, b));
const char* bc_str = env->GetStringUTFChars(bstr,0);
LOGE("獲得java屬性b:%s",bc_str);
//修改
jstring new_str = env->NewStringUTF("C++字符串");
env->SetStaticObjectField(clazz,b,new_str);
env->ReleaseStringUTFChars(bstr,bc_str);
env->DeleteLocalRef(new_str);
env->DeleteLocalRef(clazz);
}
JNI引用
在 JNI 規(guī)范中定義了三種引用:局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)。
局部引用
大多數(shù)JNI函數(shù)會創(chuàng)建局部引用。NewObject/FindClass/NewStringUTF 等等都是局部引用。
局部引用只有在創(chuàng)建它的本地方法返回前有效,本地方法返回后,局部引用會被自動釋放。
因此無法跨線程、跨方法使用。
extern "C"
JNIEXPORT jstring JNICALL
xxx(JNIEnv *env, jobject instance) {
//錯誤
//不能在本地方法中把局部引用存儲在靜態(tài)變量中緩存起來供下一次調(diào)用時使用。
// 第二次執(zhí)行 str依然有值,但是其引用的 “C++字符串” 已經(jīng)被釋放
static jstring str;
if(str == NULL){
str = env->NewStringUTF("C++字符串");
}
return str;
}
釋放一個局部引用有兩種方式:
1. 本地方法執(zhí)行完畢后VM自動釋放;
2. 通過DeleteLocalRef手動釋放;
VM會自動釋放局部引用,為什么還需要手動釋放呢?
因?yàn)榫植恳脮柚顾玫膶ο蟊籊C回收。
全局引用
全局引用可以跨方法、跨線程使用,直到它被手動釋放才會失效 。
由 NewGlobalRef 函數(shù)創(chuàng)建
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_jnitest_MainActivity_test1(JNIEnv *env, jobject instance) {
//正確
static jstring globalStr;
if(globalStr == NULL){
jstring str = env->NewStringUTF("C++字符串");
//刪除全局引用調(diào)用 DeleteGlobalRef
globalStr = static_cast<jstring>(env->NewGlobalRef(str));
//可以釋放,因?yàn)橛辛艘粋€全局引用使用str,局部str也不會使用了
env->DeleteLocalRef(str);
}
return globalStr;
}
弱引用
與全局引用類似,弱引用可以跨方法、線程使用。與全局引用不同的是,弱引用不會阻止GC回收它所指向的VM內(nèi)部的對象 。
在對Class進(jìn)行弱引用是非常合適(FindClass),因?yàn)镃lass一般直到程序進(jìn)程結(jié)束才會卸載。
在使用弱引用時,必須先檢查緩存過的弱引用是指向活動的對象,還是指向一個已經(jīng)被GC的對象
extern "C"
JNIEXPORT jclass JNICALL
Java_com_example_jnitest_MainActivity_test1(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_OnLoad
調(diào)用System.loadLibrary()函數(shù)時, 內(nèi)部就會去查找so中的 JNI_OnLoad 函數(shù),如果存在此函數(shù)則調(diào)用。
JNI_OnLoad會:
告訴 VM 此 native 組件使用的 JNI 版本。
對應(yīng)了Java版本,android中只支持JNI_VERSION_1_2 、JNI_VERSION_1_4、JNI_VERSION_1_6
在JDK1.8有 JNI_VERSION_1_8。
jint JNI_OnLoad(JavaVM* vm, void* reserved){
// 2、4、6都可以
return JNI_VERSION_1_4;
}
動態(tài)注冊
在此之前我們一直在jni中使用的 Java_PACKAGENAME_CLASSNAME_METHODNAME 來進(jìn)行與java方法的匹配,這種方式我們稱之為靜態(tài)注冊。
而動態(tài)注冊則意味著方法名可以不用這么長了,在android aosp源碼中就大量的使用了動態(tài)注冊的形式
//Java:
native void dynamicNative();
native String dynamicNative(int i);
//C++:
void dynamicNative1(JNIEnv *env, jobject jobj){
LOGE("dynamicNative1 動態(tài)注冊");
}
jstring dynamicNative2(JNIEnv *env, jobject jobj,jint i){
return env->NewStringUTF("我是動態(tài)注冊的dynamicNative2方法");
}
//需要動態(tài)注冊的方法數(shù)組
static const JNINativeMethod mMethods[] = {
{"dynamicNative","()V", (void *)dynamicNative1},
{"dynamicNative", "(I)Ljava/lang/String;", (jstring *)dynamicNative2}
};
//需要動態(tài)注冊native方法的類名
static const char* mClassName = "com/example/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,2);
if(r != JNI_OK )
{
return -1;
}
return JNI_VERSION_1_4;
}
native跨線程調(diào)用Java
native調(diào)用java需要使用JNIEnv這個結(jié)構(gòu)體,而JNIEnv是由Jvm傳入與線程相關(guān)的變量。
但是可以通過JavaVM的AttachCurrentThread方法來獲取到當(dāng)前線程中的JNIEnv指針。
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;
//將本地當(dāng)前線程附加到j(luò)vm,并獲得jnienv
//成功則返回0
_vm->AttachCurrentThread(&env,0);
jclass clazz = env->GetObjectClass(_instance);
//獲得具體的靜態(tài)方法 參數(shù)3:方法簽名
//如果不會填 可以使用javap
jmethodID staticMethod = env->GetStaticMethodID(clazz,"staticMethod","(Ljava/lang/String;IZ)V");
//調(diào)用靜態(tài)方法
jstring staticStr= env->NewStringUTF("C++調(diào)用靜態(tài)方法");
env->CallStaticVoidMethod(clazz,staticMethod,staticStr,1,1);
//獲得構(gòu)造方法
jmethodID constructMethod = env->GetMethodID(clazz,"<init>","()V");
//創(chuàng)建對象
jobject helper = env->NewObject(clazz,constructMethod);
jmethodID instanceMethod = env->GetMethodID(clazz,"instanceMethod","(Ljava/lang/String;IZ)V");
jstring instanceStr= env->NewStringUTF("C++調(diào)用實(shí)例方法");
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_example_jnitest_Helper_nativeThread(JNIEnv *env, jobject instance) {
pthread_t pid;
//這里注意釋放
_instance = env->NewGlobalRef(instance);
pthread_create(&pid,0,task,0);
}