上一篇簡單的介紹了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新建一個工程
把支持c++選項勾選上,這時候如果你沒有配好ndk-bound询刹,需要到工程的屬性設(shè)置里面配置:
配置好以后蔽挠,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é)果:
以上是簡單使用轰枝。
- 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é)果:
- 訪問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);
可以看出來,步驟和前面一樣囚似,只是訪問靜態(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的指令集
cd 到app/build/intermediates/debug目錄下逃顶,里面有編譯好的class文件,執(zhí)行javap -p -s com.example.xucong.jnitest.Man指令充甚,就能夠獲取參數(shù)以政、方法的簽名,前面獲取成員變量的簽名的時候也可以通過這種方式:
其實這些步驟也是也可以偷懶的伴找,可以參考我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);
注意:6.0需要動態(tài)權(quán)限