Android JNI開發(fā)系列之Java與C相互調(diào)用

這是這個系列的第二篇肴盏,第一篇介紹了如何配置烛愧。這一篇介紹Java與C如何相互介紹摘能。

沒有配置過的可以去看看Android JNI開發(fā)系列之配置

首先介紹的就是Java如何調(diào)用C昂勉,而C調(diào)用Java核心使用的就是反射脂凶,下面會以此介紹宪睹。

一、Java調(diào)用C

第一篇中有個簡單的例子蚕钦,就是使用Java調(diào)用C亭病,調(diào)用一個無參的native函數(shù),并返回一個String嘶居,下面接著說點更多的情況:

  • 基本類型對應(yīng)情況
  • 字符串處理
  • 數(shù)組的處理

基本類型對應(yīng)情況

因為Java和C的基本類型也有些許區(qū)別罪帖,而在這兩者之間還有一個jni的類型作為橋梁連接轉(zhuǎn)換類型,有一張圖特別好邮屁,一看就清楚了整袁,借了一下這位作者文章中的圖,表示感謝佑吝。

type_relationship.png

下邊對于數(shù)據(jù)的處理就是基于這些類型去處理的坐昙。

字符串的處理

1、首先先來一個字符串的拼接

這個也是坑了我這個萌新不少芋忿,體會到其實Java的垃圾回收機(jī)制還是很方便的炸客。

其中在c中字符串的拼接主要就是使用strcat方法,導(dǎo)入#include<string.h>包盗飒。

還是老樣子嚷量,先定義一個native方法,對于配置都是在上一篇的基礎(chǔ)上的:

public class Hello {
    static {
        System.loadLibrary("Hello");
    }

    //傳入一個字符串逆趣,拼接一段字符串后返回
    public native String sayHello(String msg);
}

接著在Hello.c文件中寫這個方法蝶溶,這里有兩種方法去寫這個方法,第一種是手動自己寫宣渗,也有點技巧:

  • 首先看到返回的是String抖所,對應(yīng)的就是jstring
  • 然后函數(shù)名就是:Java_類完全限定名_方法名,其中完全限定名痕囱,可以在Hello這個類上右鍵->Copy Reference田轧,然后再把名字中間的點改為下劃線
  • 然后函數(shù)的參數(shù):前兩個參數(shù)必須的鞍恢,JNIEnv *env, jobject instance属百,然后第三個參數(shù)開始就是在Java中定義的方法的參數(shù),這里傳入了一個String卦停,在這里的就改為jstring msg,方法如下:
jstring Java_net_arvin_androidstudy_jni_Hello_sayHello(JNIEnv *env, jobject instance,
                                                       jstring msg) {
    // implement code...
}

還有一種方法就是使用javah命令窒典,處理.java文件就能得到定義的.h文件;方法就是在該項目的java目錄下稽莉,使用命令javah 類的完全限定名瀑志,在我這個項目里就是:
javah net.arvin.androidstudy.jni.Hello

這樣在java目錄下就有一個net_arvin_androidstudy_jni_Hello.h文件,打開可以看到這個方法:

JNIEXPORT jstring JNICALL Java_net_arvin_androidstudy_jni_Hello_sayHello
  (JNIEnv *, jobject, jstring);

其中JNIEXPORT和JNICALL關(guān)鍵字都可以去掉的污秆,去掉后就和上邊的方法一樣了劈猪,然后自己去把參數(shù)的名字補(bǔ)充上即可。

最后對于字符串的拼接良拼,沒啥好說的战得,我這里提供一種方式:

jstring Java_net_arvin_androidstudy_jni_Hello_sayHello(JNIEnv *env, jobject instance,
                                                       jstring msg) {
    char *fromJava = (char *) (*env)->GetStringUTFChars(env, msg, JNI_FALSE);
    char *fromC = " add I am from C~";
    char *result = (char *) malloc(strlen(fromJava) + strlen(fromC) + 1);
    strcpy(result, fromJava);
    strcat(result, fromC);
    return (*env)->NewStringUTF(env, result);
}
  • 先將jstring轉(zhuǎn)為char*
  • 然后把要拼接的字符串定義出來
  • 接著關(guān)鍵來了,動態(tài)申請一塊區(qū)域用于存儲拼接后的字符串庸推,申請的長度就是傳進(jìn)來的字符串和要添加的長度之和
  • 接著就是把這兩個字符串拼在一起贡避,先使用strcpy是因為result還沒有初始化,相當(dāng)于把fromJava賦值給result予弧,然后再把fromC拼接到result中
  • 最后就是使用NewStringUFT將char*轉(zhuǎn)換成jstring

最后就是去調(diào)用,這就簡單了湖饱。

Hello jni = new Hello();
String result = jni.sayHello("I am from Java");
Log.d(TAG, result);
2掖蛤、字符串比較

有了上文的介紹,這個比較就比較簡單井厌,核心就是使用strcmp方法蚓庭,Java代碼如下:

public class Hello {
    static {
        System.loadLibrary("Hello");
    }

    //如果是c中要求的就返回200,否則就返回400
    public native int checkStr(String str);

}

