Android NDK开发扫盲及最新CMake的编译使用

今日科技快讯

8月1日消息,针对“华为手机被安装监听软件收集隐私”的传闻,华为终端官方回应称这是恶意诽谤的谣言。另外,华为还声称已经成立相关小组,配合警方对嫌疑目标公司和相关个人调查。值得一提的是,华为在声明的最后还呼吁全行业正当竞争,把精力更多放在如何为消费者创造价值上,共同创造一个健康的市场环境。

作者简介

本篇来自 Tsy远 的投稿,主要介绍了Android NDK开发注意事项以及CMake的编译使用的一些方法,希望大家会喜欢。

Tsy远 的博客地址:

http://www.jianshu.com/u/21716b19302d

概述

本篇文章旨在简介 Android 中 NDK 是什么以及重点讲解最新 Android Studio 编译工具 CMake 的使用。

NDK 简介

在介绍 NDK 之前还是首推 Android 官方 NDK 文档。传送门:

https://developer.android.com/ndk/guides/index.html

官方文档分别从以下几个方面介绍了 NDK。

  1. NDK 的基础概念

  2. 如何编译 NDK 项目

  3. ABI 是什么以及不同 CPU 指令集支持哪些 ABI

  4. 如何使用您自己及其他预建的库

本节将会对文档进行总结和补充。所以建议先浏览一遍文档,或者看完本篇文章再回头看一遍文档。

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 介绍: http://blog.csdn.net/singwhatiwanna/article/details/9061545

  • 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 现在主推的)

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;

具体的兼容问题可以参见这篇文章。

http://blog.coderclock.com/2017/05/07/android/Android-so-files-compatibility-and-adaptation/

当我们开发 Android 应用的时候,由于 Java 代码运行在虚拟机上,所以我们从来没有关心过这方面的问题。但是当我们开发或者使用原生代码时就需要了解不同 ABI 以及为自己的程序选择接入不同 ABI 的库。(库越多,包越大,所以要有选择)

下面我们来看下一共有哪些 ABI 以及对应的指令集

ABI

CMake 的使用

这一节将重点介绍 CMake 的规则和使用,以及如何使用 CMake 编译自己及其他预建的库。

Hello World

我们通过一个Hello World项目来理解 CMake

首先创建一个新的包含原生代码的项目。在 New Project 时,勾选 Include C++ support

New Project

项目创建好以后我们可以看到和普通 Android 项目有以下4个不同。

  1. main 下面增加了 cpp 目录,即放置 c/c++ 代码的地方

  2. module-level 的 build.gradle 有修改

  3. 增加了 CMakeLists.txt 文件

  4. 多了一个 .externalNativeBuild 目录

Difference

  • build.gradle

由于 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文档:

https://developer.android.com/ndk/guides/cmake.html

  • CMakeLists.txt

CMakeLists.txt 中主要定义了哪些文件需要编译,以及和其他库的关系等。

看下新项目中的 CMakeLists.txt

这其实是一个最基本的 CMakeLists.txt ,其实 CMakeLists.txt 里面可以非常强大,比如自定义命令、查找文件、头文件包含、设置变量等等。建议结合 CMake 的官方文档使用。

https://cmake.org/documentation/

同时在这推荐一个中文翻译的简易的 CMake手册

https://www.zybuluo.com/khan-lau/note/254724

CMake 使用自己及其他预建的库

当你需要引入已有的静态库/动态库(FFMpeg)或者自己编译核心部分并提供出去时就需要考虑如何在 CMake 中使用自己及其他预建的库。

Android NDK 官网的使用现有库的文档中还是使用 ndk-build + Android.mk + Application.mk 组合的说明文档。(其实官方文档中大部分都是的,并没有使用 CMake)

https://developer.android.com/ndk/guides/libs.html

幸运的是, Github 上的官方示例里面有个项目 hello-libs 实现了如何创建出静态库/动态库,并引用它。现在我们把代码拉下来看下具体是如何实现的。

https://github.com/googlesamples/android-ndk/tree/master/hello-libs

hello-libs

