HAL接口迁移
本文面向已经能阅读 C++、Java 和 Binder 接口,知道 system_server、vendor 进程与 HAL 之间存在进程边界的读者。建议先读 Java调用链 了解跨进程入口,再读 搜索调用链 练习从接口声明反查消费者。
本文不把 HIDL 和 AIDL 写成语法速查表,而是回答一个具体问题:Android 17 的 Power HAL hint session,怎样从 HintManagerService 的 Java 请求到达 vendor AIDL 服务;同一仓库里旧 HIDL 接口又在什么位置、以什么方式被启动? 读者读完后应能定位接口声明、服务注册、客户端获取、会话创建、异步通道和死亡清理的源码,并能说明“接口语言不同”为什么会导致服务管理器、稳定性声明和实现基类不同。
1. 迁移对象
Power HAL 是一个适合阅读迁移的案例,因为 Android 17 同时保留两套真实代码:
| 路径 | 接口 | 服务进程 | 服务管理 | 本文用途 |
|---|---|---|---|---|
hardware/interfaces/power/1.0/ | android.hardware.power@1.0::IPower | android.hardware.power@1.0-service | HIDL legacy support | 对照旧路径 |
hardware/interfaces/power/aidl/ | android.hardware.power::IPower | android.hardware.power-service.example | AIDL servicemanager | 追踪当前主线 |
这里的“迁移”不是把旧目录删除后改名。旧 HIDL 服务仍有自己的入口,AIDL 服务则以新的 descriptor、VINTF fragment 和 NDK Binder 实现注册。读源码时,首先要根据消费者使用的服务发现 API 选择正确分支。
2. 接口声明
2.1 HIDL版本
HIDL 把版本写在包名中。Power HAL 1.0 的接口以 generates 描述回调式多返回值:
源码文件:hardware/interfaces/power/1.0/IPower.hal
package android.hardware.power@1.0;
interface IPower {
setInteractive(bool interactive);
powerHint(PowerHint hint, int32_t data);
setFeature(Feature feature, bool activate);
getPlatformLowPowerStats()
generates (vec<PowerStatePlatformSleepState> states, Status retval);
}这个文件只定义协议,不负责加载 power_module_t。1.0/Android.bp 把它声明为 hidl_interface,由 HIDL 工具生成 Java/C++ 绑定;服务实现仍需继承生成的接口并接入 HIDL 服务启动框架。
2.2 AIDL版本
AIDL 使用稳定性注解和接口版本管理。Android 17 的 IPower.aidl 已经围绕 hint session 承担比旧 HIDL 更丰富的职责:
源码文件:hardware/interfaces/power/aidl/android/hardware/power/IPower.aidl
package android.hardware.power;
@VintfStability
interface IPower {
oneway void setMode(in Mode type, in boolean enabled);
boolean isModeSupported(in Mode type);
oneway void setBoost(in Boost type, in int durationMs);
IPowerHintSession createHintSession(
in int tgid, in int uid, in int[] threadIds, in long durationNanos);
IPowerHintSession createHintSessionWithConfig(in int tgid, in int uid,
in int[] threadIds, in long durationNanos, in SessionTag tag,
out SessionConfig config);
ChannelConfig getSessionChannel(in int tgid, in int uid);
oneway void closeSessionChannel(in int tgid, in int uid);
}这里有三个必须在源码中分别追踪的语义:oneway 影响调用是否等待服务端返回;out SessionConfig 是 Binder 返回值,不是 Java 方法的异常约定;IPowerHintSession 是另一个 Binder 接口,创建成功后会成为独立的远程对象。
2.3 构建边界
AIDL 的 Android.bp 把接口标成 stability: "vintf",并启用 NDK、Java 和 Rust backend,同时以 versions_with_info 固定 1~7 版 API。HIDL 的 bp 则把 root、interfaces 和 .hal 文件交给 hidl_interface。这两个构建声明比“文件后缀不同”更能说明迁移边界:稳定性、生成 backend 和版本冻结都在构建层被约束。
相关源码:
hardware/interfaces/power/aidl/Android.bphardware/interfaces/power/1.0/Android.bp
源码文件:hardware/interfaces/power/aidl/Android.bp
aidl_interface {
name: "android.hardware.power",
vendor_available: true,
srcs: ["android/hardware/power/*.aidl"],
stability: "vintf",
backend: {
cpp: { enabled: false },
ndk: { enabled: true },
java: { sdk_version: "module_current", enabled: true,
platform_apis: true },
rust: { enabled: true },
},
versions_with_info: [
{ version: "1", imports: [] },
{ version: "7", imports: [
"android.hardware.common.fmq-V1",
"android.hardware.common-V2",
] },
],
frozen: true,
}版本数字在这里表示冻结的 AIDL API 版本,不是设备 SDK_INT,也不是 HIDL 包名中的 @1.0。
3. 服务注册
3.1 AIDL入口
AIDL 默认服务的 main.cpp 是一个完整的服务启动入口。它创建 Power,取出 BnPower 生成的 Binder 对象,用 descriptor 和 /default 组成实例名,然后交给 AServiceManager_addService():
源码文件:hardware/interfaces/power/aidl/default/main.cpp
int main() {
ABinderProcess_setThreadPoolMaxThreadCount(0);
std::shared_ptr<Power> power = ndk::SharedRefBase::make<Power>();
const std::string instance = std::string() + Power::descriptor + "/default";
binder_status_t status = AServiceManager_addService(
power->asBinder().get(), instance.c_str());
CHECK_EQ(status, STATUS_OK);
ABinderProcess_joinThreadPool();
return EXIT_FAILURE;
}power-default.xml 中的 VINTF fragment 声明同一个实例:
<manifest version="1.0" type="device">
<hal format="aidl">
<name>android.hardware.power</name>
<version>7</version>
<fqname>IPower/default</fqname>
</hal>
</manifest>服务启动由 power-default.rc 负责,运行用户是 nobody,组包含 system。因此客户端的 waitForDeclaredService() 能否成功,至少依赖三个独立条件:服务进程被 init 启动、VINTF 声明了实例、实例名与 descriptor 拼接结果一致。
3.2 HIDL入口
旧服务的入口完全不同:
相关源码:
hardware/interfaces/power/1.0/default/service.cpphardware/interfaces/power/1.0/IPower.hal
源码文件:hardware/interfaces/power/1.0/default/service.cpp
#include <android/hardware/power/1.0/IPower.h>
#include <hidl/LegacySupport.h>
using android::hardware::power::V1_0::IPower;
using android::hardware::defaultPassthroughServiceImplementation;
int main() {
return defaultPassthroughServiceImplementation<IPower>();
}HIDL 实现文件通过 HIDL_FETCH_IPower() 调用 hw_get_module(POWER_HARDWARE_MODULE_ID, ...),加载传统 power_module_t,再包装成 HIDL Power 对象。这个函数是旧路径的动态加载点,不应套用到 AIDL main.cpp:AIDL 服务直接构造一个 BnPower 实现并注册 Binder。
4. 客户端发现
4.1 客户端持有者
HintManagerService 在构造时创建 mPowerHal。真正的服务发现集中在 Injector.createIPower():
源码文件:frameworks/base/services/core/java/com/android/server/power/hint/HintManagerService.java
IPower createIPower() {
return IPower.Stub.asInterface(
ServiceManager.waitForDeclaredService(
IPower.DESCRIPTOR + "/default"));
}服务找到后,构造函数立即读取接口版本和 SupportInfo。如果远端调用抛 RemoteException,构造过程抛出 IllegalStateException("Could not contact PowerHAL!");这说明 HAL 可用性是 HintManagerService 初始化的前置条件,而不是创建单个 session 时才懒加载。
源码文件:frameworks/base/services/core/java/com/android/server/power/hint/HintManagerService.java
mPowerHal = injector.createIPower();
mPowerHalVersion = 0;
if (mPowerHal != null) {
try {
mSupportInfo = getSupportInfo();
} catch (RemoteException e) {
throw new IllegalStateException("Could not contact PowerHAL!", e);
}
}4.2 版本分流
getSupportInfo() 先调用 AIDL 生成接口提供的 getInterfaceVersion()。版本至少为 6 时,直接调用 HAL 的 getSupportInfo();较旧版本则按历史版本推导支持位图,以保持旧 HAL 行为:
源码文件:frameworks/base/services/core/java/com/android/server/power/hint/HintManagerService.java
相关函数/类型:getSupportInfo
SupportInfo getSupportInfo() throws RemoteException {
mPowerHalVersion = mPowerHal.getInterfaceVersion();
if (mPowerHalVersion >= 6) {
return mPowerHal.getSupportInfo();
}
SupportInfo supportInfo = new SupportInfo();
supportInfo.usesSessions = isHintSessionSupported();
if (mPowerHalVersion == 5) {
supportInfo.sessionHints = 255;
supportInfo.sessionModes = 1;
supportInfo.sessionTags = 31;
}
return supportInfo;
}这段代码揭示了接口迁移的实际难点:AIDL 版本升级不等于所有消费者立即只走新方法,framework 仍需依据 getInterfaceVersion() 保留旧能力的解释规则。
5. 会话创建
5.1 请求校验
应用通过 framework 的 IHintManager 进入 HintManagerService.BinderService.createHintSessionWithConfig()。在触碰 HAL 之前,它先检查支持状态、token、TID 非空,并根据调用者 UID/TGID 验证每个线程确实属于调用进程。失败时分别是 UnsupportedOperationException、参数异常或 SecurityException,不会把非法线程列表交给 vendor。
源码文件:frameworks/base/services/core/java/com/android/server/power/hint/HintManagerService.java
相关函数/类型:BinderService.createHintSessionWithConfig
if (!isHintSessionSupported()) {
throw new UnsupportedOperationException(
"PowerHintSessions are not supported!");
}
Objects.requireNonNull(token);
Objects.requireNonNull(creationConfig.tids);
Preconditions.checkArgument(creationConfig.tids.length != 0,
"tids should not be empty.");
final int callingUid = Binder.getCallingUid();
final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
final Integer invalidTid = checkTidValid(callingUid, callingTgid,
creationConfig.tids, nonIsolated);
if (invalidTid != null) {
throw new SecurityException(formatTidCheckErrMsg(
callingUid, creationConfig.tids, invalidTid));
}5.2 AIDL调用
校验通过后,服务优先尝试带配置的 AIDL 方法;如果 HAL 返回 UnsupportedOperationException,会把 mConfigCreationSupport 置为 false,后续请求不再反复尝试,然后回退到基础 createHintSession():
源码文件:frameworks/base/services/core/java/com/android/server/power/hint/HintManagerService.java
相关函数/类型:BinderService.createHintSessionWithConfig
Long halSessionPtr = null;
if (mConfigCreationSupport.get()) {
try {
halSessionPtr = mNativeWrapper.halCreateHintSessionWithConfig(
callingTgid, callingUid, tids, durationNanos, tag, config);
} catch (UnsupportedOperationException e) {
mConfigCreationSupport.set(false);
} catch (IllegalStateException e) {
throw new IllegalStateException(
"createHintSessionWithConfig failed: " + e.getMessage());
}
}
if (halSessionPtr == null) {
halSessionPtr = mNativeWrapper.halCreateHintSession(
callingTgid, callingUid, tids, durationNanos);
}这里的 mNativeWrapper 是 framework 到 native Power HAL 客户端的桥;AIDL 服务本身不是 Java 直接 new 出来的对象。源码阅读时必须继续追 halCreateHintSessionWithConfig 的 native 实现,才能知道 Java 的 Long 句柄如何对应 native session。本文把 Java 侧的降级条件和 HAL AIDL 契约固定下来,不把未展开的 JNI 细节臆写成 Java 调用。
5.3 Vendor实现
AIDL 默认实现继承生成的 BnPower,业务方法返回 ndk::ScopedAStatus。示例实现对空 TID 明确返回 EX_ILLEGAL_ARGUMENT,非空时创建 PowerHintSession 并保存在 mPowerHintSessions:
源码文件:hardware/interfaces/power/aidl/default/Power.cpp
ScopedAStatus Power::createHintSession(
int32_t, int32_t, const std::vector<int32_t>& tids, int64_t,
std::shared_ptr<IPowerHintSession>* out) {
if (tids.size() == 0) {
*out = nullptr;
return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
std::shared_ptr<IPowerHintSession> session =
ndk::SharedRefBase::make<PowerHintSession>();
mPowerHintSessions.push_back(session);
*out = session;
return ScopedAStatus::ok();
}接口声明、framework 校验和 vendor 校验形成三层边界:AIDL 保证类型与事务格式,framework 负责权限和进程归属,vendor 实现仍可拒绝不支持的参数。不能因为 framework 已检查 TID 非空,就声称所有 vendor 都接受任意线程 ID。
6. 会话更新
创建返回的 IPowerHintSession 不是普通数据结构,而是一个新的 Binder 对象。framework 的 AppHintSession 持有 native 句柄和线程集合;setMode() 先检查当前 session 是否允许发 hint,再把模式转给 native wrapper:
源码文件:frameworks/base/services/core/java/com/android/server/power/hint/HintManagerService.java
public void setMode(int mode, boolean enabled) {
synchronized (this) {
if (!isHintAllowed()) {
return;
}
Preconditions.checkArgument(mode >= 0,
"the mode Id value should be greater than zero.");
if (mode == SessionMode.POWER_EFFICIENCY) {
mPowerEfficient = enabled;
} else if (mode == SessionMode.GRAPHICS_PIPELINE) {
mGraphicsPipeline = enabled;
}
mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled);
}
}“允许发 hint”是 framework 状态,不是 HAL 的返回值。UID 进入后台时,onUidStateChanged() 把更新投递到 handler,再遍历活动 session 更新 updateHintAllowedByProcState();因此一次 setMode() 被静默跳过,可能是进程状态策略,而不是 Binder 失败。
7. 通道生命周期
7.1 FMQ配置
AIDL Power HAL 为高频 session 更新提供 getSessionChannel()。ChannelItem.openChannel() 先把客户端 token 链接到 death recipient,再向 HAL 请求 ChannelConfig 并缓存。这个顺序保证:如果 token 已死亡,无法留下一个没有客户端所有者的通道。
源码文件:frameworks/base/services/core/java/com/android/server/power/hint/HintManagerService.java
相关函数/类型:ChannelItem.openChannel
private class ChannelItem implements IBinder.DeathRecipient {
public void openChannel() {
if (!mLinked) {
try {
mToken.linkToDeath(this, 0);
} catch (RemoteException e) {
throw new IllegalStateException("Client already dead", e);
}
mLinked = true;
}
if (mConfig == null) {
try {
mConfig = mPowerHal.getSessionChannel(mTgid, mUid);
} catch (RemoteException e) {
removeChannelItem(mTgid, mUid);
throw new IllegalStateException(
"Failed to create session channel!", e);
}
}
}
}默认 AIDL 实现使用 AidlMessageQueue<ChannelMessage, SynchronizedReadWrite> 创建固定容量队列,返回 duplicated descriptor 和读写 flag:
源码文件:hardware/interfaces/power/aidl/default/Power.cpp
ndk::ScopedAStatus Power::getSessionChannel(
int32_t, int32_t, ChannelConfig* out) {
static AidlMessageQueue<ChannelMessage, SynchronizedReadWrite>
queue{20, true};
out->channelDescriptor = queue.dupeDesc();
out->readFlagBitmask = 0x01;
out->writeFlagBitmask = 0x02;
out->eventFlagDescriptor = std::nullopt;
return ndk::ScopedAStatus::ok();
}这段示例里的 queue 读线程只是在没有数据时阻塞;它证明了 descriptor 的创建和 flag 的返回方式,不证明 vendor 已经实现真实的调度算法。
7.2 死亡清理
token 死亡会调用 removeChannelItem(),最终执行 closeChannel()。关闭时先解除 death link,再调用 AIDL closeSessionChannel();如果远端已经死亡,DeadObjectException 被视为通道已经关闭,不再重复失败。
源码文件:frameworks/base/services/core/java/com/android/server/power/hint/HintManagerService.java
相关函数/类型:ChannelItem.closeChannel
public void closeChannel() {
if (mLinked) {
mToken.unlinkToDeath(this, 0);
mLinked = false;
}
if (mConfig != null) {
try {
mPowerHal.closeSessionChannel(mTgid, mUid);
} catch (DeadObjectException e) {
// 远端死亡时,通道已经没有可清理的服务端对象
} catch (RemoteException e) {
throw new IllegalStateException(
"Failed to close session channel!", e);
}
mConfig = null;
}
}旧 HIDL 1.0 的 getPlatformLowPowerStats() 则通过 callback 返回 hidl_vec,并在 Power.cpp 中手动释放 voters、legacy_states。这体现了两套接口在资源边界上的差异:HIDL callback 的临时数组清理由实现负责;AIDL FMQ 通道的 descriptor 生命周期由 ChannelItem 与 Binder token 共同管理。
8. 失败分流
读取这条链路时,错误应按 owner 分层,而不是统称“HAL 调用失败”:
| 现象 | 发生位置 | 结果 | 后续路径 |
|---|---|---|---|
/default 不存在 | waitForDeclaredService | mPowerHal 为空 | Hint session 不支持 |
| 服务存在但版本读取失败 | 构造函数 | IllegalStateException | 服务初始化失败 |
| TID 不属于调用进程 | checkTidValid | SecurityException | 不触碰 HAL |
| 带配置创建不支持 | halCreateHintSessionWithConfig | 记录一次降级 | 改走基础创建 |
| TID 为空 | vendor Power::createHintSession | EX_ILLEGAL_ARGUMENT | framework 收到失败 |
| token 已死亡 | linkToDeath | IllegalStateException | 不创建通道 |
| HAL 已死亡 | closeChannel | 视为已关闭 | 清空本地配置 |
尤其要区分 AIDL 生成代理返回的 ScopedAStatus/RemoteException 与 framework 自己抛出的参数、权限异常。前者说明跨进程边界或 vendor 实现失败,后者说明请求在 system_server 内就被拒绝。
9. 接口测试
HintManagerServiceTest 使用 fake native wrapper 和模拟服务验证 framework 层条件。例如 testCreateHintSessionInvalidPid 把不属于调用进程的 TID 放入输入,断言抛出 SecurityException;它证明的是 framework 的归属检查,不是 AIDL 传输。
源码文件:frameworks/base/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
相关函数/类型:testCreateHintSessionInvalidPid
@Test
public void testCreateHintSessionInvalidPid() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
SessionCreationConfig config = makeSessionCreationConfig(
new int[]{TID, 1}, DEFAULT_TARGET_DURATION);
assertThrows(SecurityException.class, () ->
service.getBinderServiceInstance().createHintSessionWithConfig(
token, SessionTag.OTHER, config, new SessionConfig()));
}同一测试类的 testCreateHintSessionUpdatesAppTagToGame 先把 ApplicationInfo.category 设为游戏,再验证传给 native wrapper 的 tag 被转换为 SessionTag.GAME。这证明 framework 在进入 HAL 前会重写语义标签,但不证明 vendor 如何根据 tag 调频。
源码文件:frameworks/base/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
相关函数/类型:testCreateHintSessionUpdatesAppTagToGame
verify(mNativeWrapperMock).halCreateHintSessionWithConfig(
anyInt(), anyInt(), any(), anyLong(),
eq(SessionTag.GAME), any());VTS 的 Power AIDL 测试和 HIDL 1.0 测试分别位于 hardware/interfaces/power/aidl/vts/ 与 hardware/interfaces/power/1.0/vts/。它们面向 HAL 合规性;本文只把这些目录作为继续阅读的入口,不把未逐条展开的断言外推为“所有设备实现都通过”。
10. 验证路径
在 Android 17 checkout 中可以用以下命令复述主线:
rg -n "waitForDeclaredService|createIPower|getSupportInfo" \
frameworks/base/services/core/java/com/android/server/power/hint/HintManagerService.java
rg -n "createHintSessionWithConfig|halCreateHintSession" \
frameworks/base/services/core/java/com/android/server/power/hint/HintManagerService.java
rg -n "AServiceManager_addService|Power::createHintSession|getSessionChannel" \
hardware/interfaces/power/aidl/default
rg -n "defaultPassthroughServiceImplementation|HIDL_FETCH_IPower" \
hardware/interfaces/power/1.0/default复述时应能回答:客户端等待的实例名是什么?哪个对象持有 session?带配置创建失败后谁决定降级?token 死亡时哪些资源被清理?如果把 HIDL 的 HIDL_FETCH_IPower() 说成 AIDL 服务入口,说明还没有按服务发现机制区分两条路径。
11. 边界
本文追踪的是 Android 17 Power HAL hint session 的 framework、AIDL 服务和 HIDL 对照入口。它没有展开 JNI native wrapper 的全部实现、Linux kernel cpufreq 调节、厂商自定义 Power HAL,也没有把生成的 BpPower/BnPower 文件当成源码事实,因为这些文件由构建步骤产生。
真正有用的迁移判断是:先确认接口声明和稳定性,再确认服务注册名与服务管理器,接着追 framework 的版本分流、参数校验和资源 owner,最后读 vendor 实现和测试。这样才能知道一次“接口迁移”改变了哪些边界,哪些旧行为仍由兼容代码保留。
