「NDK」二 CMake詳解

一 CMake是誰(shuí)

在版本2.2及以上,構(gòu)建原生庫(kù)的默認(rèn)工具變成了CMake.CMake是一個(gè)跨平臺(tái)的構(gòu)建工具,能夠輸出各種各樣的makefile或者project文件。
CMake并不直接構(gòu)建出最終的軟件,而是產(chǎn)生其他工具的腳本如Makefile,然后再依照這個(gè)工具的構(gòu)建方式使用。
CMake是一個(gè)比make更高級(jí)的編譯配置工具,它可以根據(jù)不同的平臺(tái),不同的編譯器,生成相應(yīng)的Makefile或者vcproj項(xiàng)目,從而達(dá)到跨平臺(tái)的目的。
Android Studio利用CMake生成的ninja,ninja是一個(gè)小型的關(guān)注速度的構(gòu)建系統(tǒng)。這玩意兒是什么,我們不用去太多關(guān)注,只需要知道怎么配置cmake就可以了。

在我們創(chuàng)建項(xiàng)目的時(shí)候,如果勾選Include C++ Support ,就會(huì)在main的同級(jí)目錄下生成一個(gè)CMakeLists.txt,下面來(lái)一一介紹

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.
#1.設(shè)置創(chuàng)建本地庫(kù)所需CMake最小版本
cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#庫(kù)的創(chuàng)建和命名,將其設(shè)置為STATIC或者SHARED
#并提供其源代碼的相對(duì)路徑
#您可以定義多個(gè)庫(kù),CMake為您構(gòu)建他們
#用APK自動(dòng)打包共享庫(kù)
#2.編譯library
add_library( # Sets the name of the library.設(shè)置庫(kù)的名稱(chēng)
        native-lib

        # Sets the library as a shared library.設(shè)置為共享庫(kù)
        #設(shè)置library模式
        #SHARED 模式會(huì)編譯so文件,STATIC模式不會(huì)編譯
        SHARED

        # Provides a relative path to your source file(s).
        #設(shè)置原生代碼路徑
        src/main/cpp/native-lib.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
#搜索指定的預(yù)構(gòu)建庫(kù)并將路徑存儲(chǔ)為變量,因?yàn)镃Make默認(rèn)包含搜索路徑中的系統(tǒng)庫(kù),您只需指定要添加的公共NDK庫(kù)的名稱(chēng)即可。
#CMake在完成構(gòu)建之前驗(yàn)證該庫(kù)是否存在
find_library( # Sets the name of the path variable.設(shè)置路徑變量的名稱(chēng)
        log-lib

        # Specifies the name of the NDK library that 指定希望CMake定位的NDK庫(kù)的名稱(chēng)
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
#指定庫(kù)CMake 應(yīng)該鏈接到目標(biāo)庫(kù),您可以鏈接多個(gè)庫(kù),作為每個(gè)構(gòu)建腳本中定義的庫(kù),預(yù)構(gòu)建的第三方庫(kù)和系統(tǒng)庫(kù)
target_link_libraries( # Specifies the target library.指定目標(biāo)庫(kù)
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        #將目標(biāo)庫(kù)鏈接到日志庫(kù),包含在NDK中
        ${log-lib})

對(duì)應(yīng)編號(hào)代碼解釋?zhuān)?br> 【1】指定CMake所需最小版本,自動(dòng)生成,無(wú)需更改
【2】add_library():用來(lái)設(shè)置編譯生成原生庫(kù)的相關(guān)屬性:
native-lib : 你要生成原生庫(kù)的名稱(chēng)
SHARE : 設(shè)置庫(kù)的類(lèi)型,分為SHARE 動(dòng)態(tài)鏈接庫(kù) STATIC 靜態(tài)鏈接庫(kù)
src/main/cpp/native-lib.cpp:
您寫(xiě)的c/c++代碼的相對(duì)路徑,目前我cpp目錄中有個(gè)native-lib.cpp文件,如果再添加一個(gè)native-libw2.cpp,則可直接在下面添加:

add_library( # Sets the name of the library.設(shè)置庫(kù)的名稱(chēng)
        native-lib

        # Sets the library as a shared library.設(shè)置為共享庫(kù)
        #設(shè)置library模式
        #SHARED 模式會(huì)編譯so文件,STATIC模式不會(huì)編譯
        SHARED

        # Provides a relative path to your source file(s).
        #設(shè)置原生代碼路徑
        src/main/cpp/native-lib.cpp
        src/mainn/cpp/native-lib2.cpp)

【3】 添加其他預(yù)構(gòu)建庫(kù)
添加預(yù)構(gòu)建庫(kù)與為 CMake 指定要構(gòu)建的另一個(gè)原生庫(kù)類(lèi)似。不過(guò),由于庫(kù)已經(jīng)預(yù)先構(gòu)建,您需要使用 [IMPORTED]標(biāo)志告知 CMake 您只希望將庫(kù)導(dǎo)入到項(xiàng)目中也要用到add_library(),然后,你需要使用set_target_properties命令指定庫(kù)的路徑,比如我們將編譯的ffmpeg相關(guān)so庫(kù)集成到Android項(xiàng)目時(shí)所作的設(shè)置一樣,已經(jīng)編譯好的so庫(kù)就是這里所說(shuō)的預(yù)構(gòu)建庫(kù),在引入時(shí)只需要這般配置(以ffmpeg中的avcodec庫(kù)為例)

# avcodec
add_library( avcodec-57
        SHARED
        #IMPROTED說(shuō)明只是導(dǎo)入不需構(gòu)建
        IMPORTED )
set_target_properties( 
        #指定目標(biāo)庫(kù)
        avcodec-57
        #指定要定義的參數(shù)
        PROPERTIES IMPORTED_LOCATION
        #指定庫(kù)路徑 armeabi-v7a文件夾下的so庫(kù)
        ${distribution_DIR}/armeabi-v7a/libavcodec-57.so )

另外確保CMake可以在編譯時(shí)定位到你的頭文件,你需要使用include_directories()命令,指定包含頭文件的路徑

include_directories(你的真實(shí)路徑/include/ )

如果要將預(yù)構(gòu)建庫(kù)關(guān)聯(lián)到你的原生庫(kù),需要將其添加到CMake構(gòu)建腳本的target_link_libraries()命令中:
target_link_libraries(native-lib avcodec-57 ${log-lib})
在你構(gòu)建應(yīng)用時(shí),Gradle會(huì)自動(dòng)將導(dǎo)入的庫(kù)打包到APK中。
【4】添加NDK API
find_library():Android NDK 提供了一套實(shí)用的原生 API 和庫(kù),因其已經(jīng)存在于Android平臺(tái)中,而且NDK庫(kù)已是CMake搜索路徑的一部分,所以只需要提供要使用的庫(kù)名稱(chēng)
target_link_libraries():上面提到過(guò),這里就是將你指定的NDK庫(kù)關(guān)聯(lián)到你的原生庫(kù)native-lib里,之后你的原生庫(kù)就可以在 log 庫(kù)中調(diào)用函數(shù)了。

find_library( # Sets the name of the path variable.設(shè)置路徑變量的名稱(chēng)
        log-lib

        # Specifies the name of the NDK library that 指定希望CMake定位的NDK庫(kù)的名稱(chēng)
        # you want CMake to locate.
        log)
target_link_libraries( # Specifies the target library.指定目標(biāo)庫(kù)
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        #將目標(biāo)庫(kù)鏈接到日志庫(kù),包含在NDK中
        ${log-lib})
  • log-lib : 依賴(lài)庫(kù)的別名
  • log : 你要依賴(lài)的NDK庫(kù)名稱(chēng)
  • native-lib : 您自己的庫(kù),和add_library中的名稱(chēng)一致
  • ${log-lib} : 指定需要關(guān)聯(lián)的庫(kù)的名稱(chēng)
二.使用CMake生成so庫(kù)

現(xiàn)在編譯項(xiàng)目,我們會(huì)在 <項(xiàng)目目錄>\app\build\intermediates\cmake\debug\obj\armeabi 下面就可以看到生成的動(dòng)態(tài)鏈接庫(kù)。
如果覺(jué)得上面的so庫(kù)路徑太深了,可以自行配置,只需要在頂層的CMakeLists.txt中添加下面代碼即可:

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

編譯運(yùn)行后,會(huì)在app/src/main 中看到j(luò)niLibs目錄(這里是配置到了系統(tǒng)默認(rèn)的路徑)如果配置到其他路徑比如libs下,需要在gradle中使用:

//引入so庫(kù)
sourceSets {
    main {
        jniLibs.srcDirs = ['libs']
    }
}

來(lái)進(jìn)行制定。
然后在app下的build.gradle中指定abi abiFilters 如果不指定,會(huì)生成所有當(dāng)前支持的abi庫(kù)
這里延展下,什么是ABI?
ABI(Application binary interface)應(yīng)用程序二進(jìn)制接口。不同的cpu與指令集的每種組合都有定義的ABI(應(yīng)用程序二進(jìn)制接口),一段程序只有遵循這個(gè)接口規(guī)范才能在該cpu上運(yùn)行。所以同樣的代碼為了兼容多個(gè)不同的cpu,需要為不同的ABI構(gòu)建不同的庫(kù)文件:

armeabi設(shè)備只兼容armeabi; 
armeabi-v7a設(shè)備兼容armeabi-v7a、armeabi; 
arm64-v8a設(shè)備兼容arm64-v8a、armeabi-v7a、armeabi; 
X86設(shè)備兼容X86、armeabi; 
X86_64設(shè)備兼容X86_64、X86、armeabi; 
mips64設(shè)備兼容mips64、mips; 
mips只兼容mips;

當(dāng)我們開(kāi)發(fā)或者使用原生代碼時(shí)就需要了解不同 ABI 以及為自己的程序選擇接入不同 ABI 的庫(kù).

defaultConfig {
 
       ......
       ......
       ......
 
        externalNativeBuild {
            cmake {
                cppFlags ""
                abiFilters 'x86','armeabi-v7a'; 
            }
        }
    }

指定之后只會(huì)生成相應(yīng)的版本abi ,也就是只會(huì)生成x86,armeabi-v7a下的so文件。
那么abiFilter是作什么用的呢?貌似我們?cè)谖覀兊腶ndroid項(xiàng)目中經(jīng)常會(huì)看到類(lèi)似的配置:

