Android:JNI與NDK(三)NDK構建的腳本文件配置
- 2019 年 10 月 3 日
- 筆記
友情提示:歡迎關注本人公眾號,那裡有更好的閱讀體驗以及第一時間獲取最新文章
本文目錄
一、前言
本篇我們介紹Android.mk與CMakeLists.txt構建NDK的配置文件,我們知道目前NDK的開發已經基本廢棄Android.mk的使用了,AS創建NDK工程默認已經使用CMakeLists.txt構建文件,那我們為什麼還要介紹Android.mk呢?
因為在平時開發中我們依然有可能接觸到Android.mk文件,並且很多老的開源庫依然使用的是Android.mk配置方式來構建的,這就要求我們能夠讀懂,在我們自己開發NDK的時候使用CMakeLists.txt來構建就可以了,Android.mk我們只需要讀懂核心配置就可以了,遇到的時候能夠讀懂大部分意思,而CMakeLists.txt構建方式我們需要能夠熟練使用。
上一篇中,我們在PC上編譯動態庫或者靜態庫的時候需要給編譯器傳遞一些參數,比如頭文件,庫文件的查找路徑,那種編譯方式需要我們手動通過命令行的方式來編譯,通常編譯ffmpeg等提供源碼的三方庫為安卓平台可用的動態庫或者靜態庫的時候會用這種方式,工作中我們也會自己在安卓項目中編寫C/C++源文件,這些源文件怎麼編譯為動態庫或者靜態庫呢?編譯的時候我們用到三方的動態庫或者靜態庫怎麼設置依賴呢?怎麼設置頭文件或者庫文件的查找路徑呢?以及怎麼配置編譯文件之間的依賴呢?這些都可以在Android.mk或者CMakeLists.txt文件中配置,
本篇我們主要詳細了解一下這些配置文件怎麼配置,工作中出問題我們也好自己來查找解決。
好了,進入本文的學習。
二、Android.mk與Application.mk配置的了解
Android.mk的語法支持將源文件分組為模塊。 模塊是靜態庫、共享庫或獨立的可執行文件。 您可在每個Android.mk文件中定義一個或多個模塊,也可在多個模塊中使用同一個源文件。Android提供了一系列的內置變量來提供更加方便的構建語法規則。
Application.mk文件實際上是對應用程序本身進行描述的文件,它描述了應用程序要針對哪些CPU架構打包動態so包、要構建的是release包還是debug包以及一些編譯和鏈接參數等。
Android.mk配置文件
我們先來看幾個具體的Android.mk配置文件:
1 LOCAL_PATH := $(call my-dir) 2 3 include $(CLEAR_VARS) 4 5 LOCAL_MODULE := MyGame_shared 6 7 LOCAL_MODULE_FILENAME := libMyGame 8 9 #源文件 10 LOCAL_SRC_FILES := $(LOCAL_PATH)/A.cpp 11 $(LOCAL_PATH)/../../../Classes/B.cpp 12 $(LOCAL_PATH)/../../../Classes/C.cpp 13 14 # 編譯時查找頭文件的路徑 相當於:-I 15 LOCAL_C_INCLUDES := $(LOCAL_PATH) 16 $(LOCAL_PATH)/include 17 18 # 需要鏈接的系統默認庫 19 LOCAL_LDLIBS := -llog 20 -lz 21 22 include $(BUILD_SHARED_LIBRARY) 23 24 $(call import-add-path,$(LOCAL_PATH)/../../../aaa) 25 $(call import-add-path,$(LOCAL_PATH)/../../../aaa/bbb) 26 #引入其他路徑下的Android.mk文件 27 # 相當於 #include 28 $(call import-module, ddd)
1 LOCAL_PATH := $(call my-dir) 2 3 include $(CLEAR_VARS) 4 LOCAL_MODULE := Test 5 LOCAL_SRC_FILES := libTest.a 6 include $(PREBUILT_STATIC_LIBRARY) 7 8 include $(CLEAR_VARS) 9 LOCAL_MODULE := hello-jni 10 LOCAL_SRC_FILES := hello-jni.c 11 # 編譯hello-jni模塊 需要鏈接 Test 模塊 12 # Test模塊是一個預編譯庫模塊 13 LOCAL_STATIC_LIBRARIES := Test 14 include $(BUILD_SHARED_LIBRARY)
沒接觸或者看不懂沒關係,接下來會解釋的。
Android提供了一系列的內置變量或者函數來供我們更加方便的構建編譯腳本,接下來我們來具體了解一下怎麼使用或者配置:
1 LOCAL_PATH := $(call my-dir)
構建系統提供的宏函數 my-dir返回Android.mk 文件所在的目錄,這裡的意思就是LOCAL_PATH 指向Android.mk 文件所在的目錄。
1 include $(CLEAR_VARS)
CLEAR_VARS 變量指向一個特殊的 GNU Makefile,後者會清除許多 LOCAL_XXX 變量,例如 LOCAL_MODULE、LOCAL_SRC_FILES 和 LOCAL_STATIC_LIBRARIES。每個模塊編譯描述之前都會調用一下include $(CLEAR_VARS),意思就是本模塊不用你們之前模塊定義的那些信息,我要自己定義,也就是將之前定義的變量值全部清空自己重新定義一下。
1 LOCAL_MODULE := hello-jni
LOCAL_MODULE 變量存儲您要構建的模塊的名稱,且必須唯一不含空格,會對分配給 LOCAL_MODULE 的名稱自動添加正確的前綴和後綴。 例如,上述示例會生成名為libhello-jni.so的庫。如果模塊名稱的開頭已經是 lib,則構建系統不會附加額外的 lib前綴;而是按原樣採用模塊名稱,並添加 .so 擴展名。
1 LOCAL_MODULE_FILENAME := libnewfoo
LOCAL_MODULE_FILENAME 是一個可選變量,我們可以不配置,如果我們給這個變量配置了名稱,則會強制系統將其生成的文件命名為LOCAL_MODULE_FILENAME 所配置的名稱,也就是LOCAL_MODULE_FILENAME 的優先級高於LOCAL_MODULE ,比如,我們配置如下:
1 LOCAL_MODULE := foo 2 LOCAL_MODULE_FILENAME := libnewfoo
對於生成動態庫,生成的名稱是libnewfoo.so而不是libfoo.so。
1 LOCAL_SRC_FILES := hello-jni.c
這裡配置源文件的列表,多個文件以空格隔開,也可以 來換行配置
1 include $(BUILD_SHARED_LIBRARY)
指導生成動態庫還是靜態庫,或者是預編譯庫,預編譯庫就是已經生成的動態或者靜態庫,可選配置如下:
名稱 | 說明 |
---|---|
BUILD_SHARED_LIBRARY | 生成動態庫 |
BUILD_STATIC_LIBRARY | 生成靜態庫 |
PREBUILT_SHARED_LIBRARY | 預編譯的庫是動態庫 |
PREBUILT_STATIC_LIBRARY | 預編譯的庫是靜態庫 |
1 LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
指定C/C++編譯額外頭文件的目錄
1 LOCAL_CFLAGS += -I<path>
此可選變量用於設置在構建 C 和 C++ 源文件時構建系統要傳遞的編譯器標記,可使用此功能指定額外的宏定義或編譯選項。
1 LOCAL_CPPFLAGS += -I<path>
只構建 C++ 源文件時將傳遞的一組可選編譯器標記, 這些標記會出現在編譯器的命令行中,跟在 LOCAL_CFLAGS 之後。
LOCAL_CFLAGS 與LOCAL_CPPFLAGS 需要額外說明一下:這裡傳遞的參數會直接傳給編譯器的命令行,上一篇我們講解了給編譯傳遞頭文件,庫文件查找路徑的方式這裡都可以配置。
1 LOCAL_STATIC_LIBRARIES := Test
此變量用於存儲當前模塊所依賴的靜態庫模塊列表。比如編譯此模塊需要依賴libTest.a靜態庫,則上面配置即可。
1 LOCAL_SHARED_LIBRARIES
此變量用於存儲當前模塊所依賴的動態庫模塊列表。
1 LOCAL_LDLIBS := -llog 2 -lz
此變量包含額外的鏈接程序標記列表,供構建共享庫或可執行文件時使用。 利用此變量,您可使用 -l 前綴傳遞特定系統庫的名稱,如果為靜態庫定義此變量,構建系統會將其忽略。 上面的指定表示編譯此模塊需要依賴系統的liblog.so與libz.so動態庫。
1 $(call import-add-path,$(LOCAL_PATH)/../../../aaa) 2 $(call import-add-path,$(LOCAL_PATH)/../../../aaa/bbb) 3 $(call import-module, ddd)
import-module函數用於按模塊名稱來查找和包含模塊的Android.mk文件, import-add-path用來添加查找的路徑,二者常常配合使用,上面的意思就是查找ddd模塊,並將此模塊下的Android.mk文件配置引入引入進來,查找值前調用import-add-path方法來添加查找的路徑。
好了以上就是Android.mk文件的一些核心配置,我們回過頭來在看下上面提到的具體配置:
1 LOCAL_PATH := $(call my-dir) 2 3 #預編譯庫的引入 4 include $(CLEAR_VARS) 5 LOCAL_MODULE := Test 6 LOCAL_SRC_FILES := libTest.a 7 include $(PREBUILT_STATIC_LIBRARY) 8 9 include $(CLEAR_VARS) 10 LOCAL_MODULE := hello-jni 11 LOCAL_SRC_FILES := hello-jni.c 12 # 編譯hello-jni模塊 需要鏈接 Test 模塊 13 # Test模塊是一個預編譯庫模塊 14 LOCAL_STATIC_LIBRARIES := Test 15 include $(BUILD_SHARED_LIBRARY)
在工程中hello-jni.c源文件中使用到了libTest.a靜態庫中方法,所以在編譯hello-jni.c源文件為動態庫的時候需要先預編譯libTest.a靜態庫,並通過LOCAL_STATIC_LIBRARIES 指定引用的靜態庫。
好了Android.mk中的一些核心配置介紹到此,對於Android.mk文件工作中遇到能大體讀明白就可以了。
Application.mk配置文件
接下來我們看下Application.mk配置文件有哪些核心配置,同樣先來看一下實際例子:
1 APP_CPPFLAGS := -frtti 2 APP_LDFLAGS := -latomic 3 APP_ABI := armeabi-v7a 4 APP_PLATFORM = android-21 5 APP_OPTIM := debug
Application.mk中核心配置就少多了,主要是一些全局的配置,可作用於任何Android.mk中的編譯單元。
我們看一下核心的配置表示的意義:
1 APP_CFLAGS += -I<PATH>
APP_CFLAGS 變量用於存儲一組 C 編譯器標記,供構建系統在為任何模塊編譯任何 C 或 C++ 源代碼時傳遞到編譯器。
1 APP_CPPFLAGS
此變量包含一組 C++ 編譯器標記,僅供構建系統構建 C++ 源文件時傳遞到編譯器。 使用 APP_CFLAGS 可以為 C 和 C++ 指定標記。
1 APP_OPTIM := debug
這個可選變量定義為 release 或 debug。 使用此變量可以在構建應用的模塊時變更優化級別。
發佈模式為默認模式,可以生成高度優化的二進制文件。 調試模式會生成未經優化的二進制文件,如此更容易調試。
1 APP_ABI := armeabi-v7a arm64-v8a
使用 APP_ABI 設置為特定的平台生成機器代碼,多個值以空格隔開,如上面設置生成armeabi-v7a與 arm64-v8a平台架構下的動態或者靜態庫。
1 APP_PLATFORM = android-21
指定支持的最低版本的 Android 平台
好了,以上就是Application.mk中一些核心的配置,對於mk配置文件我們大概能讀懂就可以了,很多我也沒介紹,現在基本沒人用了,相當於上古時候的工具,現在開發NDK基本全用的CMakeLists.txt方式了,接下來我們來看看CMakelists.txt的配置方式。
三、掌握CMakeLists.txt的配置
在android studio 2.2及以上,構建原生庫的默認工具是 CMake。
CMake是一個跨平台的構建工具,可以用簡單的語句來描述所有平台的編譯過程。Cmake 並不直接建構出最終的軟件,而是產生其他工具的腳本(如Makefile ),然後再依這個工具的構建方式使用。
CMake是一個比make更高級的編譯配置工具,它可以根據不同平台、不同的編譯器,生成相應的Makefile或者vcproj項目。從而達到跨平台的目的。
Android Studio利用CMake生成的是ninja,ninja是一個小型的關注速度的構建系統。我們不需要關心ninja是什麼鬼,知道怎麼配置cmake就可以了。
以上吧啦吧啦一堆就是說CMake怎麼怎麼的好,CMake 的配置文件是CMakeLists.txt,我們只需要關心怎麼配置就好了,接下來我們看下具體的配置以及其含義。
單文件
如果我們工程只有一個C/C++文件,我們配置設置生成動態或者靜態庫,如下配置即可:
1 # 指定運行此配置文件所需的 CMake 的最低版本 2 cmake_minimum_required(VERSION 3.4.1) 3 4 # 該命令表示項目的名稱是NDK 5 project(NDK) 6 7 #src/main/cpp/native-lib.cpp 會編譯為libnative-lib.so動態庫 8 #SHARED編譯動態庫 STATIC:靜態庫 9 add_library( native-lib 10 SHARED 11 src/main/cpp/native-lib.cpp)
上面已經給出詳細解釋,不再細說。
多文件
如果我們有多個C/C++源文件想生成動態或者靜態庫加入如下配置即可:
1 # 指定運行此配置文件所需的 CMake 的最低版本 2 cmake_minimum_required(VERSION 3.4.1) 3 # 該命令表示項目的名稱是NDK 4 project(NDK) 5 6 # 多文件 7 file(GLOB DIR_SRCS src/main/cpp/*.c src/main/cpp/*.cpp) 8 # SHARED: 動態庫 STATIC:靜態庫、 9 add_library(hello-jni SHARED ${DIR_SRCS})
經過上面配置就可以將src/main/cpp目錄下的所有C/C++文件編譯為一個so動態庫。
預編譯靜態庫的引入
如果我們編譯自己源文件的時候用到了已經編譯好的靜態庫中的方法等,就需要進行額外配置讓編譯器編譯的時候能鏈接到對應靜態庫,比如我們將靜態庫放入如下目錄:
配置如下:
1 # 指定運行此配置文件所需的 CMake 的最低版本 2 cmake_minimum_required(VERSION 3.4.1) 3 # 該命令表示項目的名稱是NDK 4 project(NDK) 5 #src/main/cpp/native-lib.cpp 會編譯為libnative-lib.so動態庫 6 #SHARED編譯動態庫 STATIC:靜態庫 7 add_library( native-lib 8 SHARED 9 src/main/cpp/native-lib.cpp) 10 11 #引入預編譯好的靜態庫 12 # IMPORTED: 表示靜態庫是以導入的形式添加進來(預編譯靜態庫) 13 # StaticTest 可以隨便起名字 14 add_library(StaticTest STATIC IMPORTED) 15 16 #設置靜態庫的導入路徑 17 set_target_properties(StaticTest PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/static/armeabi-v7a/libStaticTest.a) 18 19 #生成native-lib動態庫需要用到StaticTest log動態或者靜態庫 20 #鏈接的配置 21 target_link_libraries( # Specifies the target library. 22 native-lib 23 # libTest.so 可以去掉lib與.so 24 Test 25 log )
CMAKE_SOURCE_DIR 是系統預定義好的變量,代表當前CMakeLists.txt所在的目錄。
上面我們進行編譯的時候除了設置預編譯的libStaticTest.a靜態庫,還設置了log庫用於打印一些信息,但是log庫我們並沒有配置其路徑,這是因為構建系統為我們已經編譯好一些常用的庫供我們使用,其查找路徑與頭文件路徑已經預定義好了,編譯器會自己去查找的,不用我們額外配置查找路徑,但是我們使用了額外的預編譯庫,那就需要進行額外配置了。
動態庫的引入
上面的方式其實同樣可以引入動態庫,如:
1 add_library(Test SHARED IMPORTED) 2 set_target_properties(Test PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libTest.so)
只需將STATIC改為SHARED即可,但是這樣的配置方式在6.0及以上系統會報找不到庫路徑的錯誤,CMake提供了另一種配置方式:
1 #動態庫這樣引入沒有版本差異,如果像上面那樣引入會有版本問題 2 # CMAKE_CXX_FLAGS 會傳給c++編譯器 3 # CMAKE_C_FLAGS 會傳給c編譯器 4 # CMAKE_SOURCE_DIR 的值是當前CMakelist.txt所在目錄 5 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a")
還記得-L參數的意義嗎?上一篇講過就是給編譯器傳遞庫文件查找路徑的,別忘了鏈接的時候添加動態庫名字:
1 target_link_libraries( # Specifies the target library. 2 native-lib 3 Test 4 StaticTest 5 log )
額外頭文件路徑查找配置以及其餘CMakeLists.txt配置文件的引入
1 #額外頭文件查找路徑設置 相當於-I 2 include_directories(src/main/include) 3 4 #引入src/main/subcmakelist目錄的cmakelist 5 add_subdirectory(src/main/subcmakelist)
build.gradle中有關NDK編譯的配置
主要如下:
1 android { 2 compileSdkVersion 26 3 defaultConfig { 4 applicationId "com.wanglei55.ndk" 5 minSdkVersion 18 6 targetSdkVersion 26 7 versionCode 1 8 versionName "1.0" 9 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 10 externalNativeBuild { 11 cmake { 12 //指導我們自己編寫的c/c++生成動態或者靜態庫時生成幾種架構的庫 13 abiFilters "armeabi-v7a" 14 } 15 } 16 //應該打包幾種cpu 17 //比如:三方庫中提供了arm的提供了x86的,可以在此處指導只打包arm,生成出來的apk就只會包含 arm的 18 ndk{ 19 abiFilters "armeabi-v7a" 20 } 21 } 22 buildTypes { 23 release { 24 minifyEnabled false 25 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 26 } 27 } 28 externalNativeBuild { 29 cmake {//設置CMakeLists.txt所在目錄,從build.gradle所在目錄開始查找 30 path "CMakeLists.txt" 31 } 32 } 33 }
好了,以上就是關於CMakeLists.txt核心配置的一些講解,此部分都是NDK開發中的一些基礎知識,學起來可能比較枯燥,但是卻是很重要的,否則後面的三方庫的編譯配置就直接無從下手,磨刀不誤砍柴工,先把基礎的學好吧。
CMake還有一些其餘配置,可參考官方說明:官方說明
四、總結
本篇,我們講解了一下mk與CMakeLists.txt的一些核心配置,主要為了以後我們看別人寫的或者自己需要配置的時候能知道怎麼下手,可能比較枯燥,但是這是NDK部分必須要搞懂的。
本篇到此為止。