【NDK系列11】Java和c++交互 Jni初識

JNI是一種本地編程接口隐砸。它允許運(yùn)行在JAVA虛擬機(jī)中的JAVA代碼和用其他編程語言圃庭,諸如C語言、C++病往、匯編捣染,應(yīng)用和庫之間的交互操作。 不只是Android特有的東西

1. Java調(diào)用c++方法

靜態(tài)加載so

 static {
        System.loadLibrary("native-lib");
}

c++中和java中對應(yīng)方法

  public static native void native11();
extern "C"
JNIEXPORT jstring JNICALL
Java_com_dds_anyndk_AnyNdk_native11(JNIEnv *env, jclass type, jint a, jstring str_, jfloat f) {
    // 獲得c字符串
    const char *str = env->GetStringUTFChars(str_, JNI_FALSE);

    char returnStr[100];
    //格式化字符串
    sprintf(returnStr, "C++ string:%d,%s,%f", a, str, f);

    //釋放掉內(nèi)存
    env->ReleaseStringUTFChars(str_, str);

    return env->NewStringUTF(returnStr);
}

CmakeLists.txt配置

cmake_minimum_required(VERSION 3.4.1)

add_library(
        native-lib
        SHARED
        native11.cpp)

target_link_libraries(
        native-lib
        log)

2. JNI數(shù)據(jù)類型

JNIEXPORT 和 JNICALL停巷,定義在jni.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")))

JNICALL:

? 在類Unix中無定義,在Windows中定義為:_stdcall 缸榛,一種函數(shù)調(diào)用約定

類Unix系統(tǒng)中這兩個(gè)宏可以省略不加吝羞。

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ù)組

獲取數(shù)組類型的數(shù)據(jù)

extern "C"
JNIEXPORT jstring JNICALL
Java_com_dds_anyndk_AnyNdk_native11_12(JNIEnv *env, jclass type, jobjectArray strs,
                                       jintArray ints_) {

    //1内颗、 獲得字符串?dāng)?shù)組
    int32_t str_length = env->GetArrayLength(strs);
    LOGD("字符串 數(shù)組長度:%d", str_length);
    for (int i = 0; i < str_length; ++i) {
        jstring str = jstring(env->GetObjectArrayElement(strs, i));
        const char *c_str = env->GetStringUTFChars(str, JNI_FALSE);
        LOGD("字符串有:%s", c_str);
        //使用完釋放
        env->ReleaseStringUTFChars(str, c_str);
    }

    //2钧排、獲得基本數(shù)據(jù)類型數(shù)組
    int32_t int_length = env->GetArrayLength(ints_);
    LOGD("int 數(shù)組長度:%d", int_length);
    jint *ints = env->GetIntArrayElements(ints_, nullptr);
    for (int i = 0; i < int_length; i++) {
        LOGD("int 數(shù)據(jù)有:%d", ints[i]);
    }

    env->ReleaseIntArrayElements(ints_, ints, 0);

    return env->NewStringUTF("hello");
}

3. C/C++反射Java

在C/C++中反射創(chuàng)建Java的對象,調(diào)用Java的方法

基本數(shù)據(jù)類型的簽名采用一系列大寫字母來表示, 如下表所示:

Java類型 簽名
boolean Z
short S
float F
byte B
int I
double D
char C
long J
void V
引用類型 L + 全限定名 + ;
數(shù)組 [+類型簽名

需要反射的方法

public class JavaHelper {
    private static final String TAG = "dds_native11";

    //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);
    }
}

反射調(diào)用方法

extern "C"
JNIEXPORT jstring JNICALL
Java_com_dds_anyndk_AnyNdk_native11_13(JNIEnv *env, jclass type) {
    jclass class_helper = env->FindClass("com/dds/anyndk/JavaHelper");

    // 反射調(diào)用靜態(tài)方法
    jmethodID method_staticMethod = env->GetStaticMethodID(class_helper, "staticMethod",
                                                           "(Ljava/lang/String;IZ)V");
    jstring staticStr = env->NewStringUTF("C++調(diào)用靜態(tài)方法");
    env->CallStaticVoidMethod(class_helper, method_staticMethod, staticStr, 1, true);


    // 反射調(diào)用構(gòu)造方法
    jmethodID constructMethod = env->GetMethodID(class_helper,"<init>","()V");

    jobject  helper = env->NewObject(class_helper,constructMethod);
    jmethodID instanceMethod = env->GetMethodID(class_helper,"instanceMethod","(Ljava/lang/String;IZ)V");
    jstring instanceStr= env->NewStringUTF("C++調(diào)用實(shí)例方法");
    env->CallVoidMethod(helper,instanceMethod,instanceStr,2,0);
    // 釋放資源
    env->DeleteLocalRef(class_helper);
    env->DeleteLocalRef(staticStr);
    env->DeleteLocalRef(instanceStr);
    env->DeleteLocalRef(helper);

    return env->NewStringUTF("dds");
}

可以使用javap來獲取反射方法時(shí)的簽名

javap -s com.dds.anyndk.JavaHelper

反射修改變量

需要反射的方法

public class JavaHelper {
     int a = 10;
     static String b = "java字符串";

    public void testReflect(JavaHelper javaHelper) {
        Log.e(TAG, "修改前 : a = " + a + " b=" + b);
        AnyNdk.native11_4(javaHelper);
        Log.e(TAG, "修改后 : a = " + a + " b=" + b);
    }
}

反射

#define  LOG_TAG    "dds_native4"
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,  LOG_TAG, __VA_ARGS__ )

