NDK開發(fā)系列(二)——JNI c/c++調(diào)用java

上一篇簡單的介紹了JNI,簡單的回顧下,java要想調(diào)用c/c++代碼分為概括分為三步:
1免胃、編寫native方法躺涝,在c/c++中實現(xiàn)對應(yīng)的c函數(shù)
2、將c代碼編譯成動態(tài)庫
3、System.loadLibrary()對動態(tài)庫進行加載

這一篇重點講c/c++怎么訪問java俺猿,為了方便開發(fā)環(huán)境切換到AndroidStudio,首先需要把AS的ndk環(huán)境給配好,詳細的配置這里先不講。AS3.0對ndk的支持已經(jīng)非常友好了,以前的舊版本是用的makefile來編譯動態(tài)庫,新版本的是Cmake編譯,其中差異還是比較大的,舊版本的我以前寫過一篇文章,感興趣可以翻出來看看。
首先饺藤,as新建一個工程


image.png

把支持c++選項勾選上,這時候如果你沒有配好ndk-bound询刹,需要到工程的屬性設(shè)置里面配置:


image.png

配置好以后蔽挠,as會自動生成一個ndk工程杠巡,默認(rèn)生成一些示例代碼,非常的友好叁怪。在main/cpp目錄中存放的是c/c++代碼血柳,MainActivity中自動生成了一些示例代碼皂贩,可以仿照使用栖榨。
這里我們不使用生成的代碼,把代碼全部干掉明刷,重新開始婴栽。
  • c準(zhǔn)備工作:在cpp目錄下面新建c代碼文件native.c,在java下面新建一個java類Man.java


    image.png

    這時候native方法會爆紅辈末,這是因為as沒有檢測到對應(yīng)的c函數(shù)的實現(xiàn)愚争,用alt+enter快捷鍵自動生成到native.c中映皆,不得不說as越來越強大了,然后需要在CMakeLists.txt文件中加入我們的c文件


    image.png
# native.c
JNIEXPORT jstring JNICALL
Java_com_example_xucong_jnitest_Man_accessField(JNIEnv *env, jclass type) {

    return (*env)->NewStringUTF(env, "accessField");
}

JNIEXPORT jstring JNICALL
Java_com_example_xucong_jnitest_Man_stringFromJNI(JNIEnv *env, jobject instance) {

    return (*env)->NewStringUTF(env, "stringFromJNI");
}

運行結(jié)果:


image.png

以上是簡單使用轰枝。

  • c/c++訪問java屬性
    首先看下Man.java這個類的代碼
public class Man {

    public String name = "Tom";

    public native static String accessField();

