JNI基礎(chǔ)

概述

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屬性, 該屬性是說, 啟用這個屬性:

  1. 當(dāng)-fvisibility=hidden時

動態(tài)庫中的函數(shù)默認(rèn)是被隱藏的即 hidden. 除非顯示聲明為__attribute__((visibility("default"))).

  1. 當(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);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容