Native层添加按需调用的服务以供框架层和应用层调用
在Android系统开发(AOSP)中,为了优化整机功耗和内存,我们经常需要实现服务的懒加载(Lazy Loading)。即:服务默认不运行,当上层应用有请求时才拉起;当请求处理完毕且长时间无人使用时,服务自动退出释放资源。
最近我在AOSP中完成了一个需求:在Native层添加一个属性为 oneshot、disabled 的懒加载服务,并在Framework层封装Manager类,最终打包成Jar供上层应用调用。
本文将以此为例,展示如何避免“单例持有导致服务无法释放”的常见陷阱,并深入剖析AIDL生成的Stub/Proxy机制及Binder的底层查找原理。
1. 场景重现与架构设计
我们的目标架构如下:
- Native Service (C++): 核心服务。配置为
disabled(不自启)和oneshot(运行完可退出)。它需要能够响应客户端的连接,并在无引用时退出(需配合相应的空闲检测机制)。 - AIDL: 定义跨进程通信接口。
- Framework Manager (Java): 关键点。它不能长期持有服务的Binder引用,否则服务永远认为自己“正在被使用”而无法释放。必须按需获取,用完即走。
- SDK (Jar): 封装Manager,供App集成。
2. 核心实现步骤
2.1 定义AIDL接口
路径:frameworks/base/core/java/com/example/native/INativeLazyService.aidl
1 | package com.example.native; |
2.2 Native层服务实现 (C++)与RC配置
Init.rc 配置 (懒加载核心):
1 | service native_lazy_service /system/bin/native_lazy_service |
Main.cpp (C++):
为了配合懒加载,服务在注册时通常需要配合 libbinder_ndk 的懒加载API,或者在传统Binder中,主线程不进入死循环,而是处理完任务后退出。但为了演示通用Binder机制,我们假设该服务由Client触发启动。
1 | #include <binder/LazyServiceRegistrar.h> |
2.3 Framework层 Manager 封装 (避坑指南)
错误做法:在Manager中使用单例模式,将 mService 作为成员变量长期缓存。
后果:Manager持有Binder Proxy对象,导致Native层的强引用计数永远 > 0,服务永远无法触发“空闲退出”逻辑,变成了常驻服务,违背了省电初衷。
正确做法:按需获取,用完即焚。
1 | package com.example.manager; |
在这里需要使用waitforservice等待获取服务,如果用getservice则是在手机开机之后曾经被激活的服务映射表中查询,由于该服务是按需调用,在开机时没有被调用过,所以用getservice是无法获取的。
2.4 打包 Jar
在 Android.bp 中配置 java_library 并编译即可。
3. 深度解析:AIDL调用过程与Binder机制
当我们在应用层执行 INativeLazyService.Stub.asInterface(binder) 时,系统究竟在做什么?为什么说它会“先找Framework,再找App”?
3.1 asInterface 的真相:本地对象 vs 远程代理
AIDL编译后生成的 Stub 类中,asInterface 方法是连接应用层与Binder核心的关口。其伪代码逻辑如下:
1 | // 自动生成的 Stub 类代码片段 |
这里其实存在 queryLocalInterface 的机制:
- 第一步(先在当前环境找):调用方(Client)拿到一个Binder句柄后,先检查这个句柄指向的实体是不是就在当前进程空间内。如果是(比如你在Framework层代码里调用了一个同在Framework层的Service),它就直接返回该服务的实体对象。这就好比“在Framework本地找”。
- 第二步(如果没有,则认为是远程):如果当前进程没有该实体,说明这是一个跨进程的引用。此时,系统会实例化一个
Proxy(代理)对象给应用。App拿到的这个Proxy,表面上拥有接口的所有方法,但其内部实现全是透传代码。
3.2 到底什么是 Binder?
Binder在Linux内核看来,就是一个字符驱动设备 /dev/binder。但对于Android应用架构,它是粘合剂。
**内存映射 (mmap)**:
普通IPC(如管道、Socket)需要两次拷贝(发送方用户空间 -> 内核 -> 接收方用户空间)。
Binder通过mmap将 内核缓冲区 和 接收进程的用户空间 映射到同一块物理内存。- 结果:发送方数据拷贝到内核后,接收方直接可见。只发生一次拷贝。
C/S 架构:
- Client: 发起请求(App)。
- Server: 响应请求(Native Service)。
- ServiceManager: 此处的“大管家”。
- Server 启动时向 ServiceManager 注册(addService)。
- Client 向 ServiceManager 查询(getService)。
- ServiceManager 本身也是一个 Binder 服务(Handle 为 0)。
3.3 完整的调用全流程
以应用调用 service.performTask("test") 为例:
Java Proxy 层:
App 调用 Proxy 的performTask。Proxy 将参数 “test” 序列化写入Parcel对象,然后调用mRemote.transact(CODE_PERFORM_TASK, data, reply, 0)。JNI / Native Proxy 层:
Java 的transact最终调用 Native C++ 的IPCThreadState::transact。Kernel Driver 层:
- 线程进入内核态。
- 驱动根据 Binder 句柄找到目标进程(Native Lazy Service)。
- 驱动将数据从 App 进程拷贝到目标进程的映射缓冲区。
- 驱动唤醒目标进程的 Binder 线程。
Native Stub 层:
- Native 服务线程被唤醒,读取数据。
- 执行
BnNativeLazyService::onTransact。 - 解析参数,调用真正的
performTask实现。
懒加载闭环:
- 任务执行完毕。
- Client(Java Manager)方法结束,局部变量释放。
- Binder 驱动检测到该 Handle 的引用计数归零,通知 Native 进程。
- Native 进程捕获到无引用状态,执行退出逻辑(如果实现了该策略),完成省电闭环。
- 标题: Native层添加按需调用的服务以供框架层和应用层调用
- 作者: XCurry
- 创建于 : 2026-01-03 17:00:00
- 更新于 : 2026-01-03 17:08:03
- 链接: https://github.com/XYXMichael/2026/01/03/工作随想/Native层添加按需调用的服务以供框架层和应用层调用/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。