美文网首页
「NDK」二 CMake详解

「NDK」二 CMake详解

作者: 叨码 | 来源:发表于2019-07-11 15:04 被阅读0次
一 CMake是谁

在版本2.2及以上,构建原生库的默认工具变成了CMake.CMake是一个跨平台的构建工具,能够输出各种各样的makefile或者project文件。
CMake并不直接构建出最终的软件,而是产生其他工具的脚本如Makefile,然后再依照这个工具的构建方式使用。
CMake是一个比make更高级的编译配置工具,它可以根据不同的平台,不同的编译器,生成相应的Makefile或者vcproj项目,从而达到跨平台的目的。
Android Studio利用CMake生成的ninja,ninja是一个小型的关注速度的构建系统。这玩意儿是什么,我们不用去太多关注,只需要知道怎么配置cmake就可以了。

在我们创建项目的时候,如果勾选Include C++ Support ,就会在main的同级目录下生成一个CMakeLists.txt,下面来一一介绍

# 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.设置创建本地库所需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.
#库的创建和命名,将其设置为STATIC或者SHARED
#并提供其源代码的相对路径
#您可以定义多个库,CMake为您构建他们
#用APK自动打包共享库
#2.编译library
add_library( # Sets the name of the library.设置库的名称
        native-lib

        # Sets the library as a shared library.设置为共享库
        #设置library模式
        #SHARED 模式会编译so文件,STATIC模式不会编译
        SHARED

        # Provides a relative path to your source file(s).
        #设置原生代码路径
        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.
#搜索指定的预构建库并将路径存储为变量,因为CMake默认包含搜索路径中的系统库,您只需指定要添加的公共NDK库的名称即可。
#CMake在完成构建之前验证该库是否存在
find_library( # Sets the name of the path variable.设置路径变量的名称
        log-lib

        # Specifies the name of the NDK library that 指定希望CMake定位的NDK库的名称
        # 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.
#指定库CMake 应该链接到目标库,您可以链接多个库,作为每个构建脚本中定义的库,预构建的第三方库和系统库
target_link_libraries( # Specifies the target library.指定目标库
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        #将目标库链接到日志库,包含在NDK中
        ${log-lib})

对应编号代码解释:
【1】指定CMake所需最小版本,自动生成,无需更改
【2】add_library():用来设置编译生成原生库的相关属性:
native-lib : 你要生成原生库的名称
SHARE : 设置库的类型,分为SHARE 动态链接库 STATIC 静态链接库
src/main/cpp/native-lib.cpp:
您写的c/c++代码的相对路径,目前我cpp目录中有个native-lib.cpp文件,如果再添加一个native-libw2.cpp,则可直接在下面添加:

add_library( # Sets the name of the library.设置库的名称
        native-lib

        # Sets the library as a shared library.设置为共享库
        #设置library模式
        #SHARED 模式会编译so文件,STATIC模式不会编译
        SHARED

        # Provides a relative path to your source file(s).
        #设置原生代码路径
        src/main/cpp/native-lib.cpp
        src/mainn/cpp/native-lib2.cpp)

【3】 添加其他预构建库
添加预构建库与为 CMake 指定要构建的另一个原生库类似。不过,由于库已经预先构建,您需要使用 [IMPORTED]标志告知 CMake 您只希望将库导入到项目中也要用到add_library(),然后,你需要使用set_target_properties命令指定库的路径,比如我们将编译的ffmpeg相关so库集成到Android项目时所作的设置一样,已经编译好的so库就是这里所说的预构建库,在引入时只需要这般配置(以ffmpeg中的avcodec库为例)

# avcodec
add_library( avcodec-57
        SHARED
        #IMPROTED说明只是导入不需构建
        IMPORTED )
set_target_properties( 
        #指定目标库
        avcodec-57
        #指定要定义的参数
        PROPERTIES IMPORTED_LOCATION
        #指定库路径 armeabi-v7a文件夹下的so库
        ${distribution_DIR}/armeabi-v7a/libavcodec-57.so )

另外确保CMake可以在编译时定位到你的头文件,你需要使用include_directories()命令,指定包含头文件的路径

include_directories(你的真实路径/include/ )

如果要将预构建库关联到你的原生库,需要将其添加到CMake构建脚本的target_link_libraries()命令中:
target_link_libraries(native-lib avcodec-57 ${log-lib})
在你构建应用时,Gradle会自动将导入的库打包到APK中。
【4】添加NDK API
find_library():Android NDK 提供了一套实用的原生 API 和库,因其已经存在于Android平台中,而且NDK库已是CMake搜索路径的一部分,所以只需要提供要使用的库名称
target_link_libraries():上面提到过,这里就是将你指定的NDK库关联到你的原生库native-lib里,之后你的原生库就可以在 log 库中调用函数了。

find_library( # Sets the name of the path variable.设置路径变量的名称
        log-lib

        # Specifies the name of the NDK library that 指定希望CMake定位的NDK库的名称
        # you want CMake to locate.
        log)
target_link_libraries( # Specifies the target library.指定目标库
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        #将目标库链接到日志库,包含在NDK中
        ${log-lib})
  • log-lib : 依赖库的别名
  • log : 你要依赖的NDK库名称
  • native-lib : 您自己的库,和add_library中的名称一致
  • ${log-lib} : 指定需要关联的库的名称
二.使用CMake生成so库

现在编译项目,我们会在 <项目目录>\app\build\intermediates\cmake\debug\obj\armeabi 下面就可以看到生成的动态链接库。
如果觉得上面的so库路径太深了,可以自行配置,只需要在顶层的CMakeLists.txt中添加下面代码即可:

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

编译运行后,会在app/src/main 中看到jniLibs目录(这里是配置到了系统默认的路径)如果配置到其他路径比如libs下,需要在gradle中使用:

//引入so库
sourceSets {
    main {
        jniLibs.srcDirs = ['libs']
    }
}

来进行制定。
然后在app下的build.gradle中指定abi abiFilters 如果不指定,会生成所有当前支持的abi库
这里延展下,什么是ABI?
ABI(Application binary interface)应用程序二进制接口。不同的cpu与指令集的每种组合都有定义的ABI(应用程序二进制接口),一段程序只有遵循这个接口规范才能在该cpu上运行。所以同样的代码为了兼容多个不同的cpu,需要为不同的ABI构建不同的库文件:

armeabi设备只兼容armeabi; 
armeabi-v7a设备兼容armeabi-v7a、armeabi; 
arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi; 
X86设备兼容X86、armeabi; 
X86_64设备兼容X86_64、X86、armeabi; 
mips64设备兼容mips64、mips; 
mips只兼容mips;

当我们开发或者使用原生代码时就需要了解不同 ABI 以及为自己的程序选择接入不同 ABI 的库.

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

指定之后只会生成相应的版本abi ,也就是只会生成x86,armeabi-v7a下的so文件。
那么abiFilter是作什么用的呢?貌似我们在我们的android项目中经常会看到类似的配置:

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

两者的配置功能类似:
第一个的配置决定了生成的支持什么abi的so库。
第二个的作用呢?举个例子:
如果我们的项目中引入了某个sdk,这个sdk支持armeabi、armeabi-v7a、arm64-v8a、x86、x86_64 五种ABI,但是我们的目中只支持armeabi-v7a、x86两个目录中,应用安装在armeabi-v7a、x86架构的设备上是没有问题的,但是安装在其他架构的设备上就会出现如下异常:

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

这种该怎么解决呢?

  • 方法一 将多出来的ABI删除掉,可行,但如果是远程库引用就无效了。
  • 方法二 限制安装包ABI的架构类型,不管sdk或者项目中有多少种ABI架构,最终打包进APK的so库都以abiFilters中规定的为准。
    也就是说,如果我们的项目中有五种ABI库,但是abiFilters设置了两种ABI支持,那么最终打包的APK中只会包含这两种ABI库。

在实际应用中,除了上述问题,还会经常遇到这种报错:

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

原因在于 NDK-r17版本,已经去掉了armeabi、mips、mips64的ABI支持。
碰到上述报错,只需去掉abiFilters中的armeabi选项即可,放心!armeabi-v7a会兼容armeabi。

相关文章

网友评论

      本文标题:「NDK」二 CMake详解

      本文链接:https://www.haomeiwen.com/subject/yqcdkctx.html