Android.mk認識:
在上一次【https://www.cnblogs.com/webor2006/p/9946061.html】中學會了用NDK提供的交叉編譯工程編譯成Android能運行的可執行文件,下面咱們來做個實驗來看一下使用靜態庫與動態庫的區別,還是用上一次用的源文件為例:
動態庫的具體的生成過程可以參考上一次寫的博文,接下來再生成一個靜態庫,如何生成呢?
所以咱們先來找到NDK提供的ar交叉編譯工具:
所以咱們使用它依照生成規則來生成對應的靜態庫看一下:
接下來咱們新建一個Android工程,來使用編譯出來的動靜態庫,這里還是建立一個純的非NDK的Android工程,來進一步操練下如何給一個普通Android工程來集成NDK,如下:
然后咱們來集成ndk環境,回顧上一次的步驟,得修改app下的build.gradle配置了:
這個是針對源文件的,而我們想要用Android.mk來進行構建就需要在外層來指定了,如下:
其實上一次已經使用過了,里面Android.mk的編寫規則也記不住,直接拷貝一下,重點不是記着,還是你能讀懂就成,如下:
然后咱們在對應位置建立一個native-lib.c的源文件:
那咱們編譯一個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 適用於基於 ARMv8、支持 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相關配置的東東就學到這了,比上次學習還雜,但是收獲還是頗多滴~~