c代碼如下:

jint Java_net_arvin_androidstudy_jni_Hello_checkStr
        (JNIEnv *env, jobject instance, jstring jstr) {
    char *input = (char *) (*env)->GetStringUTFChars(env, jstr, JNI_FALSE);
    char *real = "123456";
    return strcmp(input, real) == 0 ? 200 : 400;
}

這里就不接著介紹其他的處理方法了仅仆,需要時可以自己搜一下器赞。

處理數(shù)組

同樣有了上文的基礎(chǔ),Java代碼如下:

public class Hello {
    static {
        System.loadLibrary("Hello");
    }

    public native void increaseArray(int[] arr);

}

C代碼如下:

void Java_net_arvin_androidstudy_jni_Hello_increaseArray
        (JNIEnv *env, jobject instance, jintArray arr) {
    jsize length = (*env)->GetArrayLength(env, arr);
    jint *elements = (*env)->GetIntArrayElements(env, arr, JNI_FALSE);
    for (int i = 0; i < length; i++) {
        elements[i] += 10;
    }
    (*env)->ReleaseIntArrayElements(env, arr, elements, 0);
}

可以看到:

  • GetArrayLength:獲取數(shù)組長度
  • GetIntArrayElements:從java數(shù)組獲取數(shù)組指針墓拜,注意JNI_FALSE這個參數(shù)港柜,代碼是否復(fù)制一份,false表示不復(fù)制咳榜,直接使用java數(shù)組的內(nèi)存地址
  • for循環(huán)夏醉,每個數(shù)組元素都加10
  • 最后釋放本地數(shù)組內(nèi)存,最后一個參數(shù)涌韩,0表示將值修改到j(luò)ava數(shù)組中畔柔,然后釋放本地數(shù)組,這個參數(shù)還有兩個可選值:JNI_COMMIT和JNI_ABORT臣樱,前一個修改值到j(luò)ava數(shù)組靶擦,但是不釋放本地數(shù)組內(nèi)存腮考,后一個,不修改值到j(luò)ava數(shù)組玄捕,但是會釋放本地數(shù)組內(nèi)存踩蔚。

到這里Java調(diào)用C的介紹就到這里,方法基本介紹了桩盲,但是如何更好的運(yùn)用還需努力實踐寂纪。

C調(diào)用Java

上文中說到這個操作,主要是利用反射赌结,這樣就能調(diào)用Java代碼了捞蛋。

對于配置都不說了,也直接上代碼柬姚,主要的細(xì)節(jié)都是在反射那里拟杉。

先來一個C調(diào)用Java無參無返回值的函數(shù),Java代碼如下:

public class CallJava {
    static {
        System.loadLibrary("Hello");
    }

    private static final String TAG = "CallJava";

    //調(diào)用無參量承,無返回函數(shù)
    public native void callVoid();

    public void hello() {
        Log.d(TAG, "Java的hello方法");
    }
}

可以看到這里換了一個類了搬设,但是沒有影響,之后會介紹這一塊知識撕捍。

C代碼:

//調(diào)用public void hello()方法
void Java_net_arvin_androidstudy_jni_CallJava_callVoid
        (JNIEnv *env, jobject instance) {
    jclass clazz = (*env)->FindClass(env, "net/arvin/androidstudy/jni/CallJava");
    jmethodID method = (*env)->GetMethodID(env, clazz, "hello", "()V");
    jobject object = (*env)->AllocObject(env, clazz);
    (*env)->CallVoidMethod(env, object, method);
}

這個就是四部曲:

  • 獲取Java中的class
  • 獲取對應(yīng)的函數(shù)
  • 實例化該class對應(yīng)的實例
  • 調(diào)用方法
獲取Java中的class

第一步:使用FindClass方法拿穴,第二個參數(shù),就是要調(diào)用的函數(shù)的類的完全限定名忧风,但是需要把點換成/

獲取對應(yīng)的函數(shù)

第二步:使用GetMethodID方法默色,第二個參數(shù)就是剛得到的類的class,第三個就是方法名狮腿,第四個就是該函數(shù)的簽名腿宰,這里有個技巧,使用javap -s 類的完全限定名就能得到該函數(shù)的簽名缘厢,但是需要在build->intermediates->classes->debug目錄下吃度,使用該命令,得到如下結(jié)果:

//else method...

public void hello();
    descriptor: ()V

descriptor:后邊的就是該方法的簽名

實例化該class對應(yīng)的實例

第三步:使用AllocObject方法贴硫,使用clazz創(chuàng)建該class的實例椿每。

調(diào)用方法

第四步:使用CallVoidMethod方法,可以看到這個就是調(diào)用返回為void的方法英遭,第二個參數(shù)就是第三步中創(chuàng)建的實例拖刃,第三個參數(shù)就是上邊創(chuàng)建的要調(diào)用的方法。

有了這個四部就能在C中吊起Java中的代碼了贪绘。

而對于有參兑牡,有返回的方法,在這四部曲的基礎(chǔ)上税灌,只需要修改第二步獲取方法的名字和簽名均函,其中簽名以及第四步的Call<Type>Method方法亿虽,Type可以是int,string苞也,boolean洛勉,float等等。

