Android.mk認識:html
在上一次【http://www.javashuo.com/article/p-xnceylrr-s.html】中學會了用NDK提供的交叉編譯工程編譯成Android能運行的可執行文件,下面我們來作個實驗來看一下使用靜態庫與動態庫的區別,仍是用上一次用的源文件爲例:java
動態庫的具體的生成過程能夠參考上一次寫的博文,接下來再生成一個靜態庫,如何生成呢?linux
因此我們先來找到NDK提供的ar交叉編譯工具:android
因此我們使用它依照生成規則來生成對應的靜態庫看一下:c++
接下來我們新建一個Android工程,來使用編譯出來的動靜態庫,這裏仍是創建一個純的非NDK的Android工程,來進一步操練下如何給一個普通Android工程來集成NDK,以下:web
而後我們來集成ndk環境,回顧上一次的步驟,得修改app下的build.gradle配置了:shell
這個是針對源文件的,而咱們想要用Android.mk來進行構建就須要在外層來指定了,以下:windows
其實上一次已經使用過了,裏面Android.mk的編寫規則也記不住,直接拷貝一下,重點不是記着,仍是你能讀懂就成,以下:api
而後我們在對應位置創建一個native-lib.c的源文件:ruby
那我們編譯一個apk,而後能夠看到apk中已經有一個.so文件了,以下:
那若是想使用咱們剛編譯寫的libTest.so文件應該怎麼使用呢?這裏就涉及到多模塊的引入問題,先將咱們編譯好的libTest.so拷入到工程中:
而後在Android.mk中來聲明一個新的模塊,其寫法跟聲明.c源文件相似,以下:
而後我們再來編譯一個apk,看一下里面的.so的狀況,是否是變爲2個.so了?
那是由於我們新聲明的預編譯模塊尚未使用到,因此須要關聯一下,以下:
那此時我們來在本身的.c文件中來使用一下預編譯庫定義的函數,以下:
固然在運行以前還得加載一個咱們編譯的庫:
而後編譯生成一個apk,看此時它裏面包含動態庫的狀況:
記住動態庫的這個現象哈~~以後會用靜態庫作一個對比!!!!
而後下面分別在Android4.3系統和Android8.1.0的系統上運行,爲啥要在兩個版本上運行,由於確定是有坑嘛~~,因此下面先來看在Android4.3上運行,用的手機型號爲:
崩潰了,看一下崩潰日誌:
11-27 09:12:04.756 6028-6028/com.jni.test E/AndroidRuntime: FATAL EXCEPTION: main java.lang.UnsatisfiedLinkError: dlopen failed: could not load library "/Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/libTest.so" needed by "libnative-lib.so"; caused by library "/Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/libTest.so" not found at java.lang.Runtime.loadLibrary(Runtime.java:362) at java.lang.System.loadLibrary(System.java:525) at com.jni.test.MainActivity.<clinit>(MainActivity.java:9) at java.lang.Class.newInstanceImpl(Native Method) at java.lang.Class.newInstance(Class.java:1130) at android.app.Instrumentation.newActivity(Instrumentation.java:1078) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2223) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2362) at android.app.ActivityThread.access$700(ActivityThread.java:168) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1329) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:177) at android.app.ActivityThread.main(ActivityThread.java:5493) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:525) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1225) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1041) at dalvik.system.NativeStart.main(Native Method)
找不到libTest.so,這是由於在Android6.0如下版本,System.loadLibrary()不會自動爲咱們加載依賴的動態庫,若是load一個動態庫,則須要先將這個動態庫的依賴的其餘動態庫也要load進來,因此:
那此時一樣的代碼在Android8.1.0的手機上來運行,手機型號是:
又崩潰了,崩潰日誌:
--------- beginning of crash 11-27 09:26:34.879 15598-15598/? E/AndroidRuntime: FATAL EXCEPTION: main Process: com.jni.test, PID: 15598 java.lang.UnsatisfiedLinkError: dlopen failed: library "/Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/libTest.so" not found at java.lang.Runtime.loadLibrary0(Runtime.java:1016) at java.lang.System.loadLibrary(System.java:1660) at com.jni.test.MainActivity.<clinit>(MainActivity.java:10) at java.lang.Class.newInstance(Native Method) at android.app.Instrumentation.newActivity(Instrumentation.java:1179) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3054) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3278) at android.app.ActivityThread.-wrap12(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1894) at android.os.Handler.dispatchMessage(Handler.java:109) at android.os.Looper.loop(Looper.java:166) at android.app.ActivityThread.main(ActivityThread.java:7377) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:469) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:963)
標紅處的地址很顯示是個人mac電腦上的路徑了,在手機上確定找不到嘛,那爲啥在8.1.0的手機上運行會出現從這個路徑去加載預編譯的動態庫呢?其實這就是版本兼容的問題了,在NDK中提供了一個能夠看到.so文件的依賴狀況,以下:
那我們來查看一下咱們的libnative-lib.so它裏面的依賴狀況,先定位到這個生成的.so文件的路徑處:
而後使用這個ndk的依賴查看命令:
也正好是崩潰日誌中所看到的路徑,實際上是從Android6.0開始,使用Android.mk若是來引入一個預編譯動態庫是有問題的【這個須要特別的注意!】,其緣由也就是「Android6.0如下版本,System.loadLibrary()不會自動爲咱們加載依賴的動態庫,而6.0及以上的版本是會自動爲咱們加載依賴的動態庫」,也就是說在Android6.0以上版本不用咱們顯示的去寫加載的預編譯庫了,也就是這句話在Android6.0上能夠省略掉了:
那使用預編譯的動態庫在Android6.0以上系統有問題,那若是是改用靜態庫會不會有問題呢?我們試試,將以前咱們編譯好的靜態庫也放到工程當中:
此時須要修改.mk文件了,以下:
其中關於MK中的這些變量的使用能夠參考:
經常使用內置變量
變量名 | 含義 | 示例 |
---|---|---|
BUILD_STATIC_LIBRARY | 構建靜態庫的Makefile腳本 | include $(BUILD_STATIC_LIBRARY) |
PREBUILT_SHARED_LIBRARY | 預編譯共享庫的Makeifle腳本 | include $(PREBUILT_SHARED_LIBRARY) |
PREBUILT_STATIC_LIBRARY | 預編譯靜態庫的Makeifle腳本 | include $(PREBUILT_STATIC_LIBRARY) |
TARGET_PLATFORM | Android API 級別號 | TARGET_PLATFORM := android-22 |
TARGET_ARCH | CUP架構 | arm arm64 x86 x86_64 |
TARGET_ARCH_ABI | CPU架構 | armeabi armeabi-v7a arm64-v8a |
模塊描述變量
變量名 | 描述 | 例 |
---|---|---|
LOCAL_MODULE_FILENAME | 覆蓋構建系統默認用於其生成的文件的名稱 | LOCAL_MODULE := foo LOCAL_MODULE_FILENAME := libnewfoo |
LOCAL_CPP_FEATURES | 特定 C++ 功能 | 支持異常:LOCAL_CPP_FEATURES := exceptions |
LOCAL_C_INCLUDES | 頭文件目錄查找路徑 | LOCAL_C_INCLUDES := $(LOCAL_PATH)/include |
LOCAL_CFLAGS | 構建 C 和 C++ 的編譯參數 | |
LOCAL_CPPFLAGS | c++ | |
LOCAL_STATIC_LIBRARIES | 當前模塊依賴的靜態庫模塊列表 | |
LOCAL_SHARED_LIBRARIES | ||
LOCAL_WHOLE_STATIC_LIBRARIES | --whole-archive | 將未使用的函數符號也加入編譯進入這個模塊 |
LOCAL_LDLIBS | 依賴 系統庫 | LOCAL_LDLIBS := -lz |
導出給引入模塊的模塊使用:
LOCAL_EXPORT_CFLAGS
LOCAL_EXPORT_CPPFLAGS
LOCAL_EXPORT_C_INCLUDES
LOCAL_EXPORT_LDLIBS
編譯下編譯生成一個apk:
也就是說會將咱們程序用到的libTest.a靜態庫中的那段代碼抽離打包到咱們本身的so當中,而動態庫是整個會打包進apk,跟咱們本身的so是獨立的,而後在運行時進行依賴調用,也就是說其實使用靜態作爲預編譯庫來使用的話打出來的.so會小不少,由於只有咱們使用到的纔會打包,而動態庫是整個都會打包,無論使用了仍是木有使用。舉個形象的例子來理解靜態庫與動態庫打包的區別:
假設有個jar裏面包含a.java、b.java、c.java,而後我們app有一個app.java,它使用了jar包中的a.java,若是使用了靜態庫打包,則最終apk中只包含app.java和a.java;而若是使用了動態庫打包,則最終APK中包含app.java和整個jar(a.java、b.java、c.java),很顯示在開發中通常使用靜態庫較好,可是有個問題:爲啥三方SDK通常都是提供.so文件呢?其實根本緣由仍是以前講的:它只能加載動態庫,不加載靜態庫:
只是說若是是咱們本身編譯的庫可使用靜態庫,由於咱們有NDK的編譯環境嘛,若是三方給一個.a靜態庫,那咱們在工程中仍是配置NDK編譯環境來將它進行一個封裝,最終編成咱們的.so再來使用,太麻煩了。
此時再運行,在不一樣版本上均可以兼容了。總之須要記住在mk中引入預編譯的動態庫是有版本性的差別滴,要解決的話就得用Cmake來代替.mk了,像現在的項目基本都是採用Cmake來進行NDK的編譯了,那學習.mk有啥意義呢?由於好多老工程的編譯仍是基於.mk的,咱們須要能看懂它,因此基於這個學習目的,下面來看一個cocos2d的一個hello world級別的工程,它裏面的工程編譯就是經過mk的形式,咱們來試着讀一下它的mk,看可否讀懂,能讀懂的話那目的就達到了:
首先固然先導入這個cocos2d的工程啦,具體工程的代碼能夠上網上搜搜,這裏導它的目的只是爲了去了解它的mk文件的編寫規則,並不是研究怎麼用它來開發遊戲,以下:
而後工程運行的界面以下:
我們重點關心的就是它的mk文件的編寫內容啦,所在的位置以下:
首先來看一下「Android.mk」完整配置:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) #模塊名 若是沒有 LOCAL_MODULE_FILENAME 配置 就會生成一個 #libMyGame_shared.so LOCAL_MODULE := MyGame_shared #生成一個 libMyGame.so (能夠不寫) LOCAL_MODULE_FILENAME := libMyGame #源文件 LOCAL_SRC_FILES := $(LOCAL_PATH)/hellocpp/main.cpp \ $(LOCAL_PATH)/../../../Classes/AppDelegate.cpp \ $(LOCAL_PATH)/../../../Classes/HelloWorldScene.cpp # 編譯時查找頭文件的路徑 至關於:-I LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../Classes # _COCOS_HEADER_ANDROID_BEGIN # _COCOS_HEADER_ANDROID_END # 引入一個靜態庫做爲當前編譯源文件的依賴(jar) # 定義在其餘的mk文件裏面 LOCAL_STATIC_LIBRARIES := cocos2dx_static # _COCOS_LIB_ANDROID_BEGIN # _COCOS_LIB_ANDROID_END include $(BUILD_SHARED_LIBRARY) # 單獨聲明是沒有任何意義的 須要結合 import-module 來看 # 設置引入其餘mk的查找路徑 $(call import-add-path,$(LOCAL_PATH)/../../../cocos2d) $(call import-add-path,$(LOCAL_PATH)/../../../cocos2d/external) $(call import-add-path,$(LOCAL_PATH)/../../../cocos2d/cocos) $(call import-add-path,$(LOCAL_PATH)/../../../cocos2d/cocos/audio/include) #引入其餘路徑下的Android.mk文件 # 至關於 #include $(call import-module, cocos) # _COCOS_LIB_IMPORT_ANDROID_BEGIN # _COCOS_LIB_IMPORT_ANDROID_END
接下來就是嘗試來讀懂這裏面的規則,第一句不用多說,每一個Android.mk文件的開頭的固定寫法:
能夠瞅一下我們生成的apk中的.so是不是咱們指定的這個:
嗯,確實是,繼續往下看:
對就是對應於這些源文件:
關於-I參數的含義能夠回顧一下:
這個其實我們在以前的DEMO中已經用到過了,以下:
因此咱們查找一下"cocos2dx_static"這個模塊的定義,發現木有在當前的Android.mk中定義,而是在其它mk中,這就涉及到如何引入其它mk的module啦,具體使用以下:
比較抽像對吧,來回到cocos2d這塊的配置就能理解滴:
對應於工程的這塊:
而後我們打開它,看一下"cocos2dx_static"這個模塊是否認義在這個.mk裏面:
至此對於第一個Android.mk就分析完了,貌似也都能看懂嘛,接下來還有另一種mk,也就是下面要學習滴。
Application.mk:
一樣是GNU Makefile 片斷,在Application.mk中定義一些全局(整個項目)的配置
先來瞅一下工程中的這個mk:
先來看一下它裏面都配了啥:
# 指定運行時庫 (libc ) APP_STL := c++_static #會交給編譯器的參數 APP_CPPFLAGS := -frtti -DCC_ENABLE_CHIPMUNK_INTEGRATION=1 -std=c++11 -fsigned-char -Wno-extern-c-compat # 交給連接器的參數 (so依賴另外一個so,這就須要連接 ) APP_LDFLAGS := -latomic # 要生成的cpu架構 APP_ABI := armeabi-v7a #解決windows命令行不支持太長的字符輸入的問題 APP_SHORT_COMMANDS := true ifeq ($(NDK_DEBUG),1) APP_CPPFLAGS += -DCOCOS2D_DEBUG=1 APP_OPTIM := debug else APP_CPPFLAGS += -DNDEBUG APP_OPTIM := release endif
接下來一個個瞭解一下:
也就是咱們在寫cpp裏的一些系統函數,如std::cout之類的,具體這塊的配置參數以下:
而它的定義以下:
因爲這個工程的源代碼是基於cpp的,因此用上面這個標誌,若是是c,則須要用下面這個:
【注意】:因爲NDK對於mk已經接近放棄的階段,因此對於CPU的指定建議仍是要build.gradle中去指定,以下:
而它的具體配置以下:
須要生成的cpu架構(ndk r17 只支持:armeabi-v7a, arm64-v8a, x86, x86_64)
指令集 | 值 |
---|---|
基於 ARMv7 的設備上的硬件 FPU 指令 | APP_ABI := armeabi-v7a |
ARMv8 AArch64 | APP_ABI := arm64-v8a |
IA-32 | APP_ABI := x86 |
Intel64 | APP_ABI := x86_64 |
MIPS32 | APP_ABI := mips |
MIPS64 (r6) | APP_ABI := mips64 |
全部支持的指令集 | APP_ABI := all |
不一樣 Android 手機使用不一樣的 CPU,所以支持不一樣的指令集。
armeabi-v7a
armeabi-v7a
ABI 使用 -mfloat-abi=softfp
開關強制實施規則,要求編譯器在函數調用時必須傳遞核心寄存器對中的全部雙精度值,而不是專用浮點值。 系統可使用 FP 寄存器執行全部內部計算。 這樣可極大地加速計算。
若是要以 armeabi-v7a ABI 爲目標,則必須設置下列標誌:
CFLAGS= -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16
arm64-v8a
此 ABI 適用於基於 ARMv八、支持 AArch64 的 CPU。它還包含 NEON 和 VFPv4 指令集。
x86
此 ABI 適用於支持一般稱爲「x86」或「IA-32」的指令集的 CPU。設置的標誌如:
-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32
x86_64
-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel
如今手機主要是armeabi-v7a。查看手機cpu:
adb shell cat /proc/cpuinfo
adb shell getprop ro.product.cpu.abi
如查看一下我模擬器的cpu狀況:
xiongweideMacBook-Pro:LableCoffee xiongwei$ adb shell cat /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 69 model name : Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz stepping : 1 cpu MHz : 2600.058 cache size : 3072 KB physical id : 0 siblings : 4 core id : 0 cpu cores : 4 apicid : 0 initial apicid : 0 fpu : yes fpu_exception : yes cpuid level : 13 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx rdrand lahf_lm abm bugs : bogomips : 5200.11 clflush size : 64 cache_alignment : 64 address sizes : 39 bits physical, 48 bits virtual power management: processor : 1 vendor_id : GenuineIntel cpu family : 6 model : 69 model name : Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz stepping : 1 cpu MHz : 2600.058 cache size : 3072 KB physical id : 0 siblings : 4 core id : 1 cpu cores : 4 apicid : 1 initial apicid : 1 fpu : yes fpu_exception : yes cpuid level : 13 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx rdrand lahf_lm abm bugs : bogomips : 5200.11 clflush size : 64 cache_alignment : 64 address sizes : 39 bits physical, 48 bits virtual power management: processor : 2 vendor_id : GenuineIntel cpu family : 6 model : 69 model name : Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz stepping : 1 cpu MHz : 2600.058 cache size : 3072 KB physical id : 0 siblings : 4 core id : 2 cpu cores : 4 apicid : 2 initial apicid : 2 fpu : yes fpu_exception : yes cpuid level : 13 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx rdrand lahf_lm abm bugs : bogomips : 5200.11 clflush size : 64 cache_alignment : 64 address sizes : 39 bits physical, 48 bits virtual power management: processor : 3 vendor_id : GenuineIntel cpu family : 6 model : 69 model name : Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz stepping : 1 cpu MHz : 2600.058 cache size : 3072 KB physical id : 0 siblings : 4 core id : 3 cpu cores : 4 apicid : 3 initial apicid : 3 fpu : yes fpu_exception : yes cpuid level : 13 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx rdrand lahf_lm abm bugs : bogomips : 5200.11 clflush size : 64 cache_alignment : 64 address sizes : 39 bits physical, 48 bits virtual power management: xiongweideMacBook-Pro:LableCoffee xiongwei$ adb shell getprop ro.product.cpu.abi x86
apk在安裝的時候,若是手機是armeabi-v7a的,則會首先查看apk中是否存在armeabi-v7a目錄,若是沒有就會查找armeabi。
保證cpu目錄下so數量一致。
若是目標是armeabi-v7a,可是擁有一個armeabi的,也能夠把它放到armeabi-v7a目錄下。可是反過來不行
ABI(橫 so)/CPU(豎 手機) | armeabi | armeabi-v7a | arm64-v8a | x86 | x86_64 |
---|---|---|---|---|---|
ARMV5 | 支持 | ||||
ARMV7 | 支持 | 支持 | |||
ARMV8 | 支持 | 支持 | 支持 | ||
X86 | 支持 | ||||
X86_64 | 支持 | 支持 |
繼續仍是回到工程中的.mk往下瞅:
其中APP_OPTIM的具體含義以下:
以上就是關於mk的東東,配置比較複雜,不必去記,由於真實項目使用可能都會轉到下面要學習的cmake了,重點是要能讀取mk,而後能夠很容易的將mk轉成cmake就能夠啦~~
Cmake配置:
在android studio 2.2及以上,構建原生庫的默認工具是 CMake。
CMake是一個跨平臺的構建工具,能夠用簡單的語句來描述全部平臺的安裝(編譯過程)。可以輸出各類各樣的makefile或者project文件。Cmake 並不直接建構出最終的軟件,而是產生其餘工具的腳本(如Makefile ),而後再依這個工具的構建方式使用。
CMake是一個比make更高級的編譯配置工具,它能夠根據不一樣平臺、不一樣的編譯器,生成相應的Makefile或者vcproj項目。從而達到跨平臺的目的。Android Studio利用CMake生成的是ninja,ninja是一個小型的關注速度的構建系統。咱們不須要關心ninja的腳本,知道怎麼配置cmake就能夠了。從而能夠看出cmake實際上是一個跨平臺的支持產出各類不一樣的構建腳本的一個工具。
CMake的腳本名默認是CMakeLists.txt
其中標紅處提到了一個新名詞「ninja」,其實它存在於咱們的SDK當中,以下:
固然咱們不用關心ninja是如何構建的啦,只需瞭解CMake如何作就成了,這裏作一個瞭解。
下面我們來基於以前學習mk的工程來改用cmake,工程回憶一下:
首先得修改gradle的ndk編譯腳本的路徑,以下:
而後在cmake裏面也用以前的native-lib.c這個源文件:
那接下來就是來在CMakeLists.txt中來編寫構造腳本啦,那,如何寫呢。。下面來學習下,首先得指定一下最低版本,以下:
注意一個小細節:
接下來指令要編譯的源文件及編譯出來是要靜態庫或動態庫,以下:
此時編譯一下:
再次編譯:
這裏經過創建一個帶NDK環境的工程的這塊的配置一對比,發現它的ndk配置是這樣寫的:
那我們依葫蘆畫瓢一下:
再編譯一下:
Build command failed. Error while executing process /Users/xiongwei/android-sdks/Android/sdk/cmake/3.6.3155560/bin/cmake with arguments {--build /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/.externalNativeBuild/cmake/debug/mips64 --target native-lib} [1/1] Linking C shared library /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/build/intermediates/cmake/debug/obj/mips64/libnative-lib.so FAILED: : && /Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --target=mips64el-none-linux-android --gcc-toolchain=/Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/mips64el-linux-android-4.9/prebuilt/darwin-x86_64 --sysroot=/Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/platforms/android-21/arch-mips64 -fPIC -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -Wa,--noexecstack -Wformat -Werror=format-security -O0 -fno-limit-debug-info -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -Wl,--no-undefined -Wl,-z,noexecstack -Qunused-arguments -Wl,-z,relro -Wl,-z,now -shared -Wl,-soname,libnative-lib.so -o /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/build/intermediates/cmake/debug/obj/mips64/libnative-lib.so CMakeFiles/native-lib.dir/native-lib.c.o -lm && : CMakeFiles/native-lib.dir/native-lib.c.o: In function `Java_com_jni_test_MainActivity_nativeTest': /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/src/main/cmake/native-lib.c:7: undefined reference to `test' clang: error: linker command failed with exit code 1 (use -v to see invocation) ninja: build stopped: subcommand failed.
很顯然在我們的源代碼中引用了動態庫或靜態庫中的函數,可是咱們在CMakeLists.txt中並未指定動態庫或靜態庫的配置,因此接下來我們先以引入靜態庫爲例:
那從哪裏來導入呢,接下來須要設置一下靜態庫的路徑:
因爲libTest.a跟咱們的CMakeLists.txt不在同一個目錄,因此路徑須要注意一下,最後一步須要進行連接,也就是將這兩個模塊須要關聯起來:
如何寫呢?以下:
好,一切設置完畢,我們試着來編譯一下看是否正常了:
Build command failed. Error while executing process /Users/xiongwei/android-sdks/Android/sdk/cmake/3.6.3155560/bin/cmake with arguments {--build /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/.externalNativeBuild/cmake/debug/mips64 --target native-lib} ninja: error: '../../cpp/libTest.a', needed by '/Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/build/intermediates/cmake/debug/obj/mips64/libnative-lib.so', missing and no known rule to make it
貌似是靜態庫的路徑這樣設置不行,怎麼辦呢?其實能夠嘗試將CMakeLists.txt文件挪一下位置,我們先來看一下目錄結構:
挪了位置以後,固然build.gradle的腳本配置那塊也得變下了,以下:
而後在CMakeLists.txt路徑那能夠修改成:
而後再編譯一下:
Build command failed. Error while executing process /Users/xiongwei/android-sdks/Android/sdk/cmake/3.6.3155560/bin/cmake with arguments {--build /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/.externalNativeBuild/cmake/debug/mips64 --target native-lib} [1/1] Linking C shared library ../../../../build/intermediates/cmake/debug/obj/mips64/libnative-lib.so FAILED: : && /Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --target=mips64el-none-linux-android --gcc-toolchain=/Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/mips64el-linux-android-4.9/prebuilt/darwin-x86_64 --sysroot=/Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/platforms/android-21/arch-mips64 -fPIC -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -Wa,--noexecstack -Wformat -Werror=format-security -O0 -fno-limit-debug-info -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -Wl,--no-undefined -Wl,-z,noexecstack -Qunused-arguments -Wl,-z,relro -Wl,-z,now -shared -Wl,-soname,libnative-lib.so -o ../../../../build/intermediates/cmake/debug/obj/mips64/libnative-lib.so CMakeFiles/native-lib.dir/src/main/cmake/native-lib.c.o ../../../../src/main/cpp/libTest.a -lm && : /Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/mips64el-linux-android-4.9/prebuilt/darwin-x86_64/lib/gcc/mips64el-linux-android/4.9.x/../../../../mips64el-linux-android/bin/ld: unknown architecture of input file `../../../../src/main/cpp/libTest.a(test.o)' is incompatible with mips:isa64r6 output clang: error: linker command failed with exit code 1 (use -v to see invocation) ninja: build stopped: subcommand failed.
又失敗了,有點崩潰。。貌似從標紅處看是不支持相應的架構,此時想起來在build.gradle中打包APK時須要指定CPU類型,因此能夠嘗試加一下:
再編譯,終於成功了:
其中須要注意這個:
其實還有一個變量用得比較多,以下:
編譯看一下它的輸出:
那這個值是從哪獲取的呢?其實就是咱們在build.gradle中配置的,以下:
若是我們再加一個CPU架構,再看一下輸出效果:
上面是連接的靜態庫的配置,下面來使用動態庫來連接,那又如何配呢?這裏須要注意:須要將咱們的動態.so庫放到jniLibs裏面,不然不會打包進apk,具體以下:
而後修改CMakeLists.txt配置:
此時,我們在Android4.3機器上運行一下:
11-30 09:42:22.583 5731-5731/? E/AndroidRuntime: FATAL EXCEPTION: main java.lang.UnsatisfiedLinkError: dlopen failed: could not load library "../../../../src/main/jniLibs/armeabi-v7a/libTest.so" needed by "libnative-lib.so"; caused by library "../../../../src/main/jniLibs/armeabi-v7a/libTest.so" not found at java.lang.Runtime.loadLibrary(Runtime.java:362) at java.lang.System.loadLibrary(System.java:525) at com.jni.test.MainActivity.<clinit>(MainActivity.java:9) at java.lang.Class.newInstanceImpl(Native Method) at java.lang.Class.newInstance(Class.java:1130) at android.app.Instrumentation.newActivity(Instrumentation.java:1078) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2223) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2362) at android.app.ActivityThread.access$700(ActivityThread.java:168) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1329) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:177) at android.app.ActivityThread.main(ActivityThread.java:5493) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:525) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1225) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1041) at dalvik.system.NativeStart.main(Native Method)
其實跟用.mk引入預編譯動態庫同樣,6.0以前的系統是不會自動加入依賴庫的,因此我們須要手動加載一下依賴庫,以下:
此時再運行就正常了,而後apk中就有了兩個so文件,以下:
那若是改運行到Android6.0以上的機型,運行:
可是和mk不一樣的是,CMake有其它辦法引入動態庫讓它運行在Android6.0以上也沒問題,如何整:
試一下:
再運行,在全部版本均可運行了。
關於.mk和cmake引入預編譯動態庫在Android5.0及如下與6.0及以上的注意事項這裏總結一下,以勉踩坑:
好比存在兩個動態庫libhello-jni.so
與 libTest.so
。libhello-jni.so
依賴於libTest.so
(使用NDK下的ndk-depends
可查看依賴關係),則:
其中對於Android.mk來講: 使用Android.mk在 >=6.0 設備上不能再使用預編譯動態庫(靜態庫沒問題):
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := Test #libTest.so放在當前文件同目錄 LOCAL_SRC_FILES := libTest.so #預編譯庫 include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) #引入上面的Test模塊 LOCAL_SHARED_LIBRARIES := Test LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c include $(BUILD_SHARED_LIBRARY)
上面這段配置生成的libhllo-jni
在>=6.0設備中沒法執行。
而對於CMake來講:使用CMakeList.txt在 >=6.0 設備上引入預編譯動態庫配置以下:
cmake_minimum_required(VERSION 3.4.1) file(GLOB SOURCE *.c ) add_library( hello-jni SHARED ${SOURCE} ) #這段配置在6.0依然沒問題 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -L[SO所在目錄]") #這段配置只能在6.0如下使用 緣由和android.mk同樣 #add_library(Test SHARED IMPORTED) #set_target_properties(Test PROPERTIES IMPORTED_LOCATION [SO絕對地址]) target_link_libraries( hello-jni Test )
好,接下來看另一個CMake的東東:若是說咱們的代碼中要使用Android的Log庫,以前我們也說過,它的實現是處於NDK的這塊位置:
我們來看一下在CMake中來如何指定這個動態庫,先在源代碼中增長一個LOG的代碼:
目前沒有配置的話編譯確定會拋異常,以下:
Build command failed. Error while executing process /Users/xiongwei/android-sdks/Android/sdk/cmake/3.6.3155560/bin/cmake with arguments {--build /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/.externalNativeBuild/cmake/debug/armeabi-v7a --target native-lib} [1/2] Building C object CMakeFiles/native-lib.dir/src/main/cmake/native-lib.c.o clang: warning: argument unused during compilation: '-L/Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/src/main/jniLibs/armeabi-v7a' [2/2] Linking C shared library ../../../../build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so FAILED: : && /Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --target=armv7-none-linux-androideabi --gcc-toolchain=/Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 --sysroot=/Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/platforms/android-14/arch-arm -fPIC -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -fno-integrated-as -mthumb -Wa,--noexecstack -Wformat -Werror=format-security -L/Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/src/main/jniLibs/armeabi-v7a -O0 -fno-limit-debug-info -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -Wl,--fix-cortex-a8 -Wl,--no-undefined -Wl,-z,noexecstack -Qunused-arguments -Wl,-z,relro -Wl,-z,now -shared -Wl,-soname,libnative-lib.so -o ../../../../build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so CMakeFiles/native-lib.dir/src/main/cmake/native-lib.c.o -lTest -lm && : /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/src/main/cmake/native-lib.c:11: error: undefined reference to '__android_log_print' clang: error: linker command failed with exit code 1 (use -v to see invocation) ninja: build stopped: subcommand failed.
下面來配置一下,這裏就會學習到用查找的方式來將這個系統日誌的so的路徑給查出來,而不是手動去寫死,以下:
我們來看一下打印:
運行看一下是否打印出來日誌了:
可是!!其實上面在CMake引入log庫的方式還能夠簡化,以下:
另外還有一點就是我們目前的源文件只有一個,這樣配置沒啥問題:
那若是有不少源文件一個個這樣添加是否是很麻煩,這時能夠引用某個目錄下的全部源文件,以下:
另外還有一種指定目錄的方式,瞭解下:
若是在cmake中須要使用其餘目錄的cmakelist,能夠這樣,具體就不演示了:
另外對於頭文件的包含還有個小細節須要注意下,先新建一個頭文件:
若是源文件中想要引用這個頭文件,能夠這樣寫:
可是還能夠在CMake中加入這樣一個配置來支持這樣的寫法,以下:
具體配置以下:
其實這句話配置就至關於:
此時再編譯源文件就沒報紅了:
至此關於.mk和cmake相關配置的東東就學到這了,比上次學習還雜,可是收穫仍是頗多滴~~