前言
之前有一篇的文章介绍了通过CMake来进行Android NDK开发的入门文章。写那篇文章的时候由于内容较多,
所以有的内容没有细说,就一笔带过了。这篇文章算是一个小小的补充。所以,建议看这篇文章的小伙伴,先阅读之前的博文。
内容
使用CMake来进行Android NDK 开发这篇博文说道,向现有的项目里添加原生代
码的步骤分为***三大步***:
- 创建新的原生源文件。
- 创建CMake构建脚本。
- 将Gradle关联到你的原生库。
第一步比较简单就不说了,第二步对CMake构建脚本即CMakeLists.txt文件的构建
说明,先看一张图:
这张图片是系统自动给我们生成的CMakeLists.txt内容,当时我们只说明了add_library命令的作用,
那现在我们看一下find_library 代表什么,根据Google文档,find_library表示定位NDK库,图片上关于这个命令的描述说明还挺好理解的,
查找一个特殊的预构建库并且把其存储路径设置为一个变量。那这个命令里面第一个值是路径变量的名称,
第二个就是你想要CMake去定位的库的具体名字。我们为什么要定位一个NDK库呢?是这样的,Android平台上有一些系统的预构建库,这些系统的库不需要再构建,打包到apk包中的。直接指定其的路径就可以使用了。那现在用了find_library就可以直接使用系统的预构建库了么?还不行,我们还差一个target_link_libraries命令,这个命令表示:关联目标库和其他库。
find_library(...)
# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
native-lib
# Links the log library to the target library.
${log-lib} )
如图这样设置后,native-lib库中就可以调用log库的函数了。
其实在我们创建支持C/C++的新项目时,系统就自动的创建了CMakeLists.txt文件,里面有默认的定位 Android 特定的日志支持库。
添加其他的预构建库
刚刚说了是添加系统的预构建库,使用了find_library命令,那添加其他的非系统的预构建库,还是使用find_library 么?不是的了,是使用add_library。咦?之前我们指定要构建的库就是用这个命令啊,现在添加其他的预构建库也用这个命令哇~~确实使用这个命令,不过呢,还是有区别的。在使用add_libraray 添加其他的预构建库时需要,用***IMPORTED***这个作为标志。格式如下:
add_library( imported-lib
SHARED
IMPORTED )
然后,我们需要使用 set_target_properties() 命令指定库的路径,如下所示。
某些库为特定的 CPU 架构提供了单独的软件包,并将其组织到单独的目录中。此方法既有助于库充分利用特定的 CPU 架构,又能让你仅使用所需的库版本。要向 CMake 构建脚本中添加库的多个 ABI 版本,而不必为库的每个版本编写多个命令,你可以使用 ANDROID_ABI 路径变量。此变量使用 NDK 支持的一组默认 ABI,或者你手动配置 Gradle 而让其使用的一组经过筛选的 ABI。例如:
add_library(...)
set_target_properties( # Specifies the target library.
imported-lib
# Specifies the parameter you want to define.
PROPERTIES IMPORTED_LOCATION
# Provides the path to the library you want to import.
imported-lib/src/${ANDROID_ABI}/libimported-lib.so )
为了确保 CMake 可以在编译时定位您的标头文件,您需要使用 include_directories() 命令,并包含标头文件的路径:
include_directories( imported-lib/include/ )
常用的一些构建命令就介绍到这,有其他的需求,大家可以看官方CMake命令文档cmake命令文档
将Gradle关联到原生仓库的第二种方式
第二步说完,我们现在看第三步,之前的博文中介绍了,gradle关联原生仓库的第一种方式。是通过as的快捷键来实现的,右键点击你想要关联到原生库的模块(例如 app 模块),
并从菜单中选择 Link C++ Project with Gradle。
BuildSystem选择CMake,projectpath就是CMakeLists.txt文件的路径。点击ok完成。 如下图:
这样呢,就会在应用模块下的build.gradle文件中,android闭包下出现:
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
这样的语块。
那我们的第二种方式就是手动的来修改build.gradle文件夹,来实现关联的目的。时光倒退到向现有的项目里添加原生代码的步骤 三大步 中的第二步。我们在第二步完成之后,不通过as 的快捷键,我们直接在build.gradle的文件写入
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
也是可以的。。。当然,可以是可以,我们这里肯定不止这个。我们刚刚有到上面出现代码是在android闭包内的。事实上呢,在build.gradle文件的defaultConfig块中也可以设置externalNativeBuild {}块,为 CMake 指定可选参数和标志。先上代码:
android {
...
defaultConfig {
...
// This block is different from the one you use to link Gradle
// to your CMake or ndk-build script.
externalNativeBuild {
// For ndk-build, instead use ndkBuild {}
cmake {
// Passes optional arguments to CMake.
arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
// Sets optional flags for the C compiler.
cFlags "-D_EXAMPLE_C_FLAG1", "-D_EXAMPLE_C_FLAG2"
// Sets a flag to enable format macro constants for the C++ compiler.
cppFlags "-D__STDC_FORMAT_MACROS"
}
}
}
buildTypes {...}
productFlavors {
...
demo {
...
externalNativeBuild {
cmake {
...
// Specifies which native libraries to build and package for this
// product flavor. If you don't configure this property, Gradle
// builds and packages all shared object libraries that you define
// in your CMake or ndk-build project.
targets "native-lib-demo"
}
}
}
paid {
...
externalNativeBuild {
cmake {
...
targets "native-lib-paid"
}
}
}
}
// Use this block to link Gradle to your CMake or ndk-build script.
externalNativeBuild {
cmake {...}
// or ndkBuild {...}
}
}
这个代码是我从官网山得到sample,我们看到defaultConfig闭包内是存在了externalNativeBuild {},并且配置了一些属性。arguments “-DANDROID_ARM_NEON=TRUE”, “-DANDROID_TOOLCHAIN=clang”
"-DANDROID_ARM_NEON=TRUE"表示:是否让CMake构建原生库时支持“NEON”,默认的是不支持。
DANDROID_TOOLCHAIN=clang" 这个参数不知道大家还不记得这个图
这是当时创建支持原生代码的新项目时,我们见到界面,C++ Standard这个复选框,我们选择
Toolchain Default选项。这与DANDROID_TOOLCHAIN=clang意思一样,支持CMake构建原生库的意思。
cFlags “-D_EXAMPLE_C_FLAG1”, “-D_EXAMPLE_C_FLAG2”
cppFlags “-D__STDC_FORMAT_MACROS”
这两个配置,虽说能根据英文翻译出个大概,但是有点不确定含义,没用过。从网上也没搜到满意的回答。如果知道的大佬,可以说明一下,(__) 嘻嘻……
接着看上面的代码,我们发现externalNativeBuild {}块,不仅仅出现在defaultConfig闭包内,也出现在了,productFlavor块中。这是什么情况!!??仔细想想,其实,也好理解,productFlavor闭包中其他的渠道之前都是可以重写defaultConfig内的属性的。所以他自然也可以了区分各个渠道,重写externalNativeBuild {}内的内容哇。这样,每个渠道的包就有他自己的属性配置。多和谐~~
上面的代码中我们发现了,targets属性,这个是面对这种情况的。如果你的 CMake 或 ndk-build 项目定义多个原生库,你可以使用 targets 属性仅为给定渠道构建和打包这些库中的一部分。
好了,补充内容补充的差不多了,在国庆放假前一天静下心来补充这个,真是难熬/(ㄒoㄒ)/~~。。
最后再补充一个,之前博文在最后展示打包进入apk的so文件的时候,给出了下面的一张图:
我们看到了x86,armeabi、armeabi-v7a,mips只有这四个ABI。你们跑的时候可能出现了:
7个ABI,看一些size,apk大小的45.9%。多么可怕,这是不允许的。而且也不需要适配这么多的ABI。所以我们需要指定ABI。如何做呢?在build.gradle文件的defaultConfig闭包下加入如下语句即可:
ndk {
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
}
上诉语句就表明会打出对应与’x86’, ‘x86_64’, ‘armeabi’, ‘armeabi-v7a’,这四个ABI的库。
好了,补充篇到此结束,安心过节啦~
最后列出与此篇博文对应的上篇博文地址:
使用CMake来进行Android NDK 开发
PalmRead
扫码加入我的个人微信公众号:Android开发圈 ,一起学习Android知识!!