AI 技术博客
Android全栈9 分钟阅读4835

【全栈第12课】App→Framework→HAL 全竖线(阶段二收官)

Android 全栈工程师进阶教程 第12课。App→Framework→HAL 全竖线(阶段二收官)。基于公开 AOSP 知识整理,含原理、可编译示例、常见问题定位。

> 本文是《Android 全栈工程师进阶教程》系列第 12 课。完整 26 课见 GitHub 仓库

【全栈第12课】App→Framework→HAL 全竖线(阶段二收官)


0. 这节课你将做出什么

把 L1-L11 串成一条完整竖线:App 调 framework 服务 → framework 服务转手调 HAL → HAL(将来再下到驱动)。理解真实产线的分层调用模式。 学完你能回答:App 为什么不直接调 HAL,非要经过 framework 中转?一个完整的"App 点一下 → 硬件响应"链路上,每一层各干什么?

1. 背景:为什么 App 不能直接调 HAL

你可能想:App 想用硬件,直接调 HAL 不就行了?不行,原因:

  1. 权限:HAL 在 vendor 进程,App 是普通进程,SELinux 不允许 App 直接调 vendor HAL(也不应该)。
  2. 稳定性:HAL 接口是给 framework 用的,直接暴露给千万个 App 很危险。
  3. 抽象:framework 提供统一、稳定的 SDK API,屏蔽底层 HAL 的差异(不同厂商 HAL 不一样,App 不该感知)。

所以真实产线一律是:App → framework 服务(中转/权限校验/抽象)→ HAL → 驱动

2. 全局图:完整竖线(串起 L1-L11)

App
 │ getSystemService(HELLO_SERVICE)        ← L2/L3
 ▼ HelloManager(App 进程,BinderProxy)
 │ sayHelloViaHal(name) ── Binder ──▶
 ▼ HelloManagerService(system_server)     ← L2 实现,L6 sepolicy
 │ 转手调 HAL:IHelloHal::fromBinder(waitForService(...))  ← L10 姿势
 ▼ ── Binder(system→vendor)──▶ HelloHal(vendor 进程)  ← L9,L11 sepolicy
 │ ::sayHello 实现
 │ (真实场景这里再 open /dev 调 kernel driver,见阶段三 L14-L20)
 ▼ 返回值一路带回 App

每一层职责:

职责对应课
App调 SDK API-
HelloManagerApp 侧代理,转 BinderL2
HelloManagerService权限校验(getCallingUid)、转调 HAL、缓存降级L2/L3/L6
HelloHal操作硬件(将来下到驱动)L9/L11
驱动真正读写硬件寄存器阶段三

3. 某真实机型 真实源码印证

真实例子:App 调 Vibrator.vibrate()VibratorManagerService(system_server)→ VibratorHalController(L10 那套 fromBinder)→ vibrator HAL → 驱动 → 马达震动。 你写的 hello 竖线就是这个模式的简化版。framework 服务作为"中间人",是这条链的枢纽。

4. 前置准备

  • 依赖 L2(HelloManagerService)、L9(HelloHal)、L10(framework 调 HAL 姿势)、L11(HAL sepolicy)。
  • 本课主要是把前面的串起来 + 在 HelloManagerService 里加一个"转调 HAL"的方法。

5. 动手:让 framework 服务转调 HAL

在 L2 的 HelloManagerService 里加一个 sayHelloViaHal 方法(示意 framework→HAL 中转):

// HelloManagerService(system_server)新增方法
public String sayHelloViaHal(String name) {
    // 权限校验:先确认调用方有资格(L1 的 getCallingUid)
    final int uid = Binder.getCallingUid();
    // ... 这里可做权限判断 ...

    // 拿 HAL(若 HAL 接口生成了 java 后端,system_server 可直接用;
    //  否则经 JNI 下到 native 用 ndk 接口,见 L5)
    IHelloHal hal = IHelloHal.Stub.asInterface(
            ServiceManager.waitForService("vendor.example.demo.IHelloHal/default"));
    if (hal == null) return "HAL 不可用";        // 降级:HAL 没起时的兜底

    try {
        return hal.sayHello(name);   // framework 转手调 HAL,把结果带回 App
    } catch (RemoteException e) {
        return "HAL 调用失败";        // HAL 崩了的兜底
    }
}

逐步讲为什么:

  • 权限校验在 framework 做:App 调进来,framework 用 getCallingUid 判断这个 App 有没有资格用硬件,挡住没权限的。这是 App 不能直接调 HAL 的核心价值之一。
  • HAL 不可用时降级:HAL 没起/崩了,framework 返回兜底值,而不是让 App 崩。
  • App 全程不碰 HAL:App 只认 HelloManager.sayHelloViaHal,完全不知道底下有没有 HAL、HAL 在哪。这就是抽象。

> 注:Java framework 调 ndk AIDL HAL 有两条路:① HAL 接口同时生成 java 后端,system_server 直接用;② 经 JNI(L5)下到 native 用 ndk 接口。真实硬件打通在阶段三(配合驱动)更完整,本课讲清中转模式。

