Android.mk走讀與Cmake配置


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.solibhello-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相關配置的東東就學到這了,比上次學習還雜,但是收獲還是頗多滴~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM