Native层添加按需调用的服务以供框架层和应用层调用

XCurry Lv4

在Android系统开发(AOSP)中,为了优化整机功耗和内存,我们经常需要实现服务的懒加载(Lazy Loading)。即:服务默认不运行,当上层应用有请求时才拉起;当请求处理完毕且长时间无人使用时,服务自动退出释放资源。

最近我在AOSP中完成了一个需求:在Native层添加一个属性为 oneshotdisabled 的懒加载服务,并在Framework层封装Manager类,最终打包成Jar供上层应用调用。

本文将以此为例,展示如何避免“单例持有导致服务无法释放”的常见陷阱,并深入剖析AIDL生成的Stub/Proxy机制及Binder的底层查找原理。

1. 场景重现与架构设计

我们的目标架构如下:

  1. Native Service (C++): 核心服务。配置为 disabled(不自启)和 oneshot(运行完可退出)。它需要能够响应客户端的连接,并在无引用时退出(需配合相应的空闲检测机制)。
  2. AIDL: 定义跨进程通信接口。
  3. Framework Manager (Java): 关键点。它不能长期持有服务的Binder引用,否则服务永远认为自己“正在被使用”而无法释放。必须按需获取,用完即走。
  4. SDK (Jar): 封装Manager,供App集成。

2. 核心实现步骤

2.1 定义AIDL接口

路径:frameworks/base/core/java/com/example/native/INativeLazyService.aidl

1
2
3
4
5
package com.example.native;

interface INativeLazyService {
void performTask(String params);
}

2.2 Native层服务实现 (C++)与RC配置

Init.rc 配置 (懒加载核心):

1
2
3
4
5
6
7
service native_lazy_service /system/bin/native_lazy_service
class main
user system
group system
disabled # 核心:不随开机自动启动,等待指令
oneshot # 核心:进程退出后,Init进程不会自动重启它
interface aidl com.example.native.INativeLazyService/default

Main.cpp (C++):
为了配合懒加载,服务在注册时通常需要配合 libbinder_ndk 的懒加载API,或者在传统Binder中,主线程不进入死循环,而是处理完任务后退出。但为了演示通用Binder机制,我们假设该服务由Client触发启动。

1
2
3
4
5
6
#include <binder/LazyServiceRegistrar.h>

using android::binder::LazyServiceRegistrar;

auto lazyRegistrar = LazyServiceRegistrar::getInstance();
lazyRegistrar.registerService(service, serviceName);

2.3 Framework层 Manager 封装 (避坑指南)

错误做法:在Manager中使用单例模式,将 mService 作为成员变量长期缓存。
后果:Manager持有Binder Proxy对象,导致Native层的强引用计数永远 > 0,服务永远无法触发“空闲退出”逻辑,变成了常驻服务,违背了省电初衷。

正确做法按需获取,用完即焚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.example.manager;

import android.os.IBinder;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.RemoteException;
import com.example.native.INativeLazyService;

public class NativeLazyManager {
private static final String SERVICE_NAME = "com.example.native.INativeLazyService/default";
private static final String SERVICE_PROP = "ctl.start";
private static final String SERVICE_TARGET = "native_lazy_service";

// Manager本身可以是单例,但不要缓存Service接口
private static NativeLazyManager sInstance = new NativeLazyManager();
public static NativeLazyManager getInstance() { return sInstance; }

/**
* 核心方法:每次调用都重新获取服务
*/
private INativeLazyService getService() {
// 1. 尝试直接获取
IBinder binder = ServiceManager.waitforService(SERVICE_NAME);

// 2. 如果服务未启动 (Binder为空),则手动拉起
if (binder == null) {
// 设置属性触发 init 进程启动服务
SystemProperties.set(SERVICE_PROP, SERVICE_TARGET);

// 3. 轮询等待服务启动 (简化版,生产环境需增加超时控制)
int retries = 0;
while (binder == null && retries < 10) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
binder = ServiceManager.getService(SERVICE_NAME);
retries++;
}
}

if (binder != null) {
return INativeLazyService.Stub.asInterface(binder);
}
return null;
}

