适合人群:刚接触 AOSP、看到 HAL 就头大、不知道“谁在调用谁”的开发者
目标成果:让你的 App 能用一行代码点亮一块红灯 ——LedManager.setRed(255);
核心理念:AOSP 不是写代码,而是“搭管道”。
🍳 引言:把 AOSP 想象成一家“智能餐厅”
假设你要在餐厅里点一道“红光闪烁牛排”。
但这家餐厅很特别:
- 你(App)不能直接进厨房;
- 你只能对前台服务员说话;
- 服务员会通知后厨主管;
- 主管再叫厨师长;
- 厨师长最后指挥灶台师傅开火。
在 Android 世界里:
| 餐厅角色 | 对应 Android 组件 |
|---|---|
| 你(顾客) | App(比如微信、Launcher) |
| 前台服务员 | Framework API(如LedManager) |
| 后厨主管 | SystemService(如LedService) |
| 厨师长 | HAL(Hardware Abstraction Layer) |
| 灶台师傅 | Linux Kernel 驱动 |
| 火候(开火/关火) | 硬件(LED 灯) |
✅ 你的任务:搭建一条从“前台”到“灶台”的传话管道,让“点红灯”这个指令能准确传到硬件!
第一章:第 0 步 —— 先让“灶台”存在(模拟 LED)
现实中,LED 由驱动控制。但我们先用虚拟灶台(sysfs)模拟,不用真硬件。
🔧 操作(在手机或模拟器上):
# 创建三个“灶眼”:红、绿、蓝 echo "rgb_red" > /sys/class/leds/rgb_red/trigger echo "rgb_green" > /sys/class/leds/rgb_green/trigger echo "rgb_blue" > /sys/class/leds/rgb_blue/trigger现在,只要往/sys/class/leds/rgb_red/brightness写数字(0~255),红灯就会亮!
💡 这就像你在灶台上贴了标签:“红灶眼”、“绿灶眼”……
下一步,我们要让“厨师长”知道这些灶眼在哪!
第二章:第 1 步 —— 招聘“厨师长”(HAL 层)
“厨师长”就是HAL(硬件抽象层)。他的工作是:听指令,操作灶台。
📜 步骤 1:给厨师长写“岗位说明书”(HIDL 接口)
创建文件:hardware/interfaces/led/1.0/ILed.hal
interface ILed { // 指令1:设置 RGB 颜色 setRgb(uint8_t red, uint8_t green, uint8_t blue) generates (bool success); // 指令2:关灯 turnOff() generates (bool success); }✅ 这就像 HR 写的招聘要求:“会听‘setRgb’和‘turnOff’两个指令”。
🛠️ 步骤 2:真的招一个厨师长(C++ 实现)
创建文件:hardware/interfaces/led/1.0/default/Led.cpp
#include <fstream> #include <android-base/logging.h> Return<bool> Led::setRgb(uint8_t r, uint8_t g, uint8_t b) { // 打开“红灶眼”,写入火力值 std::ofstream red("/sys/class/leds/rgb_red/brightness"); red << static_cast<int>(r); // 同理处理绿、蓝 std::ofstream green("/sys/class/leds/rgb_green/brightness"); green << static_cast<int>(g); std::ofstream blue("/sys/class/leds/rgb_blue/brightness"); blue << static_cast<int>(b); LOG(INFO) << "灶台已调至: R=" << r << ", G=" << g << ", B=" << b; return true; }💡 这位厨师长只会做一件事:把数字写进 sysfs 文件。
他不关心是谁下的单,只管执行!
🚪 步骤 3:让厨师长上岗(启动服务)
Android 用.rc文件启动服务,就像“发工牌”:
android.hardware.led@1.0-service.rc:
service vendor.led-hal /vendor/bin/hw/android.hardware.led@1.0-service class hal user system编译后,系统开机时会自动运行这个服务,厨师长就位!
第三章:第 2 步 —— 设立“后厨主管”(SystemService)
“后厨主管”(LedService)负责:接收前台指令,转达给厨师长。
📞 Java 代码(LedService.java):
public class LedService extends ILedService.Stub { private ILed mChef; // 厨师长 public LedService() { try { mChef = ILed.getService(); // 找到已上岗的厨师长 } catch (Exception e) { Log.e("LedService", "找不到厨师长!"); } } @Override public boolean setRgb(int r, int g, int b) { if (mChef != null) { try { return mChef.setRgb((byte)r, (byte)g, (byte)b); // 下指令! } catch (Exception e) { Log.e("LedService", "指令失败", e); } } return false; } }✅ 注意:
ILed.getService()就像主管拨内线电话:“喂,厨师长在吗?”
🏢 把主管安排进“后厨办公室”(SystemServer)
在SystemServer.java中添加:
// 开业时,招聘 Led 主管 LedService led = new LedService(); ServiceManager.addService("led", led); // 把他登记进“员工通讯录”📗
ServiceManager就是餐厅的员工通讯录。
前台只要查“led”,就能找到这位主管!
第四章:第 3 步 —— 培训“前台服务员”(Framework API)
前台(App)不能直接找主管,必须通过标准话术。
📖 步骤 1:定义标准话术(AIDL)
frameworks/base/core/java/android/os/ILedService.aidl:
interface ILedService { boolean setRgb(int red, int green, int blue); }✅ 这就像规定:“顾客只能说‘setRgb(255,0,0)’,不能乱说话”。
👩💼 步骤 2:培训服务员(ContextImpl)
在ContextImpl.java中注册:
registerService(Context.LED_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { // 查通讯录,找到 Led 主管 IBinder binder = ServiceManager.getService("led"); // 把主管包装成“可对话的服务员” return ILedService.Stub.asInterface(binder); } });现在,App 只要写:
LedManager lm = (LedManager) getSystemService(Context.LED_SERVICE);就能拿到“前台服务员”!
第五章:第 4 步 —— 你(App)点菜!
终于到你出场了!
📱 App 代码(需系统权限):
// 1. 找到前台服务员 LedManager led = (LedManager) getSystemService(Context.LED_SERVICE); // 2. 说标准话术 if (led != null) { led.setRgb(255, 0, 0); // “我要红光牛排,火力全开!” }🎯 背后发生了什么?
你 → 说 "setRgb(255,0,0)" ↓ 前台服务员(Framework)→ 查通讯录 → 找到 Led 主管 ↓ 主管(LedService)→ 打内线 → 告诉厨师长(HAL) ↓ 厨师长 → 写文件 → /sys/class/leds/rgb_red/brightness = 255 ↓ Kernel 驱动 → 点亮红灯!✅ 全链路打通!而你只写了一行代码。
第六章:为什么你看不到“函数调用”?——因为这是“传话游戏”
回到最初的问题:
“为什么源码里全是
onXXX()、setRgb()这样的函数定义,却看不到谁在调用?”
答案是:调用发生在“运行时”,不是“写代码时”。
- 你写的
setRgb(),是厨师长的技能; - 谁调用它?是主管在运行时动态调用的;
- 主管怎么知道有这个技能?因为岗位说明书(HIDL)提前约定了。
🌟 这就是接口(Interface) + 实现(Implementation) + 动态绑定的威力!
第七章:调试技巧 —— 如何确认每一步都通了?
🔍 1. 看“厨师长”是否上岗
adb shell lshal list | grep led # 应输出:android.hardware.led@1.0::ILed/default🔍 2. 看“主管”是否在岗
adb shell service list | grep led # 应输出:led: [android.os.ILedService]🔍 3. 手动测试“灶台”
adb shell echo 255 > /sys/class/leds/rgb_red/brightness # 红灯应亮🔍 4. 看日志
adb logcat | grep -E "(Led|led_hal)" # 应看到 "灶台已调至: R=255..."第八章:常见“翻车”现场 & 解决方案
| 问题 | 原因 | 解决 |
|---|---|---|
| 红灯不亮 | SELinux 拒绝写 sysfs | 在 sepolicy 中放行 |
getService()返回 null | HAL 服务没启动 | 检查.rc文件和 init 日志 |
App 找不到LED_SERVICE | 没注册到 ContextImpl | 检查registerService |
编译报错找不到ILed | HIDL 未生成 | 运行make hidl-gen |
总结:一张图看懂全链路
[你(App)] │ ▼ [前台服务员] ← getSystemService("led") │ ▼ (Binder IPC) [后厨主管] ← ServiceManager.getService("led") │ ▼ (HIDL) [厨师长] ← ILed.getService() │ ▼ (sysfs) [灶台师傅(Kernel)] │ ▼ [LED 灯亮!]✅AOSP 客制化的本质:
不是写业务逻辑,而是搭建一条安全、可靠、可维护的“传话管道”。
每一层只关心“上游说什么”和“下游能做什么”,彼此解耦。
🌈 最后的话
当你下次看到 AOSP 里“只有函数定义”的代码,
请记住:
那不是死代码,而是等待被“传话”激活的技能。
你不需要知道“谁会调用我”,
你只需要:
- 写好岗位说明书(接口);
- 做好自己的本职工作(实现);
- 确保自己能被找到(注册服务)。
剩下的,交给 Android 的“传话系统”!