Android NDK vs AOSP Build System
by FlyFlyPeng
前言
最近自己一直在做有关 Android 系统源码底层的开发,就经常接触到 Android NDK
和 AOSP(Android Open Source Project) Build System
这两个东西,但是由于他们两者都可以将 C/C++
代码编译成可执行文件或者动态链接库,导致我经常将这两者弄混淆了。所以,痛定思痛,不想再被这种似四而非的感觉折磨了,今天就抽空写下这篇文章来捋清楚两者之间关系。
Android NDK
Android NDK 是什么?
先引用一段来自 Android NDK 官网上的非常简洁的介绍吧:
The Android NDK is a toolset that lets you implement parts of your app using native-code languages such as C and C++. For certain types of apps, this can help you reuse code libraries written in those languages.
上面的介绍我觉得已经解释地非常清楚了,我再扩展地补充一下:Android NDK 本质上是一套交叉编译工具集
,它可以将 C/C++ 源码编译成适用于不同硬件平台的库文件
和可执行文件
,而这些库文件和可执行文件可以被上层的基于 Java 语言编写的 APP 加载调用,从而实现了 C/C++ 源码在 APP 中的复用。
下面这幅图就非常简洁地体现了 NDK 的用途:
Android NDK 应用实例
例如,在图像处理中我们常用的 OpenCV
库就是使用 C++ 编写的,如果我们想在我们使用 Java 开发的 Android APP 中使用 OpenCV 库中的一些处理函数,那么该怎么办呢?
当然,你可以直接去找基于 Java 实现的 OpenCV 的 jar 包,然后去调用对应的函数,但是这种Java 实现版本的 OpenCV 在处理的效率上肯定不及 C++ 实现版本的 OpenCV(尤其是在做图形处理方面)。
所以,另外一种方法就是通过 Android NDK 工具将 OpenCV 的代码编译成指定硬件平台的库文件,然后在 Android APP 进程中通过 JNI
的方式来使用 OpenCV 中提供的处理函数,实现自己想要的某种功能。
Android NDK 编译系统
Android NDK 编译系统其实本质上就是一系列的交叉编译工具链
,而 NDK 中所使用的编译脚本 ndk-build
就是根据编译配置文件 Android.mk
和 Application.mk
来调用这些交叉编译工具链中的工具编译生成指定 ABI 平台下目标链接库文件或者可执行文件。
这里我觉得还是有必要多费点文字对 Android NDK 包中的文件及目录的内容进行一个说明,以便大家对 NDK 有更加深一步的理解。NDK 包中的文件及目录结构如下所示:
woshijpf@woshijpf-OptiPlex-9020:~/Android/NDK/android-ndk-r12b$ tree -L 1
.
|-- build
|-- CHANGELOG.md
|-- ndk-build
|-- ndk-depends
|-- ndk-gdb
|-- ndk-stack
|-- ndk-which
|-- platforms
|-- prebuilt
|-- python-packages
|-- shader-tools
|-- source.properties
|-- sources
`-- toolchains
7 directories, 7 files
- build:该目录下包含了 ndk-build 编译脚本中所使用到的各种小的脚本文件,例如我们在 Android.mk 中常见的将源码编译成共享链接库文件的语句:
include $(BUILD_SHARED_LIBRARY)
中的BUILD_SHARED_LIBRARY
对应的就是一个编译脚本文件:<ndk-home>/build/core/build-shared-library.mk
。 - ndk-build:NDK 的核心,NDK 编译所使用的最主要的编译脚本。
- ndk-gdb:调试 NDK 编译出来的库的工具。
- ndk-stack:对 Android 系统运行时共享链接库崩溃时产生的
tombstone
文件进行解析的工具。它在调试异常崩溃 Bug 时定为到具体出错的源码位置非常有帮助,具体使用方法详见我的这篇博客:Android NDK Tombstone/Crash 分析 - platforms:这个文件夹里保存的是各个 Android 系统版本下不同硬件平台对应的 NDK 提供的系统共享链接库文件,例如:
liblog.so, libdl.so, libc.so等等
。 - source:这个目录下主要包含的就是 NDK 所中的
C++ STL
库的源码,有gnu_stl
的实现,也有llvm-stl
的实现。 - toolchains:这个目录也是非常关键的一个目录,它里面包含了不同目标硬件平台下面的交叉编译工具,可以将 C/C++ 库编译成可运行在
arm,x86,x86-64,mips
等不同硬件平台上面。
这里就不对 Android.mk
编译配置文件的编写方法展开说明了,有关内容可以参见下面这篇文章:Mastering Android NDK Build System - Part 1: Techniques with ndk-build和Android.mk
Android NDK 中系统提供的共享链接库
有时我们的自己编写的源码中除了实现某种特定的功能之外,可能还会在C/C++代码中使用到日志打印输出函数,而这个日志打印函数就位于 Android NDK 中 Android 系统提供的 liblog.so
库中。除了 liblog.so 库之外,NDK 还提供了下面这些系统共享链接库供我们自己的源码进行加载调用:
woshijpf@woshijpf-OptiPlex-9020:~/Android/NDK/android-ndk-r12b/platforms/android-22/arch-x86/usr/lib$ ls -al
total 10996
drwxr-xr-x 2 woshijpf woshijpf 4096 Jun 15 2016 .
drwxr-xr-x 4 woshijpf woshijpf 4096 Jun 15 2016 ..
-rw-r--r-- 1 woshijpf woshijpf 2204 Jun 15 2016 crtbegin_dynamic.o
-rw-r--r-- 1 woshijpf woshijpf 1992 Jun 15 2016 crtbegin_so.o
-rw-r--r-- 1 woshijpf woshijpf 2204 Jun 15 2016 crtbegin_static.o
-rw-r--r-- 1 woshijpf woshijpf 704 Jun 15 2016 crtend_android.o
-rw-r--r-- 1 woshijpf woshijpf 648 Jun 15 2016 crtend_so.o
-rwxr-xr-x 1 woshijpf woshijpf 10772 Jun 15 2016 libEGL.so
-rwxr-xr-x 1 woshijpf woshijpf 34640 Jun 15 2016 libGLESv1_CM.so
-rwxr-xr-x 1 woshijpf woshijpf 28428 Jun 15 2016 libGLESv2.so
-rwxr-xr-x 1 woshijpf woshijpf 46592 Jun 15 2016 libGLESv3.so
-rwxr-xr-x 1 woshijpf woshijpf 6752 Jun 15 2016 libOpenMAXAL.so
-rwxr-xr-x 1 woshijpf woshijpf 7036 Jun 15 2016 libOpenSLES.so
-rwxr-xr-x 1 woshijpf woshijpf 28876 Jun 15 2016 libandroid.so
-rw-r--r-- 1 woshijpf woshijpf 8814206 Jun 15 2016 libc.a
-rwxr-xr-x 1 woshijpf woshijpf 125464 Jun 15 2016 libc.so
-rwxr-xr-x 1 woshijpf woshijpf 5400 Jun 15 2016 libdl.so
-rwxr-xr-x 1 woshijpf woshijpf 5212 Jun 15 2016 libjnigraphics.so
-rwxr-xr-x 1 woshijpf woshijpf 5436 Jun 15 2016 liblog.so
-rw-r--r-- 1 woshijpf woshijpf 1333352 Jun 15 2016 libm.a
-rwxr-xr-x 1 woshijpf woshijpf 26708 Jun 15 2016 libm.so
-rwxr-xr-x 1 woshijpf woshijpf 18184 Jun 15 2016 libmediandk.so
-rw-r--r-- 1 woshijpf woshijpf 105024 Jun 15 2016 libstdc++.a
-rwxr-xr-x 1 woshijpf woshijpf 5536 Jun 15 2016 libstdc++.so
-rw-r--r-- 1 woshijpf woshijpf 575800 Jun 15 2016 libz.a
-rwxr-xr-x 1 woshijpf woshijpf 11768 Jun 15 2016 libz.so
那么这些系统提供的共享链接库是怎么被加载使用的呢?
例如,我现在写了一个 C++ 源文件 hello.cpp
,并且在这个源文件中调用了 __android_log_print()
函数打印日志,那么我就需要在对应的 Android.mk
文件中加入下面这条语句来显示地链接 /system/lib/liblog.so
库:
LOCAL_LDLIBS := -llog
那么这些系统共享链接库是如何编译出来的呢?
因为安全性和兼容性问题以及上层应用程序的需求,NDK 提供的只是 Android 系统中一小部分系统共享链接库,并且这些系统共享链接库都是在 Android 源码
编译时生成的,例如,liblog.so
库就是由 Android 源码中 /system/core/liblog/ 目录下的源码编译而来的。而在 $NDK/platforms/android-22/arch-arm/usr/include/android/log.h 头文件中声明的日志打印函数 __android_log_write()
的实际代码实现就在 Android 源码的 /system/core/liblog/logd_write.c
文件中。
相关参考文章
Android NDK Native APIs C++ Library Support
AOSP Build System
AOSP
是 Android Open Source Project
的简称,接下来用我就用它等价地表示 Android 系统源码。
AOSP Build System 是什么?
AOSP Build System
是用来编译 Android 系统,Android SDK 以及相关文档的一套框架。该编译系统主要由 Make 文件(注意:这里的 Make 文件不是 Makefile 文件,而是 Android 编译系统自己构架的一套编译配置文件,通常以*.mk 为文件后缀),Shell 脚本以及 Python 脚本组成,其中最主要的是 Make 文件。
在 Android Build System 中编译所使用到的 Make
文件主要分为三类:
- 第一类是 Build 系统核心文件,此类文件定义了整个 Build 系统的框架,而其他所有 Make 文件都是在这个框架的基础上编写出来的。
- 第二类是针对某个产品(一个产品可能是某个型号的手机或者平板电脑)的 Make 文件,这些文件通常位于 device 目录下,该目录下又以公司名以及产品名分为两级目录。
- 第三类是针对某个模块的 Make 文件。整个 Android 系统中,包含了大量的模块,每个模块都有一个专门的 Make 文件,这类文件的名称统一为
Android.mk
,该文件中定义了如何编译当前模块。Build 系统会在整个源码树中扫描名称为“Android.mk”的文件并根据其中的内容执行模块的编译。
AOSP Build System 所依赖的编译工具
Android 系统从下到上主要分为下面5层,而每一层所使用的编程语言如下:
- Kernel:Android 定制化过的
Linux Kernel
,使用的当然是C
语言了。 - HAL:这一层是用户态驱动层,它主要功能是和下层 Kernel 中的硬件驱动程序进行交互,这一层主要使用的语言是
C/C++
。 - Frameworks native:这一层是 Android 系统核心组件的实现位置,在这一层中主要通过
C++
语言来实现。 - Frameworks java:这一层其实是对 Frameworks native 层套上一层 Java 的外壳,封装成 Android SDK 提供给上层的 APP 开发者进行调用,这一层主要使用了
Java
语言进行实现。 - APP:这一层主要面向的是 Android APP 开发人员,并且 Android 提供的 SDK 是基于 Java 语言的,所以 APP 的代码实现也是
Java
语言。
既然 Android 系统源码中包含了 3 种编程语言,那么在 AOSP Build System
中肯定也使用了许多编译工具来进行编译(Android 官方推荐使用 Ubuntu 14.04 来对 Android 源码进行编译,所以这里就以 Ubuntu 系统中所使用的编译工具为例):
- 编译 C/C++ 代码:使用 Ubuntu 14.04 中自带的
gcc
编译器即可。 - 编译 Java 代码:在 Ubuntu 14.04 中推荐使用
OpenJDK-1.7
来进行编译。 - make 工具:前面提到 AOSP Build System 是基于 make 工具,所以这里也使用 Ubuntu 14.04 中自带的
make
工具即可。
AOSP Build System 编译系统共享链接库的方法
在 AOSP Build System 是什么?
小节中,我提到了 Android 系统源码中也使用了 Android.mk
文件来将某个模块编译成库文件或者可执行文件。
例如,Android 系统源码中的 AudioFlinger
服务对应使用的是系统中的 libaudioflinge.so
共享链接库文件 ,该共享链接库的源码实现位于 frameworks/av/services/audioflinger
,在同一目录下面的 Android.mk
编译配置文件如下所示:
LOCAL_SRC_FILES:= \ # 编译该模块所需要使用到的源文件
AudioFlinger.cpp \
Threads.cpp \
Tracks.cpp \
Effects.cpp \
AudioMixer.cpp.arm \
PatchPanel.cpp
LOCAL_SRC_FILES += StateQueue.cpp
LOCAL_C_INCLUDES := \
$(TOPDIR)frameworks/av/services/audiopolicy \
$(call include-path-for, audio-effects) \
$(call include-path-for, audio-utils)
LOCAL_SHARED_LIBRARIES := \ # 链接该模块所依赖的共享链接库文件
libaudioresampler \
libaudioutils \
libcommon_time_client \
libcutils \
libutils \
liblog \
libbinder \
libmedia \
libnbaio \
libhardware \
libhardware_legacy \
libeffects \
libpowermanager \
libserviceutility
LOCAL_STATIC_LIBRARIES := \ # 链接该模块所依赖的静态链接库文件
libscheduling_policy \
libcpustats \
libmedia_helper
LOCAL_MODULE:= libaudioflinger
LOCAL_CFLAGS += -fvisibility=hidden #隐藏共享链接库中的符号,使之不被其他共享库所访问
include $(BUILD_SHARED_LIBRARY) # 编译成 libaudioflinger.so 库文件
在 Android 源码树的根目录下运行下面的命令来配置好 Android 源码编译的环境:
$ source build/envsetup.sh
$lunch # 选择自己需要编译的 Android 系统版本
有了 Android 编译环境之后,只需要在将当前的工作目录切换到 frameworks/av/services/audioflinger 目录下来编译 AudioFlinger
模块
# 由于 libaudioflinger.so 所需依赖其他的系统共享链接库文件,所以需要先把整个 Android 源码生成这些共享链接库文件
$ mm # 读取当前工作目录下的 Android.mk 文件,编译 libaudioflinger.so 共享链接库文件
注意:虽然咋一看上去 Android 源码中某个模块的编译配置文件 Android.mk
和 NDK 中所用的编译配置文件 Android.mk
没有什么不同,但是其实还是有一些细微的区别的,尤其是在使用共享链接库方面。
例如,上面 Android 系统中源码编译出来的 libaudioflinger.so
库文件中链接 liblog.so
库文件使用的是 LOCAL_SHARED_LIBRARIES
编译变量:
LOCAL_SHARED_LIBRARIES := \ # 链接该模块所依赖的共享链接库文件
...
liblog \
...
而在 NDK 编译自己使用 C/C++ 编写的模块时,如果要链接 liblog.so
库文件,Android.mk
文件中的写法则是:
LOCAL_LDLIBS := -llog
所以,我们可以看出来在 AOSP
中所有编译出来的系统链接库文件(不管是静态库文件还是共享链接库文件)对 AOSP
中各个模块都是可见和可以被链接使用的,而对于 NDK 来说它只能通过 LOCAL_LDLIBS
的变量来链接使用 Android 系统中提供的一小部分系统链接库文件。
相关参考文章
有关 AOSP Build System 更加详细的介绍,可以参考下面的的文章: 理解 Android Build 系统 《Embedded Android》 Chapter 4 – The Build System Android Build System Ultimate Guide Establishing a Build Environment
Android NDK 和 AOSP Build System 的差异
前面我们对 Android NDK
和 AOSP Build System
做了比较详细的说明,所以在这一小节中就是对两者从下面几个方面进行一个差异对比:
- 两种编译系统面向的开发人员群体
- 生成的链接库或可执行文件的目的
- 系统共享链接库的支持
两种编译系统面向的开发人员群体
Android NDK
它所面对的开发人员群体是APP
开发人员,他们想使用C/C++
代码来实现某种功能,然后在上层 APP 的Java
代码中来通过 JNI 的方式来调用这些函数。例如,一些手机游戏 APP 的开发人员,为了使得游戏运行时画面更加流畅,他们就常常会把这些图像渲染这块耗时和性能要求较高的模块通过C/C++
代码调用OpenGLES API
函数来实现,然后通过ndk-build
编译成共享库文件,然后被上层的 APP 中的 Java 代码加载调用。AOSP Build System
面向的开发人员群体则是一些底层操作系统的开发人员,他们需要根据自己的需求和硬件平台的特性对Android
源码进行一个定制修改,然后通过AOSP Build System
重新编译得到自己想要的 Android 系统的镜像和库文件。
生成的链接库或可执行文件的目的
Android NDK
生成链接库或可执行文件的目的是为了执行实现上层 APP 层中的需要通过C/C++
才能实现的某种功能,还是上面举过的例子,手机游戏 APP 开发人员需要通过C/C++
才能实现一些性能要求很高的图像渲染操作。AOSP Build System
中生成的链接库文件或可执行文件都是 Android 系统运行起来必须依赖
的库文件,非常重要!!!例如,我们前面一直举的liblog.so
就是由AOSP Build System
编译出来的一个系统共享链接库文件,如果没有这个文件,那么 Android 系统的日志系统就挂了。
系统共享链接库的支持
Android NDK
中支持用户自己编写的 C/C++ 代码链接调用一部分系统共享链接库文件,例如liblog.so, libOpenSLES.so, libGLESv2.so
等库文件,有关 Andorid NDK 支持那些系统库文件,详见 NDK 的官方文档:Android NDK Native APIsAOSP Build System
就好比是系统共享链接库的“妈”,既然这些库文件都由它编译出来的,那么AOSP
中任意一个模块肯定都可以使用全部的系统共享链接库文件。
Subscribe via RSS