    public native String stringFromJNI();

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

這里定義了一個屬性捅彻、一個靜態(tài)的native方法accessField(),一個非靜態(tài)的native方法stringFromJNI(),和一個靜態(tài)塊鞍陨,用來加載as為我們編譯好的so動態(tài)庫步淹,注意native-lib是as編譯好動態(tài)庫的名稱,不包括后綴诚撵,當(dāng)然這個名字我們可以CMakeLists里面修改缭裆,編譯好的so庫在app/build/intermediates/cmake里面
native方法有靜態(tài)和非靜態(tài)之分,對應(yīng)的c的實現(xiàn)函數(shù)也有所差異:

NIEXPORT jstring JNICALL
Java_com_example_xucong_jnitest_Man_accessField(JNIEnv *env, jclass type) {

    return (*env)->NewStringUTF(env, "accessField");
}

JNIEXPORT jstring JNICALL
Java_com_example_xucong_jnitest_Man_stringFromJNI(JNIEnv *env, jobject instance) {

    return (*env)->NewStringUTF(env, "stringFromJNI");
}

c對應(yīng)的java native函數(shù)中至少有兩個參數(shù)寿烟,JNIEnv澈驼、jclass或者jobject,JNIEnv是JNI的運行環(huán)境筛武,在c中一個二級結(jié)構(gòu)體指針缝其,在c++中是一級指針,他們所調(diào)用函數(shù)的方式也有不同:

JNIEXPORT jstring JNICALL
Java_com_example_xucong_jnitest_Man_accessField(JNIEnv *env, jclass type) {

    return env->NewStringUTF("accessField");
//    return (*env)->NewStringUTF(env, "accessField");
}

他們所使用的方法名都是一樣畅铭,只是c++中的所有函數(shù)不在需要傳env的上下文了氏淑,這是因為c++中有this上下文關(guān)鍵字。

jobject和jclass硕噩,這是JNI的數(shù)據(jù)類型假残,如果java中是非靜態(tài)方法,對應(yīng)的jobject炉擅,如果是靜態(tài)的對應(yīng)的是jclass辉懒,這兩個參數(shù)是java在JNI中的映射,需要通過這兩個參數(shù)來訪問java谍失。其實也好理解眶俩,如果是非靜態(tài),我們調(diào)用native方法的時候需要new一個對象快鱼,和對象實例有關(guān)颠印,靜態(tài)的方法只和Class有關(guān)。
函數(shù)的返回類型是jstring 對應(yīng)的java中的String抹竹,每種java的數(shù)據(jù)類型在JNI中都有與之對應(yīng)

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 */

/* "cardinal indices and sizes" */
typedef jint     jsize;

#ifdef __cplusplus

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;


#else /* not __cplusplus */

/*
 * Reference types, in C.
 */
typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
typedef jarray          jobjectArray;
typedef jarray          jbooleanArray;
typedef jarray          jbyteArray;
typedef jarray          jcharArray;
typedef jarray          jshortArray;
typedef jarray          jintArray;
typedef jarray          jlongArray;
typedef jarray          jfloatArray;
typedef jarray          jdoubleArray;
typedef jobject         jthrowable;
typedef jobject         jweak;

數(shù)據(jù)類型分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型线罕,引用數(shù)據(jù)類型分為jstring和jobject,還有任何數(shù)組也是jobject窃判,這些都在jni.h源碼中有钞楼。

那么進入正題:在c層調(diào)來修改Man的屬性name的值,這里需要把accessField修改為非靜態(tài)方法袄琳,因為name屬性是非靜態(tài)的询件,只有對象實例才有屬性值燃乍。

//1.訪問屬性
//修改屬性key的字符串
JNIEXPORT jstring JNICALL
Java_com_example_xucong_jnitest_Man_accessField(JNIEnv *env, jobject jobj) {

    //得到class
    jclass jclazz = (*env)->GetObjectClass(env,jobj);
    //jfieldID
    //簽名:類型的簡稱
    //屬性,方法
    jfieldID fid = (*env)->GetFieldID(env,jclazz,"name","Ljava/lang/String;");
    //獲取key屬性的值
    //注意:key為基本數(shù)據(jù)類型宛琅,規(guī)則如下
    //(*env)->GetIntField(); (*env)->Get<Type>Field();  
    jstring jstr = (*env)->GetObjectField(env,jobj,fid);
    //jstring轉(zhuǎn)為 C/C++字符串
    char * c_str = (*env)->GetStringUTFChars(env,jstr,NULL);

    strcat(c_str,"android");
    //拼接完成之后刻蟹,從C字符串轉(zhuǎn)為jstring
    jstring jstr_new = (*env)->NewStringUTF(env,c_str);
    //修改key的屬性
    //注意規(guī)則:Set<Type>Field
    (*env)->SetObjectField(env,jobj,fid,jstr_new);

    return jstr_new;
}

以上的流程和java的反射的流程非常相似,拿到class對象->獲取屬性id->拿到屬性值->修改屬性嘿辟,

  • GetFieldID 座咆,最后一個參數(shù)為數(shù)據(jù)類型的簽名,name是String類型仓洼,就將String簽名傳入介陶,各種數(shù)據(jù)類型的簽名如下:


    image.png
  • GetObjectField,獲取屬性值色建,規(guī)則為Get<Type>Field哺呜,如果java類中的屬性類型為int,則為GetIntField()箕戳;

  • Get<Type>Field();修改屬性的值某残,和GetObjectField的規(guī)則一樣。
    其中要注意的是jni的字符串是沒有修改的api的陵吸,需要通過c字符串來修改玻墅,再改回jstring。
    java代碼:

TextView tv = findViewById(R.id.sample_text);
        Man man = new Man();
        String str = "修改前:" + man.name;
        man.accessField();
        str = str + "   修改后:" + man.name;
        tv.setText(str);

運行結(jié)果:


image.png
  • 訪問java靜態(tài)屬性
    訪問java靜態(tài)屬性的步驟壮虫,只是api稍有調(diào)整
    在Man.java中增加一個屬性澳厢,和一個方法
    public static int age = 18;
    public native String accessStaticField();

c代碼:

Java_com_example_xucong_jnitest_Man_accessStaticField(JNIEnv *env, jobject jobj) {

    //獲取class
    jclass jclazz = (*env)->GetObjectClass(env,jobj);
    //獲取jfieldid
    jfieldID jid = (*env)->GetStaticFieldID(env,jclazz,"age","I");
    jint jage = (*env)->GetStaticIntField(env,jclazz,jid);
    jage++;
    (*env)->SetStaticIntField(env,jclazz,jid,jage);

    return (*env)->NewStringUTF(env, "修改成功");
}

java代碼:

TextView tv = findViewById(R.id.sample_text);
        Man man = new Man();
        String str = "name修改前:" + man.name;
        man.accessField();
        str = str + "\nname修改后:" + man.name;

