本篇總結(jié)Android JNI如何通過兩種不同的構(gòu)建方式與C/C++進(jìn)行交互,話不多少先上兩張效果圖曲楚,注意:本項(xiàng)目是基于Android Studio3.0開發(fā)的。
以上就是與C/C++語言交互的內(nèi)容部分分褥符,涵蓋了開發(fā)基本上的需求龙誊,讓我進(jìn)入正題
兩種不同的構(gòu)建方式
以下介紹一種基于CMake的方式來構(gòu)建,一種是基于比較傳統(tǒng)的mk文件構(gòu)建方式喷楣,雖然基于CMake的方式更加的只能趟大,但是也是基于傳統(tǒng)的方式來的,這種方式只能適用于Android Studio 2.2以上的版本铣焊。無論基于什么構(gòu)建逊朽,首先曲伊,你必須要下載Android NDK開發(fā)包叽讳。點(diǎn)我下載
基于CMake方式
基于CMake方式構(gòu)建的項(xiàng)目,在寫C++和C代碼的時(shí)候可以與寫Java代碼一樣的有提示熊昌,而且還可以直接的debug绽榛。
打開一個(gè)項(xiàng)目,從菜單欄中選擇 Tools > Android > SDK Manager婿屹。
點(diǎn)擊 SDK Tools 選項(xiàng)卡灭美。
勾選 LLDB,CMake 和 NDK昂利。如圖:
- The Android Native Development Kit (NDK): 讓你能在 Android 上面使用 C 和 C++ 代碼的工具集届腐。
- CMake: 外部構(gòu)建工具。如果你準(zhǔn)備只使用 ndk-build 的話蜂奸,可以不使用它犁苏。
- LLDB: Android Studio 上面調(diào)試本地代碼的工具。
創(chuàng)建支持C/C++的項(xiàng)目
點(diǎn)擊新建項(xiàng)目扩所,勾選以下選項(xiàng)以支持C/C++
- C++ Standard:點(diǎn)擊下拉框围详,可以選擇標(biāo)準(zhǔn) C++,或者選擇默認(rèn) CMake 設(shè)置的 Toolchain Default 選項(xiàng)祖屏。
- Exceptions Support:如果你想使用有關(guān) C++ 異常處理的支持助赞,就勾選它。勾選之后袁勺,Android Studio 會(huì)在 module 層的 build.gradle 文件中的 cppFlags 中添加 -fexcetions 標(biāo)志雹食。
-
Runtime Type Information Support:如果你想支持 RTTI,那么就勾選它期丰。勾選之后群叶,Android Studio 會(huì)在 module 層的 build.gradle 文件中的 cppFlags 中添加 -frtti 標(biāo)志吃挑。
最后點(diǎn)擊Finish完成工程的創(chuàng)建。
與之前普通項(xiàng)目就多了這三個(gè)文件街立。 - .externalNativeBuild:這個(gè)文件夾與下面的CMakeLists.txt文件是對(duì)應(yīng)的舶衬,他主要由CMakeLists.txt文件來構(gòu)建so文件庫的文件夾。
- cpp:這個(gè)是存放C/C++源代碼的文件夾几晤。
- CMakeLists.txt:這個(gè)文件主要是構(gòu)建整個(gè)C++/C的腳本文件约炎。里面需要編寫構(gòu)建的代碼,有點(diǎn)類似Android.mk文件蟹瘾。
到這里,基于CMake的構(gòu)建就完成了掠手,點(diǎn)擊運(yùn)行可以直接看到來自C++的字符憾朴。
在這里,主要講將CMakeLists.txt的用法喷鸽,如何用了構(gòu)建:
先看如下代碼:
cmake_minimum_required(VERSION 3.4.1)
include_directories(src/main/cpp/include/)
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )
為了讓 CMake 將源代碼(native source code)編譯成 native library众雷。需要在編譯文件中添加 cmake_minimum_required() 和 add_library() 命令。
看下圖:
include_directories(src/main/cpp/include/)
這行代碼的意思就是當(dāng)你使用 add_library()做祝,將一個(gè)源文件(source file)或庫添加到你的 CMake 構(gòu)建腳本砾省,同步你的項(xiàng)目,然后你會(huì)發(fā)現(xiàn) Android studio 將關(guān)聯(lián)的頭文件也顯示了處理混槐。然而编兄,為了讓 CMake 在編譯時(shí)期能定位到你的頭文件,你需要在 CMake 構(gòu)建腳本中添加 include_directories() 命令声登,并指定頭文件路徑狠鸳。
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
這段代碼就是如果你的native-lib代碼中有引用別的頭文件,在這里就需要把頭文件鏈接到這里悯嗓,這個(gè)與add_library的順序是一致的件舵。
最后在Java代碼中l(wèi)oad的so庫是與上面native-lib是一致的。
static {
System.loadLibrary("native-lib");
}
至此脯厨,一個(gè)完整的jni項(xiàng)目就集成完畢铅祸,至于如何引用第三方C/C++的庫到CMake中,后期會(huì)介紹將librtmp合武,F(xiàn)Fmpeg集成到Android中來講解临梗。
傳統(tǒng)的構(gòu)建JNI項(xiàng)目的方式
這個(gè)方式比較古老了,相信從eclipse轉(zhuǎn)過來的童鞋都知道眯杏,這里我就不詳細(xì)講述這種方式的集成夜焦,這里推薦大家看這位大神的一篇博客:看不到我,看不到我
我這里就詳細(xì)總結(jié)一下Android.mk與Application.mk文件的編寫岂贩。
Android.mk
Android.mk是Android提供的一種makefile文件茫经,用來指定諸如編譯生成so庫名巷波、引用的頭文件目錄、需要編譯的.c/.cpp文件和.a靜態(tài)庫文件
最基本的格式應(yīng)該是這樣:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_xxx := xxx
LOCAL_MODULE := NdkTools
LOCAL_SRC_FILES := NdkTools.cpp
LOCAL_xxx := xxx
include $(BUILD_SHARED_LIBRARY)
- LOCAL_PATH:變量制定了該.mk的路徑卸伞,$(call my-dir)調(diào)用NDK內(nèi)部的函數(shù)獲得當(dāng)前.mk文件的路徑抹镊,每個(gè)Android.mk文件必須以定義LOCAL_PATH為開始。它用于在開發(fā)tree中查找源文件荤傲。宏my-dir 則由Build System提供垮耳。返回包含Android.mk的目錄路徑。
- include $(CLEAR_VARS):清空了除了LOCAL_PATH之外的所有LOCAL_xxx變量的值遂黍,CLEAR_VARS 變量由Build System提供终佛。并指向一個(gè)指定的GNU Makefile,由它負(fù)責(zé)清理很多LOCAL_xxx.
例如:LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES等等雾家。但不清理LOCAL_PATH.
這個(gè)清理動(dòng)作是必須的铃彰,因?yàn)樗械木幾g控制文件由同一個(gè)GNU Make解析和執(zhí)行,其變量是全局的芯咧。所以清理后才能避免相互影響牙捉。 - 中間就是對(duì)于模塊參數(shù)的設(shè)置,主要包括:模塊名字敬飒、模塊源文件邪铲、模塊類型、編譯好的模塊存放位置无拗、以及編譯的平臺(tái)等
- include $(BUILD_xxx_xxx): 執(zhí)行NDK的默認(rèn)腳本带到,它會(huì)收集include $(CLEAR_VARS)腳本后所有定義的LOCAL_xxx變量,然后根據(jù)它們來生成模塊蓝纲。BUILD_SHARED_LIBRARY:是Build System提供的一個(gè)變量阴孟,指向一個(gè)GNU Makefile Script。
它負(fù)責(zé)收集自從上次調(diào)用 include $(CLEAR_VARS) 后的所有LOCAL_XXX信息税迷。并決定編譯為什么永丝。
BUILD_STATIC_LIBRARY :編譯為靜態(tài)庫。
BUILD_SHARED_LIBRARY :編譯為動(dòng)態(tài)庫
BUILD_EXECUTABLE :編譯為Native C可執(zhí)行程序
BUILD_PREBUILT :該模塊已經(jīng)預(yù)先編譯
下面詳細(xì)說說中間比較常見的東西吧:
- LOCAL_MODULE := NdkTools:LOCAL_MODULE模塊必須定義箭养,以表示Android.mk中的每一個(gè)模塊慕嚷。名字必須唯一且不包含空格。Build System會(huì)自動(dòng)添加適當(dāng)?shù)那熬Y和后綴毕泌。例如喝检,NdkTools,要產(chǎn)生動(dòng)態(tài)庫撼泛,則生成libNdkTools.so. 但請(qǐng)注意:如果模塊名被定為:libNdkTools挠说,則生成libfoo.so. 不再加前綴
- LOCAL_MODULE_PATH :=$(TARGET_ROOT_OUT) 指定最后生成的模塊的目標(biāo)地址,TARGET_ROOT_OUT:根文件系統(tǒng)愿题,路徑為out/target/product/generic/root
TARGET_OUT:system文件系統(tǒng)损俭,路徑為out/target/product/generic/system
TARGET_OUT_DATA:data文件系統(tǒng)蛙奖,路徑為out/target/product/generic/data
除了上面的這些,NDK還提供了很多其他的TARGET_XXX_XXX變量杆兵,用于將生成的模塊拷貝到輸出目錄的不同路徑雁仲,默認(rèn)是TARGET_OUT - LOCAL_SRC_FILES := NdkTools.cpp:LOCAL_SRC_FILES變量必須包含將要打包如模塊的C/C++ 源碼。不必列出頭文件琐脏,build System 會(huì)自動(dòng)幫我們找出依賴文件攒砖。缺省的C++源碼的擴(kuò)展名為.cpp. 也可以修改,通過LOCAL_CPP_EXTENSION
可能以后還會(huì)經(jīng)常用到一些以“LOCAL_”開頭的編譯變量日裙,這些變量是啥意思吹艇,可以參考這篇文章:看不到我,看不到我
使用C代碼通過JNI與Java代碼溝通
好了阅签,前面鋪墊完了掐暮,接下才是編寫代碼中經(jīng)常需要用到的地方
- 傳遞Boolean類型
JNIEXPORT jboolean
JNICALL Java_com_dramascript_ndktest_NdkTools_getBoolean
(JNIEnv *evn, jobject obj, jboolean b) {
unsigned char bo = b;
bo = 1;
return (jboolean)bo;
}
- 傳遞byte類型
JNIEXPORT jbyte
JNICALL Java_com_dramascript_ndktest_NdkTools_getByte
(JNIEnv *evn, jobject obj, jbyte b) {
int i = b;
i = 15;
return (jbyte)
i;
}
- 傳遞char類型
JNIEXPORT jchar
JNICALL Java_com_dramascript_ndktest_NdkTools_getChar
(JNIEnv *evn, jobject obj, jchar c) {
char ch = c;
ch = 'S';
return (jchar)
ch;
}
- 傳遞short類型
JNIEXPORT jshort
JNICALL Java_com_dramascript_ndktest_NdkTools_getShort
(JNIEnv *evn, jobject obj, jshort s) {
short st = s;
st = 100;
return (jshort)
st;
}
- 傳遞int類型
JNIEXPORT jint
JNICALL Java_com_dramascript_ndktest_NdkTools_getInt
(JNIEnv *evn, jobject obj, jint i) {
int a = i;
a = 1024;
return (jint)
a;
}
- 傳遞long類型
JNIEXPORT jlong
JNICALL Java_com_dramascript_ndktest_NdkTools_getLong
(JNIEnv *evn, jobject obj, jlong l) {
long lg = l;
lg = 1024L;
return (jlong)
lg;
}
- 傳遞float類型
JNIEXPORT jfloat
JNICALL Java_com_dramascript_ndktest_NdkTools_getFloat
(JNIEnv *evn, jobject obj, jfloat f) {
float ft = f;
ft = 23;
return (jfloat)
ft;
}
- 傳遞double類型
JNIEXPORT jdouble
JNICALL Java_com_dramascript_ndktest_NdkTools_getDouble
(JNIEnv *evn, jobject obj, jdouble d) {
double dl = d;
dl = 2048.12;
return (jdouble)
dl;
}
- 傳遞string類型
JNIEXPORT jstring
JNICALL Java_com_dramascript_ndktest_NdkTools_getString
(JNIEnv *env, jobject obj, jstring s) {
char *st = (char *) env->GetStringUTFChars(s, 0);
char *str = "我很好!";
jstring rtn;
rtn = env->NewStringUTF(str);
return rtn;
}
- 傳遞數(shù)組類型
JNIEXPORT jbyteArray
JNICALL Java_com_dramascript_ndktest_NdkTools_getByteArray
(JNIEnv *evn, jobject obj, jbyteArray ba) {
//獲得byte數(shù)組
jbyte * bytes = evn->GetByteArrayElements(ba, 0);
int chars_len = evn->GetArrayLength(ba);
//返回新的byte數(shù)組
jbyteArray arr = evn->NewByteArray(6);
jbyte * by = evn->GetByteArrayElements(arr, 0);
char ch[10] = "abcd";
for (int i = 0; i < 4; ++i) {
by[i] = ch[i];
}
evn->SetByteArrayRegion(arr, 0, 6, by);
return arr;
}
- 傳遞對(duì)象類型
JNIEXPORT jobject
JNICALL Java_com_dramascript_ndktest_NdkTools_getObject
(JNIEnv *env, jobject obj, jobject paramIn) {
//獲取
jclass paramInClass = env->GetObjectClass(paramIn);
if (paramInClass == NULL) {
return NULL;
}
if (env->IsInstanceOf(obj, paramInClass))//判斷jobject是否是某個(gè)jclass類型政钟。
{
jboolean iscopy;
jfieldID intId = env->GetFieldID(paramInClass, "age", "I");
jint num = (int) env->GetIntField(paramIn, intId);
jfieldID strId = env->GetFieldID(paramInClass, "name", "Ljava/lang/String;");
jstring str = (jstring)(env)->GetObjectField(paramIn, strId);
const char *locstr = env->GetStringUTFChars(str, &iscopy);
env->ReleaseStringUTFChars(str, locstr);
}
//返回
jclass cls = env->FindClass("com/dramascript/ndktest/User");
jmethodID id = env->GetMethodID(cls, "<init>", "()V");
jobject paramOut = env->NewObjectA(cls, id, 0);
jfieldID intId = env->GetFieldID(cls, "age", "I");
env->SetIntField(paramOut, intId, 23);
jfieldID strId = env->GetFieldID(cls, "name", "Ljava/lang/String;");
env->SetObjectField(paramOut, strId, (jstring)(env)->NewStringUTF("林俊杰"));
return paramOut;
}
- 傳遞數(shù)組對(duì)象類型
JNIEXPORT jobjectArray
JNICALL Java_com_dramascript_ndktest_NdkTools_getObjectArray
(JNIEnv *env, jobject _obj, jobjectArray objarr) {
//獲取
//JNI提供了兩個(gè)函數(shù)來訪問對(duì)象數(shù)組弛针,GetObjectArrayElement返回?cái)?shù)組中指定位置的元素摧找,
// SetObjectArrayElement修改數(shù)組中指定位置的元素。與基本類型不同的是枕荞,我們不能一次得到數(shù)據(jù)
// 中的所有對(duì)象元素或者一次復(fù)制多個(gè)對(duì)象元素到緩沖區(qū)瓢宦。
jobject objects = env->GetObjectArrayElement(objarr, 0);//返回第0個(gè)對(duì)象
jsize length = (env)->GetArrayLength(objarr);
//返回
//申明一個(gè)object數(shù)組
jobjectArray args = 0;
//獲取object所屬類,一般為ava/lang/Object就可以了
jclass objClass = (env)->FindClass("java/lang/Object");
//新建object數(shù)組
args = (env)->NewObjectArray(5, objClass, 0);
//給每一個(gè)實(shí)例的變量付值碎连,并且將實(shí)例作為一個(gè)object,添加到objcet數(shù)組中
for (int i = 0; i < 5; i++) {
jclass cls = env->FindClass("com/dramascript/ndktest/User");
jmethodID id = env->GetMethodID(cls, "<init>", "()V");
jobject paramOut = env->NewObjectA(cls, id, 0);
jfieldID intId = env->GetFieldID(cls, "age", "I");
env->SetIntField(paramOut, intId, 23);
jfieldID strId = env->GetFieldID(cls, "name", "Ljava/lang/String;");
env->SetObjectField(paramOut, strId, (jstring)(env)->NewStringUTF("書戲"));
//添加到objcet數(shù)組中
(env)->SetObjectArrayElement(args, i, paramOut);
}
return args;
}
JNIEXPORT jobject
JNICALL Java_com_dramascript_ndktest_NdkTools_getObjectList
(JNIEnv *env, jobject obj) {
jclass list_cls = env->FindClass("Ljava/util/ArrayList;");//獲得ArrayList類引用
jmethodID list_costruct = env->GetMethodID(list_cls, "<init>", "()V"); //獲得得構(gòu)造函數(shù)Id
jobject list_obj = env->NewObject(list_cls, list_costruct); //創(chuàng)建一個(gè)Arraylist集合對(duì)象
//或得Arraylist類中的 add()方法ID驮履,其方法原型為: boolean add(Object object) ;
jmethodID list_add = env->GetMethodID(list_cls, "add", "(Ljava/lang/Object;)Z");
jclass cls = env->FindClass("com/dramascript/ndktest/User");
jmethodID id = env->GetMethodID(cls, "<init>", "()V");
for (int i = 0; i < 3; i++) {
jobject paramOut = env->NewObject(cls, id);
env->CallBooleanMethod(list_obj, list_add, paramOut); //執(zhí)行Arraylist類實(shí)例的add方法鱼辙,添加一個(gè)stu對(duì)象
}
return list_obj;
}
- C層代碼調(diào)用Java代碼的某個(gè)方法
JNIEXPORT void JNICALL Java_com_dramascript_ndktest_NdkTools_callSetData
(JNIEnv *env, jobject obj){
jclass clz = env->FindClass("com/dramascript/ndktest/NdkTools");
jmethodID mid = env->GetMethodID(clz, "setData", "(Ljava/lang/String;I)V");
env->CallVoidMethod(obj, mid, env->NewStringUTF("haha in C ."),20);
}
注意:在C++中調(diào)用JNI頭文件的方法和在C代碼中調(diào)用的方法傳的參數(shù)是不一樣的,如下:
//在C++可以少env這個(gè)參數(shù)
jfieldID intId = env->GetFieldID(paramInClass, "age", "I");
//在C里面是不能缺少的
jfieldID intId = (*env)->GetFieldID(*env,paramInClass, "age", "I");
至此玫镐,結(jié)束倒戏,代碼在:Demo代碼,如果覺得不錯(cuò)恐似,給點(diǎn)打賞唄杜跷。