1. 为什么需要自己动手写一个Native Service?
如果你正在捣鼓Android系统底层,或者想给设备添加一些系统级别的功能,比如控制一个特殊的硬件传感器、实现一个自定义的电源管理策略,或者创建一个只有系统应用才能调用的核心服务,那你大概率绕不开Native Service。简单来说,这就是一个用C++写的、运行在Android系统进程里的后台服务。它不像我们平常在App里写的Service,那些是跑在应用自己的进程里,权限和生命周期都有限制。Native Service是系统的一部分,权限更高,能力更强,也更“底层”。
那这些服务之间怎么“说话”呢?在Android世界里,跨进程通信(IPC)的“官方语言”就是Binder。你可以把Binder想象成一条专门的高速公路,数据包在这条路上安全、高效地穿梭于不同进程之间。但是,光有路还不行,我们还得定义路上跑的车是什么样子的,也就是通信的接口和规则。这时候,AIDL就登场了。AIDL(Android Interface Definition Language)就是一种接口定义语言,专门用来描述Binder通信的接口。你写好一个.aidl文件,编译工具就会自动帮你生成一堆C++(或Java)的“脚手架”代码,这些代码负责处理打包数据、发送请求、接收回复这些繁琐的底层细节。
所以,整个流程可以概括为:用AIDL定义接口 -> 工具生成Binder通信的桩代码 -> 我们实现具体的服务逻辑 -> 把服务注册到系统 -> 客户端通过接口调用服务。这个过程听起来有点复杂,但别担心,我当初第一次搞的时候也踩了不少坑,今天我就把这些实战经验掰开揉碎了讲给你听,保证你跟着做一遍就能上手。我们这次的目标,不是仅仅看懂代码,而是真正在AOSP环境里,从零开始构建一个能跑起来的Native Service。
2. 第一步:规划你的项目目录结构
在AOSP庞大的源码树里,把自己的代码放对地方是成功的第一步。胡乱放可能会导致编译不过,或者和系统原有代码冲突。我个人的习惯,也是社区里比较常见的做法,是在frameworks/目录下创建一个vendor/子目录,用来放我们自己的、或者设备厂商定制的代码。这样既和AOSP原生代码隔离,又便于管理。
假设你的AOSP源码根目录是~/aosp,我们打算创建一个名为SampleService的示例服务。可以按照下面这个结构来创建目录和文件:
~/aosp/frameworks/vendor/service/
├── aidl/
│ └── android/
│ └── sample/
│ └── ISample.aidl
├── include/
│ ├── aidl/
│ │ ├── BnSample.h
│ │ ├── BpSample.h
│ │ └── ISample.h
│ └── SampleService.h
├── ISample.cpp
├── SampleService.cpp
├── Android.bp
└── test/
├── SampleServer.cpp
└── SampleClient.cpp
我来解释一下每个部分的作用:
aidl/: 这里存放我们的AIDL接口定义文件。注意包路径android/sample/,这决定了生成代码的命名空间。include/: 存放所有的头文件。我们把自动生成的和自己写的头文件都放在这里,方便引用。ISample.cpp: 这是AIDL工具生成的核心通信桩代码之一(另一个是BnSample.cpp,但通常我们不需要手动修改它)。SampleService.h/.cpp: 这是我们真正要动手实现的服务逻辑所在。Android.bp: Soong构建系统的蓝图文件,相当于Makefile,告诉系统怎么编译我们的模块。test/: 存放服务端和客户端的测试程序。服务端负责注册服务,客户端负责调用服务。
你可以用mkdir -p命令一层层创建这些目录。这一步虽然简单,但好的开始是成功的一半,清晰的目录结构会让后续的开发和调试轻松很多。
3. 动手编写AIDL接口定义文件
现在,我们来定义服务的“合同”。在aidl/android/sample/ISample.aidl文件里,我们写下这个接口。为了让例子更有趣一点,我们不止定义一个简单的方法,我设计一个稍微复杂点的场景:一个数据处理服务,输入一个数字和一个字符串前缀,服务返回一个字符串列表,列表内容是重复的“前缀+序号”。
// ISample.aidl
package android.sample;
interface ISample {
/**
* 根据输入生成一组字符串。
* @param count 要生成的字符串数量。
* @param prefix 每个字符串的前缀。
* @param result 输出参数,用于返回生成的字符串列表。
*/
void generateStrings(int count, in String prefix, out List<String> result);
/**
* 一个简单的ping方法,用于测试服务是否存活。
* @return 返回一个状态码,0表示成功。
*/
int ping();
}
这里有几个关键点需要注意:
- 包名(package):
android.sample。这很重要,它必须和目录路径对应,也决定了生成C++代码的命名空间(android::sample)。 - 参数方向:
in表示数据从客户端流向服务端,out表示从服务端流回客户端,inout则表示双向。对于List<String>这样的复杂类型,如果只是读取,用in;如果需要在服务端填充后返回,必须用out。用错了方向会导致编译错误或者运行时数据无法传递。 - 支持的数据类型:AIDL支持基本类型(int, long, boolean等)、String、List、Map以及其他的AIDL接口。对于复杂对象,需要实现
Parcelable接口。
这个.aidl文件就是我们服务的蓝图。它不包含任何实现,只规定了“有什么方法”、“方法叫什么”、“输入输出是什么”。写好这个,我们就完成了最核心的设计工作。
4. 让AIDL工具为你生成通信骨架代码
有了AIDL文件,下一步就是生成处理Binder通信的“粘合”代码。这部分代码非常模板化,手动写容易出错,所以让工具来干。在AOSP环境下,主要有两种方式:
方法一:使用Soong编译系统(推荐,也是集成到AOSP的标准方式)
我们通过编写Android.bp文件,让Soong在编译时自动调用AIDL编译器。这是最“原生”、最规范的做法。我们先来配置这个Android.


43

被折叠的 条评论
为什么被折叠?



