Skip to content

HAL接口迁移

追踪 Power HAL 的稳定 AIDL 服务发现、Hint Session 创建、FMQ 通道和资源清理,并与保留的 HIDL 1.0 passthrough 路径对照。

基于android-17.0.0_r1
Android源码阅读HALHIDLAIDL

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::IPowerandroid.hardware.power@1.0-serviceHIDL legacy support对照旧路径
hardware/interfaces/power/aidl/android.hardware.power::IPowerandroid.hardware.power-service.exampleAIDL 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

c
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

java
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.bp
  • hardware/interfaces/power/1.0/Android.bp

源码文件:hardware/interfaces/power/aidl/Android.bp

make
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

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 声明同一个实例:

xml
<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.cpp
  • hardware/interfaces/power/1.0/IPower.hal

源码文件:hardware/interfaces/power/1.0/default/service.cpp

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

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

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

java
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

java
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

java
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

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

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

java
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

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

java
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 不存在waitForDeclaredServicemPowerHal 为空Hint session 不支持
服务存在但版本读取失败构造函数IllegalStateException服务初始化失败
TID 不属于调用进程checkTidValidSecurityException不触碰 HAL
带配置创建不支持halCreateHintSessionWithConfig记录一次降级改走基础创建
TID 为空vendor Power::createHintSessionEX_ILLEGAL_ARGUMENTframework 收到失败
token 已死亡linkToDeathIllegalStateException不创建通道
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

java
@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

java
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 中可以用以下命令复述主线:

bash
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 实现和测试。这样才能知道一次“接口迁移”改变了哪些边界,哪些旧行为仍由兼容代码保留。