我们先看下 Github 上的 README 介绍:

  1. app - 从 $project/distribution/ 中使用一个静态库和一个动态库

  2. gen-libs - 生成一个动态库和一个静态库并复制到 $project/distribution/ 目录,你不需要再编译这个库,二进制文件已经保存在了项目中。当然,如果有需要你也可以编译自己的源码,只需要去掉 setting.gradle 和 app/build.gradle 中的注释,然后执行一次,接着注释回去,防止在 build 的过程中不受影响。

我们采用自底向上的方式分析模块,先看下 gen-libs 模块

  • gen-libs/build.gradle

查询文档可以知道 arguments 中 -DANDROID_PLATFORM 代表编译的 android 平台,文档建议直接设置 minSdkVersion 就行了,所以这个参数可忽略。另一个参数 -DANDROID_TOOLCHAIN=clang,CMake 一共有2种编译工具链 - clang 和 gcc,gcc 已经废弃,clang 是默认的。文档地址:

https://developer.android.com/ndk/guides/cmake.html

targets 'gmath', 'gperf' 代表编译哪些项目。(不填就是都编译)

  • cpp/CMakeLists.txt

外层的 CMakeLists 里面核心就是 add_subdirectory,查询CMake 官方文档可以知道这条命令的作用是为构建添加一个子路径。子路径中的 CMakeLists.txt 也会被执行。即会去分别执行 gmath 和 gperf 中的 CMakeLists.txt,CMake 官方文档:

https://cmake.org/documentation/

  • cpp/gmath/CMakeLists.txt

这个是其中一个静态库的 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点

  1. 编译静态库/动态库

  2. 修改输出路径

  3. 复制暴露的头文件

接着,我们看下 app 模块是如何使用预建好的静态库/动态库的。

  • app/src/main/cpp/CMakeLists.txt

我将解释放在了注释中。可以看下基本上分成了4个步骤引入:

  1. 分别创建静态库/动态库,直接引用已经有的 .a 文件 或者 .so 文件

  2. 创建自己应用的库 hello-libs

  3. 加入之前暴露头文件

  4. 链接上静态库/动态库

还是很好理解的。编辑好并 Sync 后,你就可以发现 hello-libs 中的c/c++代码可以引用暴露的头文件调用内部方法了

资料文献

首推 Android NDK 官方文档,虽然很多都不完整,但是绝对是必须看一遍的东西。

https://developer.android.com/ndk/guides/index.html

当初次接触 NDK 开发又觉得新建的 Hello World 项目过于简单时。建议把 googlesamples - android-ndk 项目拉下来。

https://github.com/googlesamples/android-ndk

里面有多个实例参考,比官方文档完整很多。

Google Samples

当你发现示例里的一些NDK配置满足不了你的需求后,你就需要到 CMake 官方文档 去查询完整的支持的函数,同时这里也提供一个中文翻译的简易的 CMake手册

https://www.zybuluo.com/khan-lau/note/254724

以上文档资料仅为了解决 NDK 开发过程中编译配置问题,具体 c/c++ 的逻辑编写、jni等不在此范畴。

彩蛋

文末献上一组彩蛋,将 CMake 或者 NDK 开发过程中遇到的坑和小技巧以 Q&A 的方式列出。持续更新

Q1:怎么指定 C++标准?

A:在 build_gradle 中,配置 cppFlags -std

Q2:add_library 如何编译一个目录中所有源文件?

A: 使用 aux_source_directory 方法将路径列表全部放到一个变量中。

Q3:怎么调试 CMakeLists.txt 中的代码?

A: 使用 message 方法

然后运行后在 .externalNativeBuild/cmake/debug/{abi}/cmake_build_output.txt 中查看 log。

Q4:什么时候 CMakeLists.txt 里面会执行?

A:测试了下,好像在 sync 的时候会执行。执行一次后会生成 makefile 的文件缓存之类的东西放在 externalNativeBuild 中。所以如果 CMakeLists.txt 中没有修改的话再次同步好像是不会重新执行的。(或者删除 .externalNativeBuild 目录)

真正编译的时候好像只是读取.externalNativeBuild 目录中已经解析好的 makefile 去编译。不会再去执行 CMakeLists.txt

更多

每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都有好心情。

如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。

欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值