提示:對于基本類型又個技巧如迟,括號內(nèi)依次是參數(shù)的類型的縮寫收毫,括號右邊是返回類型的縮寫,用得多了就可以不用每次都去使用命令查詢了殷勘,但是開始最好還是都查一下此再,免得出錯

但是對于靜態(tài)方法的調(diào)用就應(yīng)該使用GetStaticMethodIDCallStaticVoidMethod了,而對于靜態(tài)方法就不需要實例化對象玲销,相對來說還少一步输拇。

到這里,可能有使用過java的反射的同學(xué)有疑問了贤斜,如果是去調(diào)用private的方法策吠,會不會報錯呢,這個可以告訴你瘩绒,我試過了猴抹,也是可以調(diào)用起來的,沒有問題锁荔,不用擔(dān)心啦洽糟。

到這里,Java調(diào)用C堕战,C調(diào)用Java基本就算是完成了,這個代碼我也會上傳到github上拍霜,需要的同學(xué)可以自行下載比對嘱丢,有不足之處也請多多指教。地址在文末祠饺。

添加多個C文件的配置

前文中說了越驻,對于多文件的配置會在之后的文章中說到,果然道偷,在第二篇中缀旁,想著方法太多了,我想放到別的文件中去處理勺鸦,避免混亂了并巍,所以就去了解了一下,在此告訴大家换途,其實很簡答懊渡。

首先刽射,在之前的配置基礎(chǔ)上,再在cpp目錄下創(chuàng)建一個文件剃执,例如這里叫做Test.c誓禁,然后再到CMakeLists.txt文件中關(guān)聯(lián)上就行了,關(guān)聯(lián)方式如下:

cmake_minimum_required(VERSION 3.4.1)

add_library(Hello
            SHARED
            src/main/cpp/Hello.c
            src/main/cpp/Test.c)

對比之前的配置肾档,對了一行src/main/cpp/Test.c相當(dāng)于把Test.c文件也關(guān)聯(lián)到叫做Hello的這個lib中摹恰。

雖然現(xiàn)在c代碼也可以調(diào)試debug了,但是還是有打印日志才方便怒见,printf是沒有用的俗慈,所以需要我們手動去添加一個日志庫,首先在CMakeLists.txt中添加成如下:

cmake_minimum_required(VERSION 3.4.1)

add_library(Hello
            SHARED
            src/main/cpp/Hello.c
            src/main/cpp/Test.c)

find_library(log-lib log)

target_link_libraries(Hello ${log-lib})

多了后兩句代碼速种。然后再需要用到的地方申明:

#include "android/log.h"

#define LOG_TAG "JNI_TEST"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

這樣就能在這個類中使用了:

  • LOGD:debug級別日志
  • LOGI:info級別日志
  • LOGE:error級別日志

這里就有個技巧了姜盈,定義一個Log.c文件,導(dǎo)入上文中的配置配阵,然后在需要用日志的地方引入Log.c即可馏颂。

這樣就不用在每個文件開頭都去申明這些東西了。

示例代碼

Android JNI學(xué)習(xí)

在這個項目中棋傍,java代碼在包下的jni下救拉,配置也可在相應(yīng)位置查看。

感謝

部分代碼來源尚硅谷Android視頻《JNI》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瘫拣,一起剝皮案震驚了整個濱河市亿絮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌麸拄,老刑警劉巖派昧,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拢切,居然都是意外死亡蒂萎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門淮椰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來五慈,“玉大人,你說我怎么就攤上這事主穗⌒豪梗” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵忽媒,是天一觀的道長争拐。 經(jīng)常有香客問我,道長晦雨,這世上最難降的妖魔是什么陆错? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任灯抛,我火速辦了婚禮,結(jié)果婚禮上音瓷,老公的妹妹穿的比我還像新娘对嚼。我一直安慰自己,他們只是感情好绳慎,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布纵竖。 她就那樣靜靜地躺著,像睡著了一般杏愤。 火紅的嫁衣襯著肌膚如雪靡砌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天珊楼,我揣著相機(jī)與錄音通殃,去河邊找鬼。 笑死厕宗,一個胖子當(dāng)著我的面吹牛画舌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播已慢,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼曲聂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了佑惠?” 一聲冷哼從身側(cè)響起朋腋,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎膜楷,沒想到半個月后旭咽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡赌厅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年穷绵,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片察蹲。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖催训,靈堂內(nèi)的尸體忽然破棺而出洽议,到底是詐尸還是另有隱情,我是刑警寧澤漫拭,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布亚兄,位于F島的核電站,受9級特大地震影響采驻,放射性物質(zhì)發(fā)生泄漏审胚。R本人自食惡果不足惜匈勋,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望膳叨。 院中可真熱鬧洽洁,春花似錦、人聲如沸菲嘴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽龄坪。三九已至昭雌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間健田,已是汗流浹背烛卧。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留妓局,地道東北人总放。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像跟磨,于是被迫代替她去往敵國和親间聊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

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