1. 概述
在Android Studio 2.2之后,可以使用CMake來進(jìn)行NDK開發(fā),C/C++開發(fā)的便利性又提升了不少。這個(gè)是個(gè)好事,比較CMake使用起來還是比make要簡(jiǎn)單,并且抽象、跨平臺(tái)。例如在linux可以生產(chǎn)linux下的makefile,在windows下可以生產(chǎn)Visual Studio的工程文件。
這里需要解析幾個(gè)名詞:
- NDK
Android Native Development Kit,里面包含各個(gè)平臺(tái)上的C/C++編譯器、相關(guān)頭文件和庫(kù)(相當(dāng)于Java的庫(kù))。
- CMake
一套構(gòu)建系統(tǒng),類似Gradle,但是CMake不直接參與編譯,而是產(chǎn)生其他構(gòu)建系統(tǒng)的工程文件,再進(jìn)行編譯,在Android Studio當(dāng)中,Gradle插件會(huì)驅(qū)動(dòng)CMake產(chǎn)生各個(gè)平臺(tái)(armeabi、armeabi-v7a、x86等)的ninja的構(gòu)建文件,再驅(qū)動(dòng)編譯器進(jìn)行編譯。
- LLDB
調(diào)試器,可以用來調(diào)試原生代碼,之前版本使用的是GDB。
2. 準(zhǔn)備
本文是基于Android Studio 2.3來講解的,因此需要升級(jí)到2.3。安裝好2.3之后,打開【SDK Manager】

勾選:【CMake】和【NDK】?jī)蓚€(gè)選項(xiàng),然后點(diǎn)擊【Apply】進(jìn)行安裝
因?yàn)間oogle在國(guó)內(nèi)假設(shè)了鏡像站點(diǎn),現(xiàn)在不需要使用[可不描述]來更新SDK了
3. 實(shí)踐
3.1 創(chuàng)建項(xiàng)目
創(chuàng)建項(xiàng)目的流程,官方文檔也有: https://developer.android.com/studio/projects/add-native-code.html
因此,我這里會(huì)在流程上補(bǔ)充一些說明。
新建項(xiàng)目,在第一頁(yè)中勾選:

【include c++ support】來支持C++開發(fā),如果已有項(xiàng)目沒有勾選也沒關(guān)系,可以在菜單中【link 】
一路next來到最后一頁(yè),定制你的C++項(xiàng)目支持

