🌡️ 引言:为什么“显示 CPU 温度”是绝佳学习案例?
- 涉及层级最全:Kernel → HAL → SystemService → Framework → SystemUI
- 数据流清晰:单向读取(非控制类),逻辑简单但完整
- 贴近真实需求:性能监控、过热预警、调试工具等场景高频使用
更重要的是——
它完美展示了 AOSP 中“被动采集 + 主动推送 + UI 响应”的三段式数据流动模型。
今天,我们就用“搭建一条体温监测流水线”的比喻,手把手打通这条链路!
🏗️ 整体架构:五层数据流水线
| 层级 | 角色 | 类比 |
|---|---|---|
| Kernel | 体温传感器 | 医院的红外测温仪 |
| HAL | 数据采集员 | 护士,定时读取体温 |
| SystemService | 健康数据中心 | 医生办公室,汇总数据 |
| Framework | 对外窗口 | 挂号台,提供查询接口 |
| SystemUI | 显示屏 | 病房门口的电子体温牌 |
✅ 我们的目标:让“电子体温牌”实时显示最新体温!
第一章:第 1 步 —— Kernel 提供“体温数据源”
大多数 Android 设备通过thermal sysfs暴露 CPU 温度:
# 查看 CPU 温度(单位:毫摄氏度) cat /sys/class/thermal/thermal_zone0/temp # 输出:42000 → 表示 42.0°C💡 不同设备路径可能不同(如
thermal_zone7),可通过:adb shell ls /sys/class/thermal/thermal_zone*/type找到类型为
cpu-*或CPU的 zone。
✅本教程假设路径为/sys/class/thermal/thermal_zone0/temp。
无需修改 Kernel,直接读即可!
第二章:第 2 步 —— HAL 层:招聘“体温护士”
📜 步骤 1:定义护士岗位说明书(HIDL)
创建:hardware/interfaces/thermal/1.0/IThermal.hal
package android.hardware.thermal@1.0; interface IThermal { // 获取 CPU 温度(单位:摄氏度) getCpuTemperature() generates (float temperature); }🛠️ 步骤 2:实现护士工作(C++)
hardware/interfaces/thermal/1.0/default/Thermal.cpp:
#include <fstream> #include <string> #include <android-base/strings.h> Return<float> Thermal::getCpuTemperature() { std::ifstream tempFile("/sys/class/thermal/thermal_zone0/temp"); std::string line; if (std::getline(tempFile, line)) { // 转换:42000 → 42.0 int milliTemp = std::stoi(line); return static_cast<float>(milliTemp) / 1000.0f; } return -1.0f; // 错误值 }✅ 护士只做一件事:读文件 → 转换单位 → 返回结果。
🚪 步骤 3:让护士上岗(启动服务)
android.hardware.thermal@1.0-service.rc:
service vendor.thermal-hal /vendor/bin/hw/android.hardware.thermal@1.0-service class hal user system编译后,系统开机自动运行,护士就位!
第三章:第 3 步 —— SystemService:建立“健康数据中心”
🏢 创建 ThermalService(医生办公室)
frameworks/base/services/core/java/com/android/server/ThermalService.java:
public class ThermalService extends IThermalService.Stub { private IThermal mHal; public ThermalService() { try { mHal = IThermal.getService(); // 找到护士 } catch (Exception e) { Slog.e("ThermalService", "找不到体温护士", e); } } @Override public float getCpuTemperature() { if (mHal != null) { try { return mHal.getCpuTemperature(); // 问护士要数据 } catch (Exception e) { Slog.e("ThermalService", "获取体温失败", e); } } return -1.0f; } }📋 在 SystemServer 中注册
SystemServer.java:
ThermalService thermal = new ThermalService(); ServiceManager.addService("thermal", thermal); // 登记进通讯录 Slog.i(TAG, "Thermal Service started");✅ 现在,任何系统组件都能通过
ServiceManager.getService("thermal")查询体温!
第四章:第 4 步 —— Framework:设立“挂号窗口”
📖 定义对外接口(AIDL)
frameworks/base/core/java/android/os/IThermalService.aidl:
interface IThermalService { float getCpuTemperature(); }👩💼 在 ContextImpl 中注册窗口
ContextImpl.java:
registerService(Context.THERMAL_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService("thermal"); return IThermalService.Stub.asInterface(b); } });🏷️ 声明服务常量
Context.java:
public static final String THERMAL_SERVICE = "thermal";✅ 现在,App 或 SystemUI 可以通过
getSystemService(Context.THERMAL_SERVICE)获取窗口!
第五章:第 5 步 —— SystemUI:安装“电子体温牌”(状态栏)
这才是最精彩的部分!如何把数据画到状态栏?
🔧 步骤 1:创建温度监控器(TemperatureController)
frameworks/base/packages/SystemUI/src/com/android/systemui/thermal/TemperatureController.java:
public class TemperatureController { private final IThermalService mService; private float mCurrentTemp = -1; private final List<Callback> mCallbacks = new ArrayList<>(); public TemperatureController(Context context) { mService = IThermalService.Stub.asInterface( ServiceManager.getService(Context.THERMAL_SERVICE) ); startMonitoring(); // 启动定时轮询 } private void startMonitoring() { Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { try { float temp = mService.getCpuTemperature(); if (temp != mCurrentTemp) { mCurrentTemp = temp; notifyChange(); // 通知 UI 更新 } } catch (Exception e) { Log.w("TemperatureController", "读取温度失败", e); } }, 0, 2, TimeUnit.SECONDS); // 每 2 秒查一次 } public void addCallback(Callback cb) { mCallbacks.add(cb); } private void notifyChange() { for (Callback cb : mCallbacks) cb.onTemperatureChanged(mCurrentTemp); } public interface Callback { void onTemperatureChanged(float celsius); } }💡 为什么用轮询而不是监听?
因为 Kernel不会主动通知温度变化!只能定期查。
🖼️ 步骤 2:创建状态栏图标(TemperatureIcon)
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/TemperatureIcon.java:
public class TemperatureIcon extends StatusBarIconView { private TextView mTextView; public TemperatureIcon(Context context) { super(context, null, 0); mTextView = new TextView(context); mTextView.setTextSize(12); mTextView.setTextColor(Color.WHITE); addView(mTextView); } public void updateTemperature(float celsius) { if (celsius > 0) { mTextView.setText(String.format("CPU: %.0f°C", celsius)); setVisibility(VISIBLE); } else { setVisibility(GONE); } } }🔌 步骤 3:把图标插入状态栏
修改StatusBar.java(简化版):
// 在 makeStatusBarView() 中 TemperatureIcon tempIcon = new TemperatureIcon(mContext); statusBar.addView(tempIcon); // 绑定控制器 TemperatureController controller = Dependency.get(TemperatureController.class); controller.addCallback(temp -> tempIcon.updateTemperature(temp));✅ 注意:实际需通过
Dependency注入或SysUIComponent初始化,此处简化。
第六章:权限与 SELinux(别让安全机制拦住你!)
🔒 1. 添加系统权限(可选)
若仅 SystemUI 使用,无需额外权限。
若 App 也要用,需在AndroidManifest.xml声明:
<uses-permission android:name="android.permission.ACCESS_THERMAL_DATA" />并在platform.xml授权。
🔐 2. SELinux 放行 HAL 访问 sysfs
device/<vendor>/sepolicy/vendor/file_contexts:
/sys/class/thermal/thermal_zone[0-9]+/temp u:object_r:sysfs_thermal:s0thermal_hal.te:
allow thermal_hal sysfs_thermal:file read;✅ 否则 HAL 会因权限拒绝而返回 -1!
第七章:编译、刷机、验证
🛠️ 编译命令
source build/envsetup.sh lunch aosp_arm64-userdebug make -j$(nproc) SystemUI services thermal-hal🔍 验证步骤
- 启动模拟器或真机
- 查看日志:
adb logcat | grep -E "(Thermal|Temperature)" - 观察状态栏右上角是否出现
CPU: 42°C - 用
adb shell cat /sys/class/thermal/.../temp对比数值
💡 若模拟器无 thermal 数据,可用脚本模拟:
echo 50000 > /sys/class/thermal/thermal_zone0/temp
第八章:优化建议(从能用到好用)
| 问题 | 优化方案 |
|---|---|
| 轮询耗电 | 改用inotify监听 sysfs 文件变化(需 native 支持) |
| 温度跳变 | 加滑动平均滤波(如取最近 3 次平均值) |
| 高温告警 | 当 > 60°C 时变红闪烁 |
| 多核支持 | 读取所有thermal_zone*取最高值 |
总结:数据流动的三大艺术
采集艺术(Kernel → HAL)
→ 用 sysfs 暴露硬件数据,HAL 封装读取逻辑。服务艺术(HAL → SystemService → Framework)
→ 通过 HIDL + Binder 构建安全、跨进程的数据通道。呈现艺术(Framework → SystemUI)
→ 用 Controller + Callback 解耦数据与 UI,实现响应式更新。
✅ 这套模式不仅适用于温度,还可用于:
- 电池健康度
- 网络信号强度
- 自定义传感器数据
- 系统负载(CPU 使用率)
🌈 结语:你已掌握 AOSP 数据流动的“任督二脉”
从前,你看到 SystemUI 里全是onXXX()、updateState(),觉得“没人调用”。
现在,你知道:
每一个回调,都是数据流经的驿站;
每一个监听,都是系统在等待下一次心跳。
当你能在状态栏显示 CPU 温度,
你就真正理解了:
AOSP 不是代码的堆砌,而是数据的生命之河。
下一篇预告:
《AOSP 客制化内功心法(六):
从零实现“勿扰模式快捷开关”——深入 Settings 与 Notification Manager 的联动机制》