Android NDK开发扫盲及最新CMake的编译使用
2017-11-18 15:53
716 查看
1 NDK 简介
在介绍NDK之前还是首推 Android 官方
NDK文档。传送门
官方文档分别从以下几个方面介绍了
NDK
NDK的基础概念
如何编译
NDK项目
ABI是什么以及不同 CPU 指令集支持哪些
ABI
如何使用您自己及其他预建的库
本节将会对文档进行总结和补充。所以建议先浏览一遍文档,或者看完本篇文章再回头看一遍文档。
1.1 NDK 基础概念
首先先用简单的话分别解释下JNI、
NDK, 以及分别和 Android 开发、c/c++ 开发的配合。在解释过程中会对
Android.mk、
Application.mk、
ndk-build、
CMake、
CMakeList这些常见名词进行扫盲。
JNI(Java Native Interface):Java本地接口。是为了方便Java调用c、c++等本地代码所封装的一层接口(也是一个标准)。大家都知道,Java的优点是跨平台,但是作为优点的同时,其在本地交互的时候就编程了缺点。Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,于是Java提供了jni专门用于和本地代码交互,这样就增强了Java语言的本地交互能力。上述部分文字摘自任玉刚的 Java JNI 介绍
NDK(Native Development Kit) : 原生开发工具包,即帮助开发原生代码的一系列工具,包括但不限于编译工具、一些公共库、开发IDE等。
NDK工具包中提供了完整的一套将 c/c++ 代码编译成静态/动态库的工具,而
Android.mk和
Application.mk你可以认为是描述编译参数和一些配置的文件。比如指定使用c++11还是c++14编译,会引用哪些共享库,并描述关系等,还会指定编译的
abi。只有有了这些
NDK中的编译工具才能准确的编译 c/c++ 代码。
ndk-build文件是 Android NDK r4 中引入的一个 shell 脚本。其用途是调用正确的
NDK构建脚本。其实最终还是会去调用
NDK自己的编译工具。
那
CMake又是什么呢。脱离 Android 开发来看,c/c++ 的编译文件在不同平台是不一样的。Unix 下会使用
makefile文件编译,Windows 下会使用
project文件编译。而
CMake则是一个跨平台的编译工具,它并不会直接编译出对象,而是根据自定义的语言规则(
CMakeLists.txt)生成 对应
makefile或
project文件,然后再调用底层的编译。
在Android Studio 2.2 之后,工具中增加了
CMake的支持,你可以这么认为,在 Android Studio 2.2 之后你有2种选择来编译你写的 c/c++ 代码。一个是
ndk-build+
Android.mk+
Application.mk组合,另一个是
CMake+
CMakeLists.txt组合。这2个组合与Android代码和c/c++代码无关,只是不同的构建脚本和构建命令。本篇文章主要会描述后者的组合。(也是Android现在主推的)
1.2 ABI 是什么
ABI(Application binary interface)应用程序二进制接口。不同的CPU 与指令集的每种组合都有定义的
ABI(应用程序二进制接口),一段程序只有遵循这个接口规范才能在该 CPU 上运行,所以同样的程序代码为了兼容多个不同的CPU,需要为不同的
ABI构建不同的库文件。当然对于CPU来说,不同的架构并不意味着一定互不兼容。
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;
具体的兼容问题可以参见这篇文章。Android SO文件的兼容和适配
当我们开发 Android 应用的时候,由于 Java 代码运行在虚拟机上,所以我们从来没有关心过这方面的问题。但是当我们开发或者使用原生代码时就需要了解不同
ABI以及为自己的程序选择接入不同
ABI的库。(库越多,包越大,所以要有选择)
下面我们来看下一共有哪些
ABI以及对应的指令集
ABI
2 CMake 的使用
这一节将重点介绍CMake的规则和使用,以及如何使用
CMake编译自己及其他预建的库。
2.1 Hello world
我们通过一个Hello World项目来理解CMake
首先创建一个新的包含原生代码的项目。在 New Project 时,勾选 Include C++ support
New Project
项目创建好以后我们可以看到和普通Android项目有以下4个不同。
main下面增加了
cpp目录,即放置 c/c++ 代码的地方
module-level 的
build.gradle有修改
增加了
CMakeLists.txt文件
多了一个
.externalNativeBuild目录
Difference
build.gradle
android { ... defaultConfig { ... externalNativeBuild { cmake { cppFlags "-frtti -fexceptions" arguments "-DANDROID_ARM_NEON=TRUE" } } } buildTypes { ... } externalNativeBuild { cmake { path "CMakeLists.txt" } } } ...由于
CMake的命令集成在了
gradle-
externalNativeBuild中,所以在
gradle中有2个地方配置
CMake。
defaultConfig外面的
externalNativeBuild - cmake,指明了
CMakeList.txt的路径;
defaultConfig里面的
externalNativeBuild - cmake,主要填写
CMake的命令参数。即由
arguments中的参数最后转化成一个可执行的
CMake的命令,可以在
.externalNativeBuild/cmake/debug/{abi}/cmake_build_command.txt中查到。如下
cmake command更多的可以填写的命令参数和含义可以参见Android NDK-CMake文档
CMakeLists.txt
CMakeLists.txt中主要定义了哪些文件需要编译,以及和其他库的关系等。
看下新项目中的
CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1) # 编译出一个动态库 native-lib,源文件只有 src/main/cpp/native-lib.cpp add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-lib.cpp ) # 找到预编译库 log_lib 并link到我们的动态库 native-lib中 find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # 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. ${log-lib} )这其实是一个最基本的
CMakeLists.txt,其实
CMakeLists.txt里面可以非常强大,比如自定义命令、查找文件、头文件包含、设置变量等等。建议结合
CMake的官方文档使用。同时在这推荐一个中文翻译的简易的CMake手册
2.2 CMake 使用自己及其他预建的库
当你需要引入已有的静态库/动态库(FFMpeg)或者自己编译核心部分并提供出去时就需要考虑如何在CMake中使用自己及其他预建的库。
Android NDK 官网的使用现有库的文档中还是使用
ndk-build+
Android.mk+
Application.mk组合的说明文档。(其实官方文档中大部分都是的,并没有使用
CMake)
幸运的是, Github上的官方示例 里面有个项目 hello-libs 实现了如何创建出静态库/动态库,并引用它。现在我们把代码拉下来看下具体是如何实现的。
hello-libs我们先看下Github上的README介绍:
app - 从
$project/distribution/中使用一个静态库和一个动态库
gen-libs - 生成一个动态库和一个静态库并复制到
$project/distribution/目录,你不需要再编译这个库,二进制文件已经保存在了项目中。当然,如果有需要你也可以编译自己的源码,只需要去掉
setting.gradle和
app/build.gradle中的注释,然后执行一次,接着注释回去,防止在 build 的过程中不受影响。
我们采用自底向上的方式分析模块,先看下 gen-libs
模块。
gen-libs/build.gradleandroid { ... defaultConfig { ... externalNativeBuild { cmake { arguments '-DANDROID_PLATFORM=android-9', '-DANDROID_TOOLCHAIN=clang' // explicitly build libs targets 'gmath', 'gperf' } } } ... } ...查询文档可以知道
arguments中
-DANDROID_PLATFORM代表编译的 android 平台,文档建议直接设置
minSdkVersion就行了,所以这个参数可忽略。另一个参数
-DANDROID_TOOLCHAIN=clang,
CMake一共有2种编译工具链 -
clang和
gcc,
gcc已经废弃,
clang是默认的。
targets 'gmath', 'gperf'代表编译哪些项目。(不填就是都编译)
cpp/CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1) set(CMAKE_VERBOSE_MAKEFILE on) set(lib_src_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(lib_build_DIR $ENV{HOME}/tmp) file(MAKE_DIRECTORY ${lib_build_DIR}) add_subdirectory(${lib_src_DIR}/gmath ${lib_build_DIR}/gmath) add_subdirectory(${lib_src_DIR}/gperf ${lib_build_DIR}/gperf)外层的
CMakeLists里面核心就是
add_subdirectory,查询CMake 官方文档 可以知道这条命令的作用是为构建添加一个子路径。子路径中的
CMakeLists.txt也会被执行。即会去分别执行
gmath和
gperf中的
CMakeLists.txt
cpp/gmath/CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1) set(CMAKE_VERBOSE_MAKEFILE on) add_library(gmath STATIC src/gmath.c) # copy out the lib binary... need to leave the static lib around to pass gradle check set(distribution_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../distribution) set_target_properties(gmath PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${distribution_DIR}/gmath/lib/${ANDROID_ABI}") # copy out lib header file... add_custom_command(TARGET gmath POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/gmath.h" "${distribution_DIR}/gmath/include/gmath.h" # **** the following 2 lines are for potential future debug purpose **** # COMMAND "${CMAKE_COMMAND}" -E # remove_directory "${CMAKE_CURRENT_BINARY_DIR}" COMMENT "Copying gmath to output directory")这个是其中一个静态库的
CMakeLists.txt,另一个跟他很像。只是把
STATIC改成了
SHARED(动态库)。
add_library(gmath STATIC src/gmath.c)之前用到过,编译出一个静态库,源文件是
src/gmath.c
set_target_properties命令的意思是设置目标的一些属性来改变它们构建的方式。这个命令中设置了
gmath的
ARCHIVE_OUTPUT_DIRECTORY属性。也就是改变了输出路径。
add_custom_command命令是自定义命令。命令中把头文件也复制到了
distribution_DIR中。
以上就是一个静态库/动态库的编译过程。总结以下3点
编译静态库/动态库
修改输出路径
复制暴露的头文件
接着,我们看下 app
模块是如何使用预建好的静态库/动态库的。
app/src/main/cpp/CMakeLists.txtcmake_minimum_required(VERSION 3.4.1) # configure import libs set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../distribution) # 创建一个静态库 lib_gmath 直接引用libgmath.a add_library(lib_gmath STATIC IMPORTED) set_target_properties(lib_gmath PROPERTIES IMPORTED_LOCATION ${distribution_DIR}/gmath/lib/${ANDROID_ABI}/libgmath.a) # 创建一个动态库 lib_gperf 直接引用libgperf.so add_library(lib_gperf SHARED IMPORTED) set_target_properties(lib_gperf PROPERTIES IMPORTED_LOCATION ${distribution_DIR}/gperf/lib/${ANDROID_ABI}/libgperf.so) # build application's shared lib set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") # 创建库 hello-libs add_library(hello-libs SHARED hello-libs.cpp) # 加入头文件 target_include_directories(hello-libs PRIVATE ${distribution_DIR}/gmath/include ${distribution_DIR}/gperf/include) # hello-libs库链接上 lib_gmath 和 lib_gperf target_link_libraries(hello-libs android lib_gmath lib_gperf log)我将解释放在了注释中。可以看下基本上分成了4个步骤引入:
分别创建静态库/动态库,直接引用已经有的 .a 文件 或者 .so 文件
创建自己应用的库
hello-libs
加入之前暴露头文件
链接上静态库/动态库
还是很好理解的。编辑好并
Sync后,你就可以发现
hello-libs中的c/c++代码可以引用暴露的头文件调用内部方法了。
3 资料文献
首推 Android NDK 官方文档,虽然很多都不完整,但是绝对是必须看一遍的东西。当初次接触
NDK开发又觉得新建的 Hello World 项目过于简单时。建议把 googlesamples - android-ndk 项目拉下来。里面有多个实例参考,比官方文档完整很多。
Google Samples当你发现示例里的一些NDK配置满足不了你的需求后,你就需要到 CMake 官方文档 去查询完整的支持的函数,同时这里也提供一个中文翻译的简易的CMake手册。
以上文档资料仅为了解决 NDK 开发过程中编译配置问题,具体 c/c++ 的逻辑编写、jni等不在此范畴。
彩蛋
文末献上一组彩蛋,将CMake或者
NDK开发过程中遇到的坑和小技巧以 Q&A 的方式列出。持续更新
Q1:怎么指定 C++标准?
A:在build_gradle中,配置
cppFlags -std
externalNativeBuild { cmake { cppFlags "-frtti -fexceptions -std=c++14" arguments '-DANDROID_STL=c++_shared' } }
Q2:add_library 如何编译一个目录中所有源文件?
A: 使用aux_source_directory方法将路径列表全部放到一个变量中。
# 查找所有源码 并拼接到路径列表 aux_source_directory(${CMAKE_HOME_DIRECTORY}/src/api SRC_LIST) aux_source_directory(${CMAKE_HOME_DIRECTORY}/src/core CORE_SRC_LIST) list(APPEND SRC_LIST ${CORE_SRC_LIST}) add_library(native-lib SHARED ${SRC_LIST})
Q3:怎么调试 CMakeLists.txt 中的代码?
A:使用message方法
cmake_minimum_required(VERSION 3.4.1) message(STATUS "execute CMakeLists") ...然后运行后在
.externalNativeBuild/cmake/debug/{abi}/cmake_build_output.txt中查看 log。
Q4:什么时候 CMakeLists.txt 里面会执行?
A:测试了下,好像在 sync 的时候会执行。执行一次后会生成makefile的文件缓存之类的东西放在
externalNativeBuild中。所以如果
CMakeLists.txt中没有修改的话再次同步好像是不会重新执行的。(或者删除
.externalNativeBuild目录)
真正编译的时候好像只是读取
.externalNativeBuild目录中已经解析好的
makefile去编译。不会再去执行
CMakeLists.txt
Android追根究底 © 著作权归作者所有 举报文章 关注 Tsy远 写了 39126 字,被 1052 人关注,获得了 1402 个喜欢
微信公众号:Tsy远Github:https://github.com/tsy12321注意:所有文章及以后更新已经迁移至掘金,地址是 https://juejin
作者:Tsy远
链接:http://www.jianshu.com/p/6332418b12b1
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章推荐
- Android NDK开发扫盲及最新CMake的编译使用
- Android NDK开发扫盲及最新CMake的编译使用
- Android NDK开发扫盲及最新CMake的编译使用
- Android NDK开发扫盲及最新CMake的编译使用
- Android NDK开发扫盲及最新CMake的编译使用
- Android NDK开发——CMake的编译使用
- android NDK开发使用Builder自动编译生成so文件
- ANDROID NDK实践开发系列--(01) 使用ndk编译c可执行程序
- Linux下使用android ndk编译FFmpeg(最新版FFmpeg 4.4.1)
- 使用CMake来进行Android NDK开发
- 使用CMake来进行Android NDK开发
- 树莓派 安装 OpenCV 使用CMake 编译工程 最新版2015
- Android NDK开发(一) 使用CMake构建工具进行NDK开发
- Android NDK 开发:CMake 使用
- Android逆向基础笔记—Android NDK开发3之使用ndk-build工具手动编译
- Android NDK开发之旅(2):Android Studio中使用CMake进行NDK/JNI开发(初级)
- 使用QT 4.8.6 + Cmake 3.0.0 编译 最新版本OpenCv3.0.0
- 使用CMake + MinGW + Eclipse 开发OpenCV --编译,使用以及可能遇到的问题详解
- Android NDK开发之旅(2):一篇文章搞定Android Studio中使用CMake进行NDK/JNI开发
- android ndk第一步使用ndk和Cmake编译.so文件