主要有三個(gè)參數(shù)組成:
- C++標(biāo)準(zhǔn)
現(xiàn)在基本都是C++ 11開發(fā)了,如果不是要維護(hù)非常老的代碼,建議選擇C++11。
C++11相對(duì)于之前的版本(C++03)增加的功能非常豐富,具體可以參考這篇文章: http://blog.csdn.net/zhuxianjianqi/article/details/8658169
- Exception Support
異常支持,如果取消掉的話,那么就不能使用 try-catch 進(jìn)行異常處理了,建議選擇。
- Runtime Type Information Support
運(yùn)行時(shí)類型信息支持,在C++運(yùn)行的時(shí)候,不像Java、C#等一樣,可以動(dòng)態(tài)獲取對(duì)象的類信息,開啟這個(gè)選項(xiàng)來支持這個(gè)功能,建議選擇。
到這里,項(xiàng)目就創(chuàng)建完畢了,點(diǎn)擊 run 按鈕,APP就可以在模擬器或者android設(shè)備上運(yùn)行。
3.2 工程結(jié)構(gòu)
打開代碼所在的目錄,進(jìn)入APP子模塊,可以看到相比傳統(tǒng)的APP項(xiàng)目,會(huì)多出以下文件或者目錄:
- CMakeList.txt
CMake的工程文件,相當(dāng)于 build.gradle 用于說明編譯那些C/C++源碼,以及相關(guān)的編譯參數(shù)
- src/main/cpp
C/C++源碼目錄
- .externalNativeBuild
該文件夾是臨時(shí)文件夾,gradle插件會(huì)調(diào)用cmake產(chǎn)生各個(gè)平臺(tái)的臨時(shí)構(gòu)建文件,都存放在該目錄
3.3 編譯流程
需要注意的是,cmake并不能直接編譯 c/c++ 源碼,需要產(chǎn)生 ninja 的項(xiàng)目文件,才會(huì)編譯,其流程大體是這樣的。
生成ninja工程 ---> 編譯/鏈接 ---> APP打包
對(duì)于 產(chǎn)生ninja工程,可以通過下述三種方式:
- 修改CMakeLists.txt,然后執(zhí)行
Build菜單的Make Project或者Make module或者直接Run - 執(zhí)行
Gradle Sync - 執(zhí)行
Build菜單的Refresh Linked C++ Projects
在實(shí)際使用中,有時(shí)候修改CMakeLists.txt不會(huì)重新產(chǎn)生ninja工程文件,導(dǎo)致編譯會(huì)出現(xiàn)問題,所以官方可能也留了 Refresh Linked C++ Projects 給到大家手動(dòng)刷新。另外,手動(dòng)添加 C/C++源碼的時(shí)候,也可以通過這種方式刷新工程。
4. 編譯參數(shù)解析
4.1 build.gradle
該文件可以指定工具鏈的大部分核心參數(shù),里面的源碼大致如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "25.0.3"
defaultConfig {
applicationId "com.test.ndkhelloworld"
...
// 該代碼塊用于配置相關(guān)的參數(shù)
externalNativeBuild {
cmake {
cppFlags "-std=c++11 -frtti -fexceptions"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// 該代碼塊用于鏈接到指定的CMakeLists.txt,路徑是相對(duì)路徑
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
...
這里有兩個(gè) externalNativeBuild 代碼塊
android.externalNativeBuild
ExternalNativeBuild 對(duì)象:http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.ExternalNativeBuild.html
主要配置 cmake 或者 ndk-build 的工程文件路徑
- `android.defaultConfig.externalNativeBuild
ExternalNativeBuildOptions 對(duì)象: http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.ExternalNativeBuild.html
具體的參數(shù)配置。后面都是以這個(gè)配置參數(shù)來講解。
4.2 CMake變量解析
可以通過 arguments 命令來傳遞CMake構(gòu)建參數(shù)(這些參數(shù),實(shí)際會(huì)傳遞到NDK的構(gòu)建工具鏈),形式為: -D參數(shù)名=參數(shù)值1 參數(shù)值2,需要注意的是,如果有多個(gè)參數(shù),那么必須換行來傳遞,例如:
externalNativeBuild {
cmake {
arguments "-DANDROID_TOOLCHAIN=gcc"
"-DANDROID_STL=stlport_static"
}
}
以下是常用的變量:
- ANDROID_TOOLCHAIN
編譯工具鏈,可選:clang(默認(rèn))和gcc(已經(jīng)過期)。
- ANDROID_PLATFORM
android平臺(tái),例如:android-18 注意,該取值會(huì)影響到原生API的時(shí)候,有些原生API在低版本的android是沒有的,詳見: https://developer.android.com/ndk/guides/stable_apis.html
- ANDROID_STL
STL(標(biāo)準(zhǔn)模板庫(kù))的選擇,NDK自帶了很多個(gè)版本的STL,功能大體上是一樣的,但是授權(quán)會(huì)不一樣。詳細(xì)請(qǐng)閱讀: https://developer.android.com/ndk/guides/cpp-support.html#hr
(stlport已經(jīng)實(shí)現(xiàn)異常處理了,在低版本的NDK是不支持的)
- ANDROID_PIE
啟用PIE(position-independent executables),如果是 ANDROID_PLATFORM = android-16,該選項(xiàng)默認(rèn)開啟(取值為 ON),否則為 OFF
- ANDROID_CPP_FEATURES
C++的功能,取值為: rtti 和 exceptions,也可以使用 cppFlags 指定
- ANDROID_ALLOW_UNDEFINED_SYMBOLS
允許未定義的符號(hào),默認(rèn)為 FALSE,可以取值為:TRUE。
- ANDROID_ARM_MODE
指令集模式,默認(rèn)為: thumb ,可以取值為: arm
- ANDROID_ARM_NEON
是否啟用NEON,默認(rèn)為FALSE,啟用為:TRUE
- ANDROID_DISABLE_FORMAT_STRING_CHECKS
是否禁用字符串格式化檢查,默認(rèn)為:FALSE,可以取值為:TRUE,建議默認(rèn)值,不要禁用,因?yàn)楹芏嗦┒椿蛘連UG都出現(xiàn)在字符串格式化上面。
4.3 abi過濾器
通過 abiFilters 可以編譯出指定ABI的二進(jìn)制文件,可選值為:armeabi、armeabi-v7a、arm64-v8a、mips、mips64、x86以及x86_64
默認(rèn)情況下,編譯出來的都包含上述的ABI的二進(jìn)制文件,如下圖:

可以清楚的看到,編譯出來的二進(jìn)制文件(庫(kù))可以在ARM、X86和MIPS所有平臺(tái)上運(yùn)行。實(shí)際上,我們想給APK瘦身的,不需要在那么多平臺(tái)上運(yùn)行,可以取消掉一些平臺(tái)的支持,例如我們只支持armeabi和armeabi-v7a,X86和MIPS都不需要
externalNativeBuild {
cmake {
abiFilters "armeabi", "armeabi-v7a"
cppFlags "-std=c++11 -frtti -fexceptions"
arguments "-DANDROID_TOOLCHAIN=gcc",
"-DANDROID_STL=stlport_static"
}
}
執(zhí)行clean之后再產(chǎn)生APK,可以看到,只有兩個(gè)ABI的二進(jìn)制產(chǎn)生

我發(fā)現(xiàn)一個(gè)問題,即使sync、clean等一系列的操作后,不會(huì)刪除原有產(chǎn)生的ninja工程文件,可以先手動(dòng)刪除掉 .externalNativeBuild 目錄再重試一下。
4.4 傳遞C/C++參數(shù)
cFlags和cppFlags可以傳遞C/C++編譯參數(shù),這里不詳細(xì)討論。