extern "C"
JNIEXPORT void JNICALL
Java_com_dds_anyndk_AnyNdk_native11_14(JNIEnv *env, jclass type, jobject javaHelper) {

    jclass clazz = env->GetObjectClass(javaHelper);

    //獲得int a的標(biāo)示
    jfieldID a = env->GetFieldID(clazz, "a", "I");
    // 獲取a的值
    int value = env->GetIntField(javaHelper, a);
    LOGD("獲得java屬性a:%d", value);

    env->SetIntField(javaHelper, a, 100);

    // 獲取String b 的標(biāo)示
    jfieldID b = env->GetStaticFieldID(clazz, "b", "Ljava.lang.String;");
    // 獲取靜態(tài)變量的值
    auto bStr = jstring(env->GetStaticObjectField(clazz, b));
    
    const char *bc_str = env->GetStringUTFChars(bStr, JNI_FALSE);
    LOGD("獲得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);

}

4. JNI_OnLoad

調(diào)用System.loadLibrary()函數(shù)時(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方法");
}

//需要?jiǎng)討B(tài)注冊的方法數(shù)組
static const JNINativeMethod mMethods[] = {
        {"dynamicNative","()V", (void *)dynamicNative1},
        {"dynamicNative", "(I)Ljava/lang/String;", (jstring *)dynamicNative2}

};
//需要?jiǎng)討B(tài)注冊native方法的類名
static const char* mClassName = "com/dds/anyndk/AnyNdk";
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 activityCls = env->FindClass(mClassName);
    // 注冊 如果小于0則注冊失敗
    r = env->RegisterNatives(activityCls, mMethods, 2);
    if (r != JNI_OK) {
        return -1;
    }
    return JNI_VERSION_1_4;
}

5. c++線程中調(diào)用Java

native調(diào)用java需要使用JNIEnv這個(gè)結(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;
    _vm->AttachCurrentThread(&env, 0);

    jclass clazz = env->GetObjectClass(_instance);
    jmethodID methodId = env->GetStaticMethodID(clazz, "staticMethod", "(Ljava/lang/String;IZ)V");
    jstring staticStr = env->NewStringUTF("C++調(diào)用靜態(tài)方法");
    env->CallStaticVoidMethod(clazz, methodId, staticStr, 1, true);
    env->DeleteLocalRef(clazz);
    env->DeleteLocalRef(staticStr);

    _vm->DetachCurrentThread();
    return 0;


}

extern "C"
JNIEXPORT void JNICALL
Java_com_dds_anyndk_AnyNdk_native11_15(JNIEnv *env, jclass type, jobject javaHelper) {
    pthread_t pid;
    _instance = env->NewGlobalRef(javaHelper);
    // 開啟線程
    pthread_create(&pid, 0, task, 0);


}



代碼

https://github.com/ddssingsong/AnyNdk

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氓仲,一起剝皮案震驚了整個(gè)濱河市水慨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌敬扛,老刑警劉巖晰洒,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異啥箭,居然都是意外死亡谍珊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門急侥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砌滞,“玉大人,你說我怎么就攤上這事坏怪”慈螅” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵铝宵,是天一觀的道長打掘。 經(jīng)常有香客問我,道長鹏秋,這世上最難降的妖魔是什么尊蚁? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮侣夷,結(jié)果婚禮上横朋,老公的妹妹穿的比我還像新娘。我一直安慰自己惜纸,他們只是感情好叶撒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耐版,像睡著了一般祠够。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上粪牲,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天古瓤,我揣著相機(jī)與錄音,去河邊找鬼腺阳。 笑死落君,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亭引。 我是一名探鬼主播绎速,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼焙蚓!你這毒婦竟也來了纹冤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤购公,失蹤者是張志新(化名)和其女友劉穎萌京,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宏浩,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡知残,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了比庄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片求妹。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖印蔗,靈堂內(nèi)的尸體忽然破棺而出扒最,到底是詐尸還是另有隱情,我是刑警寧澤华嘹,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布吧趣,位于F島的核電站,受9級特大地震影響耙厚,放射性物質(zhì)發(fā)生泄漏强挫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一薛躬、第九天 我趴在偏房一處隱蔽的房頂上張望俯渤。 院中可真熱鬧,春花似錦型宝、人聲如沸八匠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梨树。三九已至坑夯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抡四,已是汗流浹背柜蜈。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留指巡,地道東北人淑履。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像藻雪,于是被迫代替她去往敵國和親秘噪。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 什么是JNI阔涉? JNI 是java本地開發(fā)接口.JNI 是一個(gè)協(xié)議,這個(gè)協(xié)議用來溝通java代碼和外部的本地代碼(...
    a_tomcat閱讀 2,821評論 0 54
  • JNI編程 JNI是一種本地編程接口缆娃。它允許運(yùn)行在JAVA虛擬機(jī)中的JAVA代碼和用其他編程語言,諸如C語言瑰排、C+...
    微塵_8957閱讀 456評論 0 0
  • _ 聲明: 對原文格式以及內(nèi)容做了細(xì)微的修改和美化, 主要為了方便閱讀和理解 _ 一. 基礎(chǔ) Java Nativ...
    元亨利貞o閱讀 5,921評論 0 34
  • 0.要素1.類操作2.異常操作3.全局及局部引用4.對象操作5.字符串操作6.數(shù)組操作7.訪問對象的屬性和方法7....
    MagicalGuy閱讀 1,336評論 0 2
  • 朵朵開盒子開不開贯要,生氣啦?那兒甩著盒子椭住,然后我問他怎么了(和善而堅(jiān)定)不告訴你崇渗!然后還把沙發(fā)上的靠墊兒挪到了一遍。...
    a6bac2457fa3閱讀 205評論 1 0