defaultConfig {
    ...
    ndk {
        abiFilters "armeabi-v7a", "x86"; 
    }
}

兩者的配置功能類(lèi)似:
第一個(gè)的配置決定了生成的支持什么abi的so庫(kù)。
第二個(gè)的作用呢?舉個(gè)例子:
如果我們的項(xiàng)目中引入了某個(gè)sdk,這個(gè)sdk支持armeabi、armeabi-v7a、arm64-v8a、x86、x86_64 五種ABI,但是我們的目中只支持armeabi-v7a、x86兩個(gè)目錄中,應(yīng)用安裝在armeabi-v7a、x86架構(gòu)的設(shè)備上是沒(méi)有問(wèn)題的,但是安裝在其他架構(gòu)的設(shè)備上就會(huì)出現(xiàn)如下異常:

java.lang.UnsatisfiedLinkError: Couldn't load native-lib from loader dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.yl.ndkdemo-1.apk"]
nativeLibraryDirectories=[/data/app-lib/com.yl.ndkdemo-1, /vendor/lib, /system/lib, /system/lib/arm]]]: findLibrary returned null

這種該怎么解決呢?

  • 方法一 將多出來(lái)的ABI刪除掉,可行,但如果是遠(yuǎn)程庫(kù)引用就無(wú)效了。
  • 方法二 限制安裝包ABI的架構(gòu)類(lèi)型,不管sdk或者項(xiàng)目中有多少種ABI架構(gòu),最終打包進(jìn)APK的so庫(kù)都以abiFilters中規(guī)定的為準(zhǔn)。
    也就是說(shuō),如果我們的項(xiàng)目中有五種ABI庫(kù),但是abiFilters設(shè)置了兩種ABI支持,那么最終打包的APK中只會(huì)包含這兩種ABI庫(kù)。

在實(shí)際應(yīng)用中,除了上述問(wèn)題,還會(huì)經(jīng)常遇到這種報(bào)錯(cuò):

ABIs [armeabi] are not supported for platform. Supported ABIs are [armeabi-v7a, arm64-v8a, x86, x86_64].

原因在于 NDK-r17版本,已經(jīng)去掉了armeabi、mips、mips64的ABI支持。
碰到上述報(bào)錯(cuò),只需去掉abiFilters中的armeabi選項(xiàng)即可,放心!armeabi-v7a會(huì)兼容armeabi。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容