平时工作内容基于安卓系统,确并不直接参与安卓部分开发,而只是为安卓层提供natvie方法的调用界面、实现以及再往下的BSP相关的驱动开发。但是总感觉理解安卓系统的一些重要特性也是有必要的。本文仅记录自己对安卓框架原理上的理解,无实用性。
使用java语言做终端开发的人大致分为两类:应用开发与framework开发。应用开发总的来说是使用安卓系统提供的原材料与框架编写特定需求的app,假如某些功能framework并未提供,例如你的产品需要使用一款奇葩的传感器,原生framework没有提供对应的api来操作它,那么就需要在有人在原有框架中添加通路,向上为应用开发工程师提供传感器的操作函数java或者叫虚拟机内的函数,向下打通到c实现的针对该传感器的linux驱动调用,这些人做的就是framework开发。
上图中展示了framework提供的所有功能框架,大部分是java代码实现,肯定也都是虚拟机这个容器内的框架了。例如安卓四大组件中的服务与activity都在这里实现
Framework定义了客户端组件和服务端组件功能及接口。包含3个主要部分:服务端,客户端和Linux驱动。
一:服务端
服务端主要包含两个重要类,分别是WindowManagerService(Wms)和ActivityManagerService(Ams)。Wms的作用是为所有的应用程序分配窗口,并管理这些窗口。包括分配窗口大小,调节各窗口的叠放次序,隐藏或显示窗口。Ams的作用是管理所有应用程序中的Activity。
除此之外,在服务端还包括2个消息处理类:
1)KeyQ类:
该类为Wms的内部类,继承于KeyInputQueue类, KeyQ对象一旦创建,就立即启动一个线程,该线程会不断地读取用户的UI操作消息,比如按键、触摸屏、trackball、鼠标等,并把这些消息放到一个消息队列QueueEvent类中。
2)InputDispatcherThread类:
该类的对象一旦创建,也会立即启动一个线程,该线程会不断地从QueueEvent中取出用户消息,并进行一定的过滤(用Wms中相应函数处理),过滤后,再将这些消息发给当前活动的客户端程序中。
二:客户端
客户端主要包括以下重要类:
1)ActivityThread类:
该类为应用程序的主线程类,所有的Apk程序有且只有一个ActivityThread类,程序的入口为该类中的static main()函数。
2)Activity类:
该类为APK程序的一个最小运行单元,一个APK程序中可以包含多个Activity对象,ActivityThread主类会根据用户操作选择运行哪个Activity对象。
3)PhoneWindow类:
该类继承与Window类,同时,PhoneWindow类内部包含了一个DecorView对象。简而言之,PhoneWindow是把一个FrameLayout进行了一定的包装,并提供了一组通用的窗口操作接口。
4)Window类:
该类提供了一组通用的窗口(Window)操作API, 这里的窗口仅仅是程序层面上的,Wms所管理的窗口并不是Window类,而是一个View或者ViewGroup类,一般就是指DecorView类,即一个DecorView就是Wms所有管理的一个窗口。Window是一个abstract类型。
5)DecorView类:
该类是一个FrameLayout的子类,并且是PhoneWindow中的一个内部类。Decor的英文是Decoration, 即“修饰”的意思,DecorView就是对普通的FrameLayout进行了一定的修饰,比如添加一个通用的TitleBar, 并响应特定的按键消息等。
6)ViewRoot类:
Wms管理客户端窗口时,需要通知客户端进行某种操作,这些都是通过异步消息完成的,实现方式就是使用Handler, ViewRoot就是继承于Handler,其作用主要是接收Wms的通知。
7)W类:
该类继承于Binder, 并且是ViewRoot的一个内部类。
8)WindowManager类:
客户端要申请创建一个窗口,而具体创建窗口的任务是由Wms完成的,WindowManager类就像是一个部门经理,谁有什么需求就告诉它,由它和Wms进行交互,客户端不能直接和Wms进行交互。
上一节提到java在虚拟机的包装下可以跨平台运行(即虚拟机屏蔽了软硬件的差异),但是某些驱动程序必须使用c语言进行编写,那么framework就需要提供一种方法实现这一需求。当然这并不是安卓系统中需要面对的问题,任何采用java开发应用程序都需要提供一种本地库的调用机制。
JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。具体JNI技术使用的例子可以参考:
但是要注意,JNI不是什么IPC技术,无论是JNI的Java调用者还是C++世界的实现,虽然跨越了JVM(JVM内的Java与C++的内存管理截然不同),但是这都是编译的东西,整个过程只会有一个进程,在安卓系统那就是JAVA写的activity、service所依附的进程,而C++部分只是一个so的形式,调用时是被重定位进其它进程中使用的。所以说,JNI只是一种接口规范,为JAVA语言调用c++接口提供一种跨越虚拟机的路径,当然没有framework,java可以通过JNI调用C++,如果有framework,那么java则需要先实用binder联系到framework提供的对应服务,再由服务通过JNI间接调用c++实现的native方法,这两者方法的实现步骤可总结为:
java->jni->c++
1、C/C++完成linux设备节点的打开,读写操作,并向java对应类注册实现的native方法,编译成动态库,供app加载使用
2、java编写一个类,用于在实例化时,加载已经编译好的C动态库(这里需要将so打包到apk中)
3、app UI类在放生事件后,通过上面的类,调用本地方法
java client->binder -> framework service-> JNI->c++
1、新建服务如何声明(LedService):
新建一个java class (编译aidl文件,编译生成接口类),接触该接口类,编写自己的class
2、新建服务注册:
修改已有文件SystemServer,启动时实例化新加服务类(实际为向系统注册服务)
3、编写本地方法对应的jni接口(com_android_server_LedService.cpp)
封装jni接口
4、编写本地方法实现(led_hal.c)
具体的设备节点操作
5、修改已有文件onload.cpp
添加第三步实现的jni接口中的注册函数,完成本地方法的注册
6、修改app
iLedService = ILedService.Stub.asInterface(ServiceManager.getService(“led”));
意思是通过binder到sevice_manager中查找服务接口(led为SystemServer中添加服务的名称)
android sdk (Android Software Development Kit, 即android软件开发工具包)可以说只要你使用java去开发Android这个东西就必须用到。他包含了SDK Manager 和 AVD Manage,对于android系统的一些开发版本的管理以及模拟器管理。它只能运行纯java程序,有了它模拟器才可以使用。
而ndk (Native Development Kit)跟sdk差不多的是它也是一个开发工具包。用它开发c/c++是很方便的。他有一个强大的编译集合。Java调C、C++(jni接口),是一些java如何调C的代码。它会把C代码编译成一个.SO的动态库,通过jni接口用java代码去调用它,有了它我们可以直接在android代码中去添加C代码。
很早以前android是只有sdk的。并没有ndk。这就意味着一旦android的开发者要使用c/c++的三方库或者需要用到c/c++就必须使用非官方的法子,用java的jni去调用c/c++。就像耍小聪明走后门一样。而ndk的出现就意味着jni调用的这种方法转正了变成官方了以后你不需要再走后面大路正面随你走。如果要操作底层直接操作内存,操作地址那不得不去使用c/c++因为java这块想做这些有点困难。所以ndk是必须需要出现的。对于android来说sdk和ndk是同种语言的2种不同时期的必须品。
基于NDK使用jni
安卓系统中进程间通信的最主要的方式就是IPC,每一个服务均运行在一个独立的进程中,因此是以Java实现,所以本质上来说是运行在一个独立进程的Dalvik虚拟机中。那么问题来了,开发者的App也运行在一个独立的进程空间中,如果调用到系统的服务层中的接口?答案是IPC(Inter-Process Communication),进程间通讯是和RPC(Remote Procedure Call)不一样的,实现原理也不一样。每一个系统服务在应用框架层都有一个Manager与之对应,方便开发者调用其相关功能,具体关系如下:
Android 从下而上分了内核层、硬件抽象层、系统服务层、Binder IPC 层、应用程序框架层
Android 中"应用程序框架层"以 SDK 的形式开放给开发者使用,“系统服务层” 中的核心服务随系统启动而运行,通过应用层序框架层提供的 Manager 实时为应用程序提供服务调用。系统服务层中每一个服务运行在自己独立的进程空间中,应用程序框架层中的 Manager 通过 Binder IPC 的方式调用系统服务层中的服务。
安卓的驱动核心是linux,因此Binder的实现也离不开linux驱动的协助,按照代码分层的概念来看Binder应该是这样的:
可以看到,虽然安卓系统极力在弱化进程的概念,只暴露给用户组件的使用方式,但是组件间(并不仅仅值JVM中的应用,linux下的应用程序也是如此)的通信同样属于Linux中的IPC,另外要注意到Binder属于一种偏基础的功能,后面提到的AIDL\HIDL都是基于Binder实现的RPC。仔细观察Binder的实现,如果要通过ioctl来使用Binder相关驱动,势必涉及到JVM内接口到Native的调用,那么就会用到JNI,因此实际的Binder代码结构如下(此图仅展示参与Bindr通信的一个安卓服务进程的函数调用,当然若进程装载的是一个linux应用,则没有Java与JNI部分):
Binder详解
官话:
HAL接口定义语言(简称HIDL)适用于指定HAL和其用户之间的接口的一种接口描述语言(IDL)。HIDL允许指定类型和方法调用。从更更烦的意义上来说HIDL适用于在独立编程的代码库之间通信的系统。HIDL旨在用于进程间通信(IPC)。进程之间的通信经过Binder化。对于必须与进程相关联的代码库,还可以使用直通模式。
其实HIDL目的就一个:利用binder进程通信方式,将原来通过JNI调用Vendor提供的So的方式进行解耦,任何System下的应用进程要想使用Vendor下的接口,必须通过IPC打通。这一做法的好处就是安卓Framework下的进程与Vendor提供的服务区分为完全隔离的两个进程。
早在Android提出HIDL之前,某电视厂商在Vendor的Hal与Framework之间就有添加一层中间件代码,此时的Hal调用并非简单的JNI,具体过程如下:
安卓引入Treble后,无论你是中间件代码还是芯片厂家代码,都被分配到了Vendor分区,System分区的进程无法直接调用Hal接口,虽然某电视厂商的设计构架中是通过原生Binder实现的,但是就连Binder的调用也被禁止了,所以只能适配最新的HIDL,将原生的Binder直接调整为HIDL封装过的Binder,各进程关系保持不变。具体HIDL的使用方法可以参考:
Binder死磕到底(四):Treble化架构
类似于HIDL,AIDL也是一种接口规范,主要用于JAVA世界内进程的通信,在Treble下则是system分区内进程(进程代码位于System分区)的通信规则。实际上AIDL仍然是Binder机制的包装,安卓只是提供了一套开发环境与规则,方便JAVA程序员更加快捷的基于Binder机制完成进程间的通信。至此,AIDL别无他妙,附注一个实现例子Android AIDL使用详解
Android不仅在业务上为应用提供众多实用的组件与服务,在编程技术上也是想破脑袋。对于C++程序员来说,内存管理从来都是既方便又麻烦的事情。而在Android 中可以广泛看到的template class Sp 句柄类实际上是android 为实现垃圾回收机制的智能指针。智能指针是c++ 中的一个概念,因为c++ 本身不具备垃圾回收机制,而且指针也不具备构造函数和析构函数,所以为了实现内存( 动态存储区) 的安全回收,必须对指针进行一层封装,而这个封装就是智能指针,其实说白了,智能指针就是具备指针功能同时提供安全内存回收的一个类。
sp: 实现自己的类MyClass继承RefBase,在堆上分配一个该类,用指针p指向这块地址,再用p实例化一个sp,那么sp就会动态修改
MyClass::RefBase中的引用计数,其他地方在实例化另外一个sp指向相同内存地址,也会维护引用计数,当有sp析构时则会对引用计数减1
,继而在RefBase的析构中判断引用计数是否为0。若为0,则delete指向的堆内存。这样可利用析构函数自动判断是否该delete内存
wp: wp类似sp,但是维护的引用计数成为弱引用,默认配置下,弱引用计数不会控制实际对象的delete,只会delete影子对象。
因此,wp指向的对象,仅仅用作记录,真正要访问该对象内存时,需要先promote为sp,如果实际对象已经delete,那么promote会
失败,这样可确保防止错误的内存访问。
广播介绍
由于应用程序之间不能共享内存。在不同应用程序之间交互数据(跨进程通讯),在Android SDK中提供了4种用于跨进程通讯的方式。这4种方式正好对应于android系统中4种应用程序组件:Activity、Content Provider、Broadcast和Service。其中Activity可以跨进程调用其他应用程序的Activity;Content Provider可以跨进程访问其他应用程序中的数据(以Cursor对象形式返回),当然,也可以对其他应用程序的数据进行增、删、改操 作;Broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播;Service和Content Provider类似,也可以访问其他应用程序中的数据,但不同的是,Content Provider返回的是Cursor对象,而Service返回的是Java对象,这种可以跨进程通讯的服务叫AIDL服务。
Activity的跨进程访问与进程内访问略有不同。虽然它们都需要Intent对象,但跨进程访问并不需要指定Context对象和Activity的 Class对象,而需要指定的是要访问的Activity所对应的Action(一个字符串)。有些Activity还需要指定一个Uri(通过 Intent构造方法的第2个参数指定)。
在android系统中有很多应用程序提供了可以跨进程访问的Activity,例如,下面的代码可以直接调用拨打电话的Activity。
Intent callIntent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:12345678" );
startActivity(callIntent);
Android应用程序可以使用文件或SqlLite数据库来存储数据。Content Provider提供了一种在多个应用程序之间数据共享的方式(跨进程共享数据)。应用程序可以利用Content Provider完成下面的工作
查询数据
修改数据
添加数据
删除数据
虽然Content Provider也可以在同一个应用程序中被访问,但这么做并没有什么意义。Content Provider存在的目的向其他应用程序共享数据和允许其他应用程序对数据进行增、删、改操作。
Android系统本身提供了很多Content Provider,例如,音频、视频、联系人信息等等。我们可以通过这些Content Provider获得相关信息的列表。这些列表数据将以Cursor对象返回。因此,从Content Provider返回的数据是二维表的形式。
广播是一种被动跨进程通讯的方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。这就象电台进行广播一样,听众只能被动地收听,而不能主动与电台进行沟通。
在应用程序中发送广播比较简单。只需要调用sendBroadcast方法即可。该方法需要一个Intent对象。通过Intent对象可以发送需要广播的数据。
1.利用AIDL Service实现跨进程通信,这是我个人比较推崇的方式,因为它相比Broadcast而言,虽然实现上稍微麻烦了一点,但是它的优势就是不会像广播那样在手机中的广播较多时会有明显的时延,甚至有广播发送不成功的情况出现。
注意普通的Service并不能实现跨进程操作,实际上普通的Service和它所在的应用处于同一个进程中,而且它也不会专门开一条新的线程,因此如果在普通的Service中实现在耗时的任务,需要新开线程。
要实现跨进程通信,需要借助AIDL(Android Interface Definition Language)。Android中的跨进程服务其实是采用C/S的架构,因而AIDL的目的就是实现通信接口。
对于耗时处理,不能放在Activity的主线程中做,所以开辟子线程是最容易想到的事情。
Android系统存在多个层面的线程接口:
从分层来看,Android的framework或者使用Java写的APk,都运行于Linux之上,自然上层的各种形式的线程实现全部由Linux提供,所以它们都是基于C库的pthread来作为底层实现的(早期Java有所谓的green thread,它由运行环境或虚拟机(VM)调度,而不是由本地底层操作系统调度的线程。不过目前已弃用)。
所以Andorid下的多线程开发时,看你是在虚拟机还是native层开发,当然不管那一层其本质都是pthread提供实现,再往底层说的话,都是linux驱动来实现,只不过越接近上层封装的越便捷方便,同时也更加的不利于细节的展示。
https://www.jianshu.com/p/9f978d57c683
1. init
首先linux的uboot引导程序拉起整个Linux os的kernel层,kernel的start_kerenl()函数会完成一系列Linux系统级的初始化工作,包括 :
2. zygote
init进程仍属于linux系统,当它通过init.rc去fork安卓的始祖(既是盘古也是女娲,因为后面zygote会产生其它进程还会响应其它进程的召唤)进程时,才会逐渐拉起安卓的运行环境,例如system_server进程、虚拟机等。拉起zygote的init入口函数就是service_start()。整个zygote初始化的过程如下图(一半native 一半java),关键工作包括:
3. system_server
system_server主体已经完全运行于java世界,他在运行伊始需要完成如下几步工作:
前言
最近自己一直在做有关 Android 系统源码底层的开发,就经常接触到Android NDK和AOSP(Android Open Source Project) Build System这两个东西,但是由于他们两者都可以将C/C++代码编译成可执行文件或者动态链接库,导致我经常将这两者弄混淆了。所以,痛定思痛,不想再被这种似四而非的感觉折磨了,今天就抽空写下这篇文章来捋清楚两者之间关系。
Android NDK 是什么?
Android NDK 本质上是一套交叉编译工具集,它可以将 C/C++ 源码编译成适用于不同硬件平台的库文件和可执行文件,而这些库文件和可执行文件可以被上层的基于 Java 语言编写的 APP 加载调用,从而实现了 C/C++ 源码在 APP 中的复用。
例如,在图像处理中我们常用的OpenCV库就是使用 C++ 编写的,如果我们想在我们使用 Java 开发的 Android APP 中使用 OpenCV 库中的一些处理函数,那么该怎么办呢?
当然,你可以直接去找基于 Java 实现的 OpenCV 的 jar 包,然后去调用对应的函数,但是这种Java 实现版本的 OpenCV 在处理的效率上肯定不及 C++ 实现版本的 OpenCV(尤其是在做图形处理方面)。
所以,另外一种方法就是通过 Android NDK 工具将 OpenCV 的代码编译成指定硬件平台的库文件,然后在 Android APP 进程中通过JNI的方式来使用 OpenCV 中提供的处理函数,实现自己想要的某种功能。
Android NDK 编译系统其实本质上就是一系列的交叉编译工具链,而 NDK 中所使用的编译脚本ndk-build就是根据编译配置文件Android.mk和Application.mk来调用这些交叉编译工具链中的工具编译生成指定 ABI 平台下目标链接库文件或者可执行文件。
这里我觉得还是有必要多费点文字对 Android NDK 包中的文件及目录的内容进行一个说明,以便大家对 NDK 有更加深一步的理解。NDK 包中的文件及目录结构如下所示:
a@a:~/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
这里就不对Android.mk编译配置文件的编写方法展开说明了,有关内容可以参见下面这篇文章:Mastering Android NDK Build System - Part 1: Techniques with ndk-build和Android.mk
有时我们的自己编写的源码中除了实现某种特定的功能之外,可能还会在C/C++代码中使用到日志打印输出函数,而这个日志打印函数就位于 Android NDK 中 Android 系统提供的liblog.so库中。除了 liblog.so 库之外,NDK 还提供了下面这些系统共享链接库供我们自己的源码进行加载调用:
a@a:~/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文件中。
AOSP是Android Open Source Project的简称,接下来用我就用它等价地表示 Android 系统源码。
AOSP Build System是用来编译 Android 系统,Android SDK 以及相关文档的一套框架。该编译系统主要由 Make 文件(注意:这里的 Make 文件不是 Makefile 文件,而是 Android 编译系统自己构架的一套编译配置文件,通常以*.mk 为文件后缀),Shell 脚本以及 Python 脚本组成,其中最主要的是 Make 文件。
在 Android Build System 中编译所使用到的Make文件主要分为三类:
既然 Android 系统源码中包含了 3 种编程语言,那么在AOSP Build System中肯定也使用了许多编译工具来进行编译(Android 官方推荐使用 Ubuntu 14.04 来对 Android 源码进行编译,所以这里就以 Ubuntu 系统中所使用的编译工具为例):
例如,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做了比较详细的说明,所以在这一小节中就是对两者从下面几个方面进行一个差异对比:
两种编译系统面向的开发人员群体
生成的链接库或可执行文件的目的
系统共享链接库的支持
Native开发开发,基本是躺平状态,应用提的需求照单全收。但是,躺的姿势也是有区别的,不同的姿势可以让上面的人多出点力或者多享受一些。无论是AOSP或者NDK编译方式都是有两种可选的Native开放套路:
Android的NDK开发相信各位已经精通各种姿势了。不过基本上都是那种native代码和java代码都在同一个工程中,因为应用从头到脚都是我们自己的,也不需要分离。但有时候可能需要我们自己把某些库打包起来供别人使用,或者使用别人提供给我们的库。本篇文章及下篇文章就讲讲so库如何打包。
参考
打包so库及jar包的博客我讲了如何将自己的代码打包成so库,并且配合jar包供他人调用。但那种方式仅适合对方从java层调用,如果算法是比较核心的,而又为了效率必须从native来调用,那种方式就不合适了。本篇讲如何打包我们自己的核心代码供他人在native调用,如果对方愿意,也可以自己封装然后从java来调用,灵活性更高。并且在调试的时候更加方便。这种方式是更接近纯C/C++工程的集成方式。
参考