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

勾選:【CMake】和【NDK】兩個選項,然后點擊【Apply】進行安裝
因為google在國內假設了鏡像站點,現(xiàn)在不需要使用[可不描述]來更新SDK了
3. 實踐
3.1 創(chuàng)建項目
創(chuàng)建項目的流程,官方文檔也有: https://developer.android.com/studio/projects/add-native-code.html
因此,我這里會在流程上補充一些說明。
新建項目,在第一頁中勾選:

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

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

可以清楚的看到,編譯出來的二進制文件(庫)可以在ARM、X86和MIPS所有平臺上運行。實際上,我們想給APK瘦身的,不需要在那么多平臺上運行,可以取消掉一些平臺的支持,例如我們只支持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之后再產生APK,可以看到,只有兩個ABI的二進制產生

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