public void doWork(String params) {
// 获取服务引用
INativeLazyService service = getService();
if (service != null) {
try {
service.performTask(params);
} catch (RemoteException e) {
e.printStackTrace();
}
// 方法执行完毕,service 局部变量销毁
// Java层不再持有Proxy引用,Native层引用计数递减
}
}
}

在这里需要使用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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 自动生成的 Stub 类代码片段
public static com.example.native.INativeLazyService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
// 1. 关键查询:询问 Binder 对象,你是不是就在我这个进程里?
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

// 2. 如果 queryLocalInterface 返回了非空对象
// 说明 Service 和 Client 在同一个进程(例如都在 SystemServer 中)
// 此时直接返回 Java 对象本身,不走 Binder 驱动,直接进行函数调用(高效率)
if (((iin != null) && (iin instanceof com.example.native.INativeLazyService))) {
return ((com.example.native.INativeLazyService) iin);
}

// 3. 如果返回 null
// 说明 Service 在另一个进程(App调Native,或者App A 调 App B)
// 此时创建一个 Proxy 对象,封装 Binder 句柄,准备进行跨进程通信
return new com.example.native.INativeLazyService.Stub.Proxy(obj);
}

这里其实存在 queryLocalInterface 的机制:

  • 第一步(先在当前环境找):调用方(Client)拿到一个Binder句柄后,先检查这个句柄指向的实体是不是就在当前进程空间内。如果是(比如你在Framework层代码里调用了一个同在Framework层的Service),它就直接返回该服务的实体对象。这就好比“在Framework本地找”。
  • 第二步(如果没有,则认为是远程):如果当前进程没有该实体,说明这是一个跨进程的引用。此时,系统会实例化一个 Proxy(代理)对象给应用。App拿到的这个Proxy,表面上拥有接口的所有方法,但其内部实现全是透传代码。

3.2 到底什么是 Binder?

Binder在Linux内核看来,就是一个字符驱动设备 /dev/binder。但对于Android应用架构,它是粘合剂

  1. **内存映射 (mmap)**:
    普通IPC(如管道、Socket)需要两次拷贝(发送方用户空间 -> 内核 -> 接收方用户空间)。
    Binder通过 mmap内核缓冲区接收进程的用户空间 映射到同一块物理内存。

    • 结果:发送方数据拷贝到内核后,接收方直接可见。只发生一次拷贝
  2. C/S 架构

    • Client: 发起请求(App)。
    • Server: 响应请求(Native Service)。
    • ServiceManager: 此处的“大管家”。
      • Server 启动时向 ServiceManager 注册(addService)。
      • Client 向 ServiceManager 查询(getService)。
      • ServiceManager 本身也是一个 Binder 服务(Handle 为 0)。

3.3 完整的调用全流程

以应用调用 service.performTask("test") 为例:

  1. Java Proxy 层
    App 调用 Proxy 的 performTask。Proxy 将参数 “test” 序列化写入 Parcel 对象,然后调用 mRemote.transact(CODE_PERFORM_TASK, data, reply, 0)

  2. JNI / Native Proxy 层
    Java 的 transact 最终调用 Native C++ 的 IPCThreadState::transact

  3. Kernel Driver 层

    • 线程进入内核态。
    • 驱动根据 Binder 句柄找到目标进程(Native Lazy Service)。
    • 驱动将数据从 App 进程拷贝到目标进程的映射缓冲区。
    • 驱动唤醒目标进程的 Binder 线程。
  4. Native Stub 层

    • Native 服务线程被唤醒,读取数据。
    • 执行 BnNativeLazyService::onTransact
    • 解析参数,调用真正的 performTask 实现。
  5. 懒加载闭环

    • 任务执行完毕。
    • 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 进行许可。
评论