        str += "\nage修改前:" + Man.age;
        man.accessStaticField();
        str = str + "\nage修改后:" + Man.age;
        tv.setText(str);
image.png

可以看出來,步驟和前面一樣囚似,只是訪問靜態(tài)屬性的方法都加上了static
另外就是SetStaticIntField()方法的第二個參數(shù)類型是jclass剩拢,而不是jobject,為什么呢饶唤?這個和java的類是對應(yīng)的徐伐,我們訪問java的靜態(tài)變量的時候,變量只和Class有關(guān)募狂,和實例對象的應(yīng)用無關(guān)办素,而非靜態(tài)成員變量和必須要通過對象的引用來訪問,在JNI中也是這個理祸穷。如果accessStaticField()方法改為static性穿,那么JNI中實現(xiàn)的c方法為jclass對象可以省去jclass jclazz = (*env)->GetObjectClass(env,jobj);這一步驟。

  • C/C++D調(diào)用java方法
    直接上代碼:
public native int accessMethod();

public int getRandomNum(int max) {
    return new Random().nextInt(max);
}

accessMethod()方法是進入c粱哼,c/c++中再去調(diào)用getRandomNum產(chǎn)生隨機數(shù)季二,返回給accessMethod()方法檩咱。
看看JNI的實現(xiàn):

//訪問java方法
JNIEXPORT jint JNICALL
Java_com_example_xucong_jnitest_Man_accessMethod(JNIEnv *env, jobject jobj) {
    //獲取class
    jclass jclazz = (*env)->GetObjectClass(env,jobj);
    jmethodID jmid = (*env)->GetMethodID(env,jclazz,"getRandomNum","(I)I");
    jint random = (*env)->CallIntMethod(env,jobj,jmid,100);
    return random;
}

步驟套路和前面及其的相似揭措,不同的只是方法的調(diào)用胯舷,GetMethodID獲取方法id,方法第三個參數(shù)為方法名绊含,最后一個參數(shù)是方法簽名桑嘶,方法簽名為對應(yīng)的是jobj的java類的簽名。

獲取簽名:
獲取簽名是用javap命令躬充,打開as的terminal 可以看到j(luò)avap的指令集


image.png

cd 到app/build/intermediates/debug目錄下逃顶,里面有編譯好的class文件,執(zhí)行javap -p -s com.example.xucong.jnitest.Man指令充甚,就能夠獲取參數(shù)以政、方法的簽名,前面獲取成員變量的簽名的時候也可以通過這種方式:


image.png

其實這些步驟也是也可以偷懶的伴找,可以參考我AS NDK環(huán)境變量配置的文章的末尾片段盈蛮。

public native int accessStaticMethod(String filepath);
    //獲取uuid隨機文件名
    public static String getUUID() {
        return UUID.randomUUID().toString();
    }

//訪問靜態(tài)方法
//借用java api 產(chǎn)生一個UUID字符串,作為文件的名稱
JNIEXPORT jint JNICALL
Java_com_example_xucong_jnitest_Man_accessStaticMethod(JNIEnv *env, jobject jobj, jstring jstr_file_path) {

    jclass jclazz = (*env)->GetObjectClass(env,jobj);

    jmethodID jmid = (*env)->GetStaticMethodID(env,jclazz,"getUUID","()Ljava/lang/String;");

    jstring jstr_uuid = (*env)->CallStaticObjectMethod(env,jclazz,jmid);

    char *cstr_uuid = (*env)->GetStringUTFChars(env,jstr_uuid,JNI_FALSE);
    char *cstr_file_path = (*env)->GetStringUTFChars(env,jstr_file_path,JNI_FALSE);

    char filename[100];

    sprintf(filename,cstr_file_path,cstr_uuid);

    FILE *fp = fopen(filename,"w");
    fputs(filename,fp);
    fclose(fp);

}

java :
 String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "%s.txt";
        man.accessStaticMethod(path);
image.png

注意:6.0需要動態(tài)權(quán)限

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末技矮,一起剝皮案震驚了整個濱河市抖誉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌衰倦,老刑警劉巖袒炉,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異樊零,居然都是意外死亡我磁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門驻襟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來十性,“玉大人,你說我怎么就攤上這事塑悼【⑹剩” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵厢蒜,是天一觀的道長霞势。 經(jīng)常有香客問我,道長斑鸦,這世上最難降的妖魔是什么愕贡? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮巷屿,結(jié)果婚禮上固以,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好憨琳,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布诫钓。 她就那樣靜靜地躺著,像睡著了一般篙螟。 火紅的嫁衣襯著肌膚如雪菌湃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天遍略,我揣著相機與錄音惧所,去河邊找鬼。 笑死绪杏,一個胖子當(dāng)著我的面吹牛下愈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蕾久,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼驰唬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了腔彰?” 一聲冷哼從身側(cè)響起叫编,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎霹抛,沒想到半個月后搓逾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡杯拐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年霞篡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片端逼。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡朗兵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出顶滩,到底是詐尸還是另有隱情余掖,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布礁鲁,位于F島的核電站盐欺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏仅醇。R本人自食惡果不足惜冗美,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望析二。 院中可真熱鬧粉洼,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挫剑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柱衔,已是汗流浹背樊破。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留唆铐,地道東北人哲戚。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像艾岂,于是被迫代替她去往敵國和親顺少。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

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