Android NDK以前默認使用Android.mk與Application.mk進行構建,但是在Android Studio2.2之後推薦使用CMake進行編譯。CMake是跨平臺編譯工具,全稱爲cross platform make,內建c、c++、java自動相依性分析功能。NDK通過工具鏈支持CMake,工具鏈文件是用於自定義交叉編譯工具鏈的CMake文件。用於NDK的工具鏈位於<NDK>/build/cmake/android.toolchain.cmake,關於CMake更多詳情請參考官網:https://cmake.org/documentation/。下面我們對比下Android.mk與CMakeLists.txt的語法。
一、Android.mk語法
以動態庫編譯hello模塊爲例,完整腳本如下:
WORKING_DIR := $(call my-dir)
LOCAL_PATH := $(WORKING_DIR)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_MODULE := libffmpeg
LOCAL_SRC_FILES := $(LOCAL_PATH)/ffmpeg/$(TARGET_ARCH_ABI)/lib$(LOCAL_MODULE).so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/ffmpeg/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
#模塊名稱
LOCAL_MODULE := hello
#源文件
LOCAL_SRC_FILES := hello.c
#頭文件路徑
LOCAL_C_INCLUDES := $(LOCAL_PATH)
#系統庫依賴
LOCAL_LDLIBS := -llog -lz -lm -landroid
#第三方動態庫
LOCAL_SHARED_LIBRARIES := libffmpeg
#以動態庫形式編譯
include $(BUILD_SHARED_LIBRARY)
1、LOCAL_MODULE
聲明模塊名稱,例如這裏編譯出來的動態庫名稱爲libhello.so。
2、LOCAL_SRC_FILES
聲明源文件列表,文件之間用空格分開,需要換行時使用'\'換行符。
3、LOCAL_C_INCLUDES
聲明頭文件路徑,例如$(LOCAL_PATH)/xxx
4、LOCAL_CPP_EXTENSION
指定C++源文件除.cpp以外的文件擴展名,例如這樣LOCAL_CPP_EXTENSION := .cpp .cxx .cc
5、LOCAL_CPP_FEATURES
指定依賴c++的某些功能,例如RTTI(運行時類型信息):
LOCAL_CPP_FEATURES := rtti
使用c++異常檢測:
LOCAL_CPP_FEATURES := exceptions
6、LOCAL_CFLAGS
在編譯c和c++源文件時編譯系統要傳遞的編譯器標記,即指定額外的宏定義或編譯選項。
LOCAL_CFLAGS += -I<path>
7、LOCAL_STATIC_LIBRARIES
共享靜態庫,作爲第三方庫被引用
LOCAL_STATIC_LIBRARIES := libavcodec libavutil libavformat libavfilter
8、LOCAL_SHARED_LIBRARIES
共享動態庫,與共享靜態庫一樣作爲第三方庫被引用
LOCAL_SHARED_LIBRARIES := libffmpeg
9、LOCAL_LDLIBS
額外鏈接器,一般爲系統庫,使用-l來引用
LOCAL_LDLIBS := -lz -lm
10、 LOCAL_ARM_MODE
ndk默認使用thumb模式來生成目標二進制文件,每條指令爲16位寬。也可以指定爲ARM模式,來生成32位ARM的目標文件:
LOCAL_ARM_MODE := arm
11、LOCAL_ARM_NEON
用於開啓NEON指令加速,僅對armeabi-v7a平臺有效。爲模塊開啓NEON:
LOCAL_ARM_NEON := true
爲單獨源文件開啓NEON:
LOCAL_SRC_FILES := hello.c.neon
12、TARGET_ARCH
用於指向CPU架構,包括x86、x86_64、armeabi-v7a、 arm64-v8a
13、TARGET_PLATFORM
目標平臺,對應Android API級別號,例如Android5.0系統鏡像對應Android API級別21:android-21
14、打印信息
可用warning、debug、info、error級別來打印信息,如果是打印error信息,會終止編譯。以warning爲例:
$(warning 'This is a test')
15、if條件判斷
採用ifeq關鍵字,然後左右變量放在括號體內,用逗號分隔:
ifeq($(TARGET_ABI), arm64-v8a)
$(debug 'This is arm64-v8a')
endif
二、Application.mk語法
Android.mk依賴Application.mk文件進行編譯,一般Application.mk腳本如下所示:
APP_STL := c++_static
APP_DEBUG := false
APP_OPTIM := release
APP_CPPFLAGS := -frtti
APP_PLATFORM := android-16
APP_ABI := armeabi-v7a arm64-v8a
1、APP_ABI
與Android.mk的TARGET_ABI對應,包括CPU架構有:x86、x86_64、armeabi-v7a、arm64-v8a,支持所有平臺這樣表示:
APP_ABI := all
2、APP_BUILD_SCRIPT
指向編譯腳本的路徑,一般Android.mk和Application.mk都位於jni目錄,默認指向jni/Android.mk路徑,如果是其他路徑,需要使用此變量來指定絕對路徑:
APP_BUILD_SCRIPT := /xx/xx/Android.mk
3、APP_OPTIM
編譯優化選項,調試模式爲debug,發佈模式爲release。在調試模式下,會保留symbol符號表;在發佈模式下,會開啓優化,去掉symbol符號表。
4、APP_PLATFORM
指定編譯平臺,面向於Android API級別,對應gradle聲明的minSdkVersion。如果不聲明,默認爲ndk支持的最低API版本
5、APP_STL
聲明使用c++的標準庫,默認爲system STL。其他選項包括c++_static、c++_shared和none
三、CMakeLists.txt語法
以編譯hello模塊以及依賴ffmpeg模塊爲例:
cmake_minimum_required(VERSION 3.4.1)
#添加動態庫,包含源文件路徑
add_library( hello
SHARED
src/main/jni/hello.c)
#添加第三方動態庫
add_library( ffmpeg
SHARED
IMPORTED )
#指定第三方庫路徑
set_target_properties( ffmpeg
PROPERTIES IMPORTED_LOCATION
../../../../libs/${CMAKE_ANDROID_ARCH_ABI}/libffmpeg.so )
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
#指定頭文件路徑
include_directories(src/main/cpp)
if(${CMAKE_ANDROID_ARCH_ABI} MATCHES "armeabi-v7a")
include_directories(src/main/cpp/include/armeabi-v7a)
message("This is armeabi-v7a")
elseif(${CMAKE_ANDROID_ARCH_ABI} MATCHES "arm64-v8a")
include_directories(src/main/cpp/include/arm64-v8a)
message("This is arm64-v8a")
endif()
#查找系統庫
find_library( # Sets the name of the path variable.
log-lib
log )
#鏈接目標庫
target_link_libraries( hello
#ffmpeg
${log-lib} )
1、add_library
傳遞三個參數,第一個參數是模塊名稱,第二個參數是SHARED或者STATIC。如果是源文件模塊,第三個參數是源文件列表;如果是第三方庫,第三個參數是IMPORTED。
2、set_target_properties
用於指定第三方庫路徑,IMPORT_LOCATION一般是指向src/main/cpp目錄
3、include_directories
用於指定頭文件路徑,頭文件路徑可以有多個
4、find_library
用於查找系統庫,比如Android系統的log日誌庫
5、target_link_libraries
鏈接目標庫,把依賴庫都鏈接到目標庫中
6、if條件判斷
與Android.mk稍有差異,CMake採用if...MATCHES形式,例如:
if(${CMAKE_ANDROID_ARCH_ABI} MATCHES "armeabi-v7a")
......
endif()
7、打印日誌
與Android.mk不同的是,CMake採用message函數來打印日誌,括號體傳入msg內容
message("hello, cmake")
8、命令行參數
命令行參數前面統一加上-D,常用的參數:
1) -DANRDOID_ABI :android的ABI架構平臺
2) -DANDROID_NDK :ndk路徑
3) -DANDROID_ARM_MODE :arm模式/thumb模式
4) -DANDROID_ARM_NEON :是否開啓arm neon加速,針對armeabi-v7a平臺
5) -DANDROID_TOOLCHAIN :編譯工具鏈
6) -DANDROID_NATIVE_API_LEVEL :與ANDROID_PLATFORM相同,對應minSdkVersion
7) -DCMAKE_BUILD_TYPE :編譯類型,debug或release
8) -DCMAKE_MAKE_PROGRAM :編譯程序
9) -DCMAKE_TOOLCHAIN_FILE :編譯文件
9、命令行編譯
以cmake作爲關鍵字,後面帶着指定參數,示例如下:
cmake \
-DANDROID_ABI=armeabi-v7a \
-DANDROID_NDK=${HOME}/Android/Sdk/ndk-bundle \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_MAKE_PROGRAM=${HOME}/Android/Sdk/cmake/3.6.3155560/bin/ninja \
-DCMAKE_TOOLCHAIN_FILE=${HOME}/Android/Sdk/ndk-bundle/build/cmake/android.toolchain.cmake \
-DANDROID_NATIVE_API_LEVEL=23 \
-DANDROID_TOOLCHAIN=clang
四、ndk編譯配置
1、Android.mk方式配置
在gradle的defaultConfig配置ndk:
defaultConfig {
......
ndk {
moduleName "hello"
abiFilters "armeabi-v7a", "arm64-v8a"
}
}
然後配置jni源文件路徑:
sourceSets {
main {
jniLibs.srcDir 'src/main/libs' // Enable to use libs
jni.srcDirs 'src/main/jni' // Enable the automatic ndk-build
}
}
另外配置Android.mk文件絕對路徑:
externalNativeBuild {
ndkBuild {
"src/main/jni/Android.mk"
}
}
2、CMake方式配置
前兩步與Android.mk方式一樣,配置腳本路徑稍有差異:
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
另外在defaultConfig設置cppFlags:
externalNativeBuild {
cmake {
cppFlags ""
}
}
五、ndk編譯過程
1、ndk-build編譯
如上圖所示,在命令行輸入ndk-build後,會根據聲明所支持的平臺依次編譯。首先是armeabi-v7a平臺架構,把hello.c源文件編譯成hello目標文件,然後鏈接成libhello.so動態庫,最終安裝到libs/armeabi-v7a目錄下。
2、cmake在Gradle中編譯
如上圖所示,編譯arm64-v8a平臺架構的hello模塊。首先把hello.c源文件編譯成hello.c.o目標文件,然後鏈接成libhello.so動態庫。生成的debug模式動態庫在/build/intermediates/cmake/debug/obj/arm64-v8a目錄下。