6. 看底层:这条竖线上的三次 Binder

完整一次 App 调用,跨了两次 Binder:

  1. App → HelloManagerService:第一次 Binder(App 进程 → system_server)
  2. HelloManagerService → HelloHal:第二次 Binder(system_server → vendor 进程) 每次 Binder 都各自盖调用方 uid 戳(L1)。所以 HAL 里 getCallingUid 拿到的是 system_server 的 uid,不是原始 App 的——这点要注意,权限校验要在 framework 那一层做(它能拿到真正的 App uid)。

7. 编译 & 运行(验证状态如实)

make framework services
# 刷机后 App 调用:
adb logcat | grep -iE "HelloManagerService|HelloHal"

预期(完整链路日志):

HelloManagerService: sayHelloViaHal name=zhoubenliang, caller uid=10xxx(App)
HelloHal: sayHello called (caller uid=1000 system_server)   ← 注意 HAL 看到的是 system uid
HelloManagerService: HAL 返回 Hello from HAL, zhoubenliang
App: 收到 Hello from HAL, zhoubenliang

> 验证状态(诚实):本课为串联/集成说明课,展示真实产线中转模式;Java 调 ndk HAL 的完整打通 + 真机验证在阶段三结合驱动更有意义,本课未实跑。

8. 踩坑提醒

  1. 以为 HAL 能拿到 App 的 uid:错!HAL 经 framework 中转,getCallingUid 拿到的是 system_server 的 uid。权限校验必须在 framework 层(能拿到 App uid)。
  2. App 直接调 HAL:SELinux 会拒(也不该这么设计)。一律经 framework。
  3. framework 没做降级:HAL 没起就 NPE/抛异常给 App。要兜底。
  4. 同步调用链太长卡主线程:App→framework→HAL 两次 Binder 都同步,任一层慢都卡。考虑异步/oneway。
  5. Java 调 ndk HAL 没生成 java 后端:aidl_interface 没开 java 后端,system_server 直接用不了,要么开 java 后端要么走 JNI。

9. 常见问题分析与定位(全竖线实战)

9.1 常见问题清单

  1. App 调用返回兜底值("HAL 不可用")
  2. App 调用 SecurityException(权限被拒)
  3. 链路某处卡住,App 无响应
  4. HAL 拿到的 uid 不对导致权限逻辑错
  5. 数据在某一层"丢了"或变了

9.2 分析与定位

分层定位法:沿竖线从上往下,确认每一层"收到了没"。

# 第1层 App:有没有发出调用
adb logcat | grep "<App tag>"
# 第2层 framework 服务:收到了吗
adb logcat | grep HelloManagerService
# 第3层 HAL:收到了吗
adb logcat | grep HelloHal
adb shell lshal | grep hello      # HAL 起着吗

① 返回"HAL 不可用" — framework 拿不到 HAL → HAL 没起(回 L9/L11)或服务名不一致(L10)。 ② SecurityException — framework 权限校验拒了,或 SELinux 拦。看 framework 服务里的校验逻辑 + logcat avc③ 卡住 — 哪一层日志停了就卡在那层之后。framework 日志有、HAL 日志没有 → 卡在 framework→HAL 这次 Binder(HAL 慢/死锁,看 HAL 进程 trace)。 ④ uid 不对 — 见踩坑1,权限校验放错层。framework 层用 App 的 uid,别在 HAL 层判 App 权限。 ⑤ 数据丢/变 — 在每层日志打印参数/返回值,看哪层进去对、出来错。

9.3 定位决策树

全竖线出问题
├─ 沿竖线看日志:App→HelloManagerService→HelloHal,哪层没收到=卡在上一层之后
├─ "HAL 不可用" → lshal HAL 起没(L9/L11)+ 服务名(L10)
├─ SecurityException → framework 校验逻辑 + logcat avc
├─ 卡住 → 哪层日志停 → 那层之后(framework→HAL 卡看 HAL trace)
├─ uid 不对 → 权限校验放 framework 层(用 App uid),别在 HAL 判 App
└─ 数据丢/变 → 每层打印参数/返回值,定位哪层出错

10. 小结(阶段二收官)

  1. App 不直接调 HAL:经 framework 中转,为了权限/稳定/抽象。
  2. 完整竖线:App→HelloManager→HelloManagerService(权限校验+转调+降级)→HAL→(驱动)。
  3. 跨两次 Binder,HAL 拿到的是 system_server uid,权限校验要在 framework 层做。
  4. framework 是枢纽:校验、转调、缓存、降级都在这层。
  5. 排查口诀:沿竖线看每层日志,哪层没收到就卡在上层之后;HAL 不可用查 lshal。

🎓 阶段二(HAL+Treble,L7-L12)收官:你已掌握写一个 HAL、让它起来、被 framework 调、最终服务 App 的完整能力。

下节预告

L13:内核编译 + menuconfig + GKI —— 进入阶段三(Kernel/驱动)。HAL 下面就是 kernel driver,先搞懂现代 Android 内核(GKI)怎么编。

评论