引言

手机发热是每个用户都经历过的痛点——玩游戏时突然降帧、户外拍摄自动退出、烈日下车载导航卡顿……这些都是系统温控机制在发挥作用。HarmonyOS NEXT 通过 @ohos.thermal 模块向应用层暴露了设备的温控等级,让开发者能够主动感知设备的热状态,在设备过热前做出智能调整。

不同于电池信息(@ohos.batteryInfo)的静态快照模式,@ohos.thermal 提供了两种数据获取方式:同步查询当前温控等级,以及回调注册监听等级变化。这是我们在鸿蒙新特性系列中遇到的第三种异步模式——Callback 模式(区别于 Promise 链式调用和同步常量读取)。

本文将创建一个"设备温控实验室"Demo,深入解析 @ohos.thermal 的全部 API,并探讨如何在实际应用中进行热感知编程。

@ohos.thermal 模块概述

@ohos.thermal 是 HarmonyOS NEXT 提供的设备热管理模块,位于 @kit.BasicServicesKit。它不直接返回温度数值(摄氏度),而是返回一个温控等级——由系统综合评估 CPU 温度、电池温度、环境温度等因素后计算出的抽象等级。

这种设计是务实的:应用开发者不需要关心具体的温度阈值(这些阈值因设备型号和硬件差异而不同),只需要知道"当前设备是否该降低负载"。

导入方式

import thermal from '@ohos.thermal';

API 总览

API 签名 类型 说明
getLevel() (): ThermalLevel 同步 获取当前温控等级
registerThermalLevelCallback() (callback: Callback<ThermalLevel>): void 注册回调 订阅温控等级变化
unregisterThermalLevelCallback() (callback?: Callback<void>): void 取消回调 取消订阅

ThermalLevel 枚举

这是整个模块最核心的类型——一个从 0(清凉)到 7(热逃逸)的八级枚举:

枚举值 数值 含义 系统行为
COOL 0 清凉 无限制,设备处于最佳状态
NORMAL 1 常温 正常发热,无需关注
WARM 2 微温 后台非关键任务可能受限
HOT 3 发热 非感知服务停止,性能下调
OVERHEATED 4 过热 主要服务负载降低
WARNING 5 高温警告 服务最大程度降级
EMERGENCY 6 紧急 仅保留基础功能
ESCAPE 7 热逃逸 所有 Ability 将被强制停止

COOLESCAPE 的递进反映了系统逐步收紧资源管控的过程。对于应用开发者来说,关键的分界点在 WARM(2)OVERHEATED(4)

  • WARM 以下:一切正常
  • WARM ~ HOT:建议主动降低非必要操作
  • OVERHEATED 以上:必须立即减少负载

核心 API 详解

1. getLevel() — 同步查询当前等级

function getLevel(): ThermalLevel;

这是最常用的 API。它永远是同步的,无需 Promise 或回调。你在任何时候调用它,都能立即获得当前的温控等级。

const level: thermal.ThermalLevel = thermal.getLevel();

if (level >= thermal.ThermalLevel.OVERHEATED) {
  // 设备过热,立即采取降温措施
  this.reduceWorkload();
}

注意getThermalLevel() 是旧版 API(已 deprecated),新代码应使用 getLevel()。两者功能相同,仅命名不同。

2. registerThermalLevelCallback() — 实时监听等级变化

function registerThermalLevelCallback(callback: Callback<ThermalLevel>): void;

这是 @ohos.thermal 区别于 @ohos.batteryInfo 的关键能力——它允许应用注册一个回调函数,当系统温控等级发生变化时自动触发。这在需要实时响应的场景中非常有用。

Callback 类型定义

在 HarmonyOS 中,Callback<T> 是一个函数类型:(data: T) => void。对于 thermal 模块,它就是 (level: ThermalLevel) => void

const onThermalChange: Callback<thermal.ThermalLevel> = (level: thermal.ThermalLevel) => {
  console.log('温控等级变化: ' + level);
  if (level >= thermal.ThermalLevel.HOT) {
    this.pauseAnimations();
    this.reduceFrameRate();
  }
};

thermal.registerThermalLevelCallback(onThermalChange);

关键约束:同一个回调函数只能注册一次。重复注册同一个函数不会生效(不会触发多次回调)。

3. unregisterThermalLevelCallback() — 取消订阅

function unregisterThermalLevelCallback(callback?: Callback<void>): void;

当不再需要监听温控变化时,应取消注册以避免内存泄漏。这个回调在页面销毁(aboutToDisappear)时必须调用。

重要发现unregisterThermalLevelCallbackcallback 参数是可选的。调用时不传参数会取消所有注册的回调。在 Demo 中,我们利用这个特性避免了类型转换问题:

private stopListen(): void {
  if (!this.listening) {
    return;
  }
  try {
    thermal.unregisterThermalLevelCallback(); // 无参调用
    this.listening = false;
  } catch (e) {
    // 处理错误
  }
}

这个设计的实用意义:registerThermalLevelCallback 接受 Callback<ThermalLevel>,但 unregisterThermalLevelCallback 的签名接受 Callback<void>——两个类型在 ArkTS 严格模式下不兼容。无参取消巧妙地绕过了这个类型鸿沟。

与 WiFi/getLinkedInfo 和 batteryInfo 的对比

// wifiManager:Promise 异步,链式调用
wifiManager.getLinkedInfo()
  .then(info => { ... })
  .catch(err => { ... });

// batteryInfo:同步常量,直接读取
const level: number = batteryInfo.batterySOC;

// thermal:同步查询 + 回调监听
const level: thermal.ThermalLevel = thermal.getLevel();
thermal.registerThermalLevelCallback(level => { ... });
thermal.unregisterThermalLevelCallback();

三种模式对应三种使用场景:

  • 同步常量适用于静态快照(电池信息不会每毫秒变化)
  • Promise 适用于一次性异步获取(WiFi 连接信息)
  • Callback 适用于持续监听(温控等级可能随负载变化)

实战:设备温控实验室

页面结构

┌─────────────────────────────────┐
│  < 设备温控实验室  @ohos.thermal │  ← 标题栏(#F97316 暖橙)
├─────────────────────────────────┤
│  当前温控等级                    │
│  ┌─────────────────────────┐   │
│  │  ┌────┐                 │   │  ← 等级大圆 + 描述 + 操作建议
│  │  │常温│  设备运行正常...  │   │
│  │  └────┘  ● 保持正常使用  │   │
│  └─────────────────────────┘   │
├─────────────────────────────────┤
│  温控等级刻度 (0~7)              │
│  ┌─────────────────────────┐   │
│  │ ██ ██ ██ ░░ ░░ ░░ ░░ ░░ │  ← 8段热度条(当前等级高亮)
│  │ 清凉 常温 微温 发热 ...   │   │
│  │ ─────────────────────── │   │
│  │ 等级 1/7 · 常温 — 安全范围│   │
│  └─────────────────────────┘   │
├─────────────────────────────────┤
│  [开始监听温控变化]             │  ← 实时监听开关
├─────────────────────────────────┤
│  温控知识                        │  ← 三则科普小贴士
├─────────────────────────────────┤
│  温控日志                        │  ← 等级变化事件日志
└─────────────────────────────────┘

状态管理设计

@State currentLevel: number = -1;       // 当前等级数值 (0-7)
@State levelLabel: string = '加载中...'; // 中文标签
@State levelColor: string = '#94A3B8';  // 动态颜色
@State levelDesc: string = '';          // 等级描述
@State levelAction: string = '';        // 操作建议
@State listening: boolean = false;      // 是否正在监听
@State eventLogs: EventLog[] = [];      // 事件日志
@State loading: boolean = true;         // 加载状态

private thermalCallback: Callback<thermal.ThermalLevel> | null = null;

设计要点:将原始枚举值 currentLevel 和派生的中文文本、颜色分离管理。这带来两个好处:

  1. 枚举值用于数值比较(如判断是否 ≥ 警戒线),中文字符串仅用于 UI 展示
  2. 颜色逻辑集中在一个方法中,修改配色方案时只需改一处

等级到 UI 的映射逻辑

Demo 中最复杂的逻辑是 updateLevelUI()——将 8 个枚举值映射到对应的中文标签、颜色、描述和建议:

private updateLevelUI(level: thermal.ThermalLevel): void {
  if (level === thermal.ThermalLevel.COOL) {
    this.levelLabel = '清凉';
    this.levelColor = '#3B82F6';               // 蓝色
    this.levelDesc = '设备温度正常,所有功能无限制';
    this.levelAction = '无需任何操作,尽情使用';
  } else if (level === thermal.ThermalLevel.NORMAL) {
    this.levelLabel = '常温';
    this.levelColor = '#10B981';               // 绿色
    this.levelDesc = '设备运行正常,轻微发热属正常现象';
    this.levelAction = '保持正常使用,注意散热即可';
  } else if (level === thermal.ThermalLevel.WARM) {
    this.levelLabel = '微温';
    this.levelColor = '#F59E0B';               // 琥珀色
    this.levelDesc = '设备轻微发热,后台非关键任务可能受限';
    this.levelAction = '建议关闭不用的后台应用';
  }
  // ... 更多等级
}

颜色语义设计:

  • 蓝/绿(COOL/NORMAL):安全区间,冷色调降低用户焦虑
  • 琥珀(WARM):过渡区间,黄色唤起注意但不恐慌
  • 橙/红(HOT ~ ESCAPE):危险区间,红色暗示紧迫性

8 级热度刻度可视化

使用 ForEach + 动态背景色创建一个直观的热度条:

Row({ space: 3 }) {
  ForEach(this.thermalBars, (item: ThermalBar) => {
    Column() {
      Column()
        .width('100%')
        .height(24)
        .backgroundColor(this.thermalBarColor(item.idx))
        .borderRadius(3)
      Text(item.label)
        .fontSize(9).fontColor(
          this.currentLevel === item.idx ? '#F97316' : '#94A3B8')
        .fontWeight(this.currentLevel === item.idx ?
          FontWeight.Bold : FontWeight.Normal)
    }
    .layoutWeight(1)
  })
}

thermalBarColor() 方法根据当前等级决定各段的颜色:

private thermalBarColor(levelIdx: number): string {
  if (this.currentLevel >= 0 && levelIdx <= this.currentLevel) {
    if (this.currentLevel <= 1) return '#3B82F6';  // 蓝
    if (this.currentLevel <= 2) return '#F59E0B';  // 琥珀
    if (this.currentLevel <= 3) return '#F97316';  // 橙
    return '#EF4444';                               // 红
  }
  return '#E2E8F0';  // 未到达的等级用浅灰
}

这个设计创造了一个"温度计效应"——越往右颜色越暖,当前等级以前的格都亮起,直观展示设备所处的热区间。
在这里插入图片描述
在这里插入图片描述

回调生命周期管理

回调的注册与清理是使用 @ohos.thermal 最容易出错的环节。Demo 展示了正确的生命周期管理模式:

aboutToDisappear(): void {
  this.stopListen();  // 页面销毁时取消回调
}

private stopListen(): void {
  if (!this.listening) {
    return;
  }
  try {
    thermal.unregisterThermalLevelCallback();  // 无参取消
    this.listening = false;
    this.addLog('温控监听已停止');
  } catch (e) {
    this.addLog('停止温控监听失败');
  }
}

如果不取消回调会发生什么? 页面被销毁后,回调函数仍被系统持有,导致:

  1. 内存泄漏——回调捕获的闭包变量无法被 GC
  2. 异常行为——回调触发时试图更新已销毁的 UI 组件

回调中的状态变更追踪

当温控等级变化时,Demo 不只记录新等级,还记录"从哪一级变化到哪一级":

this.thermalCallback = (level: thermal.ThermalLevel) => {
  this.currentLevel = level;
  const prevLabel: string = this.levelLabel;
  this.updateLevelUI(level);
  this.addLog('温控变化: ' + prevLabel + ' → ' + this.levelLabel);
};

这样做的好处:日志中能清晰地看到热状态的变化轨迹,便于调试和分析。

完整的温控感知架构

在实际应用中,热感知不是一个孤立的特性,而应该融入应用的整体架构。一个健壮的热感知方案包含三个层次:

第一层:等级查询

// 在关键操作前检查温控等级
if (thermal.getLevel() >= thermal.ThermalLevel.HOT) {
  // 降级策略
}

适用于:开始一个重负载任务前(如视频渲染、大文件下载、复杂的动画序列)。

第二层:实时监听

thermal.registerThermalLevelCallback((level: thermal.ThermalLevel) => {
  // 根据等级动态调整
});

适用于:需要在运行过程中持续调整策略的场景(如游戏引擎、视频通话)。

第三层:用户提示

if (level >= thermal.ThermalLevel.OVERHEATED) {
  AlertDialog.show({
    message: '设备温度过高,部分功能已被限制以保护设备',
  });
}

适用于:需要让用户理解为什么性能下降或功能受限的场景。

完整代码

import { router } from '@kit.ArkUI';
import thermal from '@ohos.thermal';
import { FontSize, Spacing } from '../common/Constants';

interface EventLog {
  time: string;
  msg: string;
}

interface ThermalBar {
  label: string;
  en: string;
  idx: number;
}

@Entry
@Component
struct ThermalLabPage {
  @State currentLevel: number = -1;
  @State levelLabel: string = '加载中...';
  @State levelColor: string = '#94A3B8';
  @State levelDesc: string = '';
  @State levelAction: string = '';
  @State listening: boolean = false;
  @State eventLogs: EventLog[] = [];
  @State loading: boolean = true;

  private thermalCallback: Callback<thermal.ThermalLevel> | null = null;

  private readonly thermalBars: ThermalBar[] = [
    { label: '清凉', en: 'COOL', idx: 0 },
    { label: '常温', en: 'NORMAL', idx: 1 },
    { label: '微温', en: 'WARM', idx: 2 },
    { label: '发热', en: 'HOT', idx: 3 },
    { label: '过热', en: 'OVERHEATED', idx: 4 },
    { label: '警告', en: 'WARNING', idx: 5 },
    { label: '紧急', en: 'EMERGENCY', idx: 6 },
    { label: '逃逸', en: 'ESCAPE', idx: 7 }
  ];

  aboutToAppear(): void {
    this.queryLevel();
  }

  aboutToDisappear(): void {
    this.stopListen();
  }

  private queryLevel(): void {
    try {
      const level: thermal.ThermalLevel = thermal.getLevel();
      this.currentLevel = level;
      this.updateLevelUI(level);
      this.addLog('查询温控等级: ' + this.levelLabel);
      this.loading = false;
    } catch (e) {
      this.loading = false;
      this.addLog('温控等级查询失败');
    }
  }

  private updateLevelUI(level: thermal.ThermalLevel): void {
    if (level === thermal.ThermalLevel.COOL) {
      this.levelLabel = '清凉';
      this.levelColor = '#3B82F6';
      this.levelDesc = '设备温度正常,所有功能无限制';
      this.levelAction = '无需任何操作,尽情使用';
    } else if (level === thermal.ThermalLevel.NORMAL) {
      this.levelLabel = '常温';
      this.levelColor = '#10B981';
      this.levelDesc = '设备运行正常,轻微发热属正常现象';
      this.levelAction = '保持正常使用,注意散热即可';
    } else if (level === thermal.ThermalLevel.WARM) {
      this.levelLabel = '微温';
      this.levelColor = '#F59E0B';
      this.levelDesc = '设备轻微发热,后台非关键任务可能受限';
      this.levelAction = '建议关闭不用的后台应用';
    } else if (level === thermal.ThermalLevel.HOT) {
      this.levelLabel = '发热';
      this.levelColor = '#F97316';
      this.levelDesc = '设备明显发热,非感知服务已停止,性能下调';
      this.levelAction = '建议暂停游戏或视频,让设备降温';
    } else if (level === thermal.ThermalLevel.OVERHEATED) {
      this.levelLabel = '过热';
      this.levelColor = '#EF4444';
      this.levelDesc = '设备过热,主要服务的负载已被降低';
      this.levelAction = '请停止使用高负载应用,移除保护壳';
    } else if (level === thermal.ThermalLevel.WARNING) {
      this.levelLabel = '高温警告';
      this.levelColor = '#DC2626';
      this.levelDesc = '设备即将进入紧急状态,服务已被最大程度降级';
      this.levelAction = '请立即停止使用,将设备置于阴凉处';
    } else if (level === thermal.ThermalLevel.EMERGENCY) {
      this.levelLabel = '紧急';
      this.levelColor = '#B91C1C';
      this.levelDesc = '设备已进入紧急状态,仅保留基础功能';
      this.levelAction = '设备将强制关闭非必要功能,等待降温';
    } else if (level === thermal.ThermalLevel.ESCAPE) {
      this.levelLabel = '热逃逸';
      this.levelColor = '#7F1D1D';
      this.levelDesc = '即将触发热逃逸,所有 Ability 将被强制停止';
      this.levelAction = '设备即将自我保护关闭,请立即停止使用';
    } else {
      this.levelLabel = '未知';
      this.levelColor = '#94A3B8';
      this.levelDesc = '无法获取温控等级';
      this.levelAction = '请刷新重试';
    }
  }

  private startListen(): void {
    if (this.listening) { return; }
    try {
      this.thermalCallback = (level: thermal.ThermalLevel) => {
        this.currentLevel = level;
        const prevLabel: string = this.levelLabel;
        this.updateLevelUI(level);
        this.addLog('温控变化: ' + prevLabel + ' → ' + this.levelLabel);
      };
      thermal.registerThermalLevelCallback(this.thermalCallback);
      this.listening = true;
      this.addLog('温控监听已启动');
    } catch (e) {
      this.addLog('启动温控监听失败');
    }
  }

  private stopListen(): void {
    if (!this.listening) { return; }
    try {
      thermal.unregisterThermalLevelCallback();
      this.listening = false;
      this.addLog('温控监听已停止');
    } catch (e) {
      this.addLog('停止温控监听失败');
    }
  }

  // addLog / thermalBarColor / build 省略,参见项目源码
}

权限说明

@ohos.thermal 的所有 API 均无需额外权限,属于 SystemCapability.PowerManager.ThermalManager 系统基础能力,所有鸿蒙设备默认支持。

模拟器行为说明

在模拟器上,getLevel() 通常返回 COOL(0)——模拟器没有物理温度传感器,不会触发高温降级。温控回调也不会主动触发。要验证完整的温控链路(等级变化 → 回调通知 → UI 更新),需要在真机上测试。

但 Demo 的 UI 逻辑是完全可验证的——你可以通过刷新按钮确认等级查询功能正常,监听开关的启停逻辑也能在模拟器上完整测试。

与前几篇的对比:三种异步模式

本文是鸿蒙新特性系列的第四篇。回顾前几篇,我们分别接触了三种不同的 API 调用模式:

篇次 模块 异步模式 典型特征
#1 @ohos.window Promise getLastWindow(ctx).then(w => {})
#2 @ohos.wifiManager Promise 链 .then().then().catch()
#3 @ohos.batteryInfo 同步常量 直接读取 batteryInfo.batterySOC
#4 @ohos.thermal 同步 + Callback getLevel() + registerThermalLevelCallback()

理解这三种模式的区别,对于在鸿蒙生态中高效使用各类系统 API 至关重要:

  • 同步常量 — 最简单的模式,代表"随时可用"的系统快照
  • Promise — 用于一次性异步操作,链式调用简洁优雅
  • Callback — 用于持续监听,生命周期管理是重点

总结

本文深入讲解了 HarmonyOS NEXT 中 @ohos.thermal 模块的设计思路和实战应用:

  1. 温控等级而非温度数值:系统将复杂的热状态抽象为 8 个等级,应用只需根据等级决策,无需关心具体的温度阈值。

  2. 查询 + 监听的双模式getLevel() 用于"点查询",registerThermalLevelCallback() 用于"持续监听"——两者配合覆盖了所有热感知场景。

  3. 生命周期管理:Callback 模式的核心陷阱是忘记取消注册。在 aboutToDisappear() 中清理是必须遵守的铁律。

  4. 用户体验设计:温控信息不应该冷冰冰地呈现给用户。Demo 中的颜色语义、操作建议、温控知识三个层次,将技术数据转化为用户可理解的指引。

掌握 @ohos.thermal,你就能在应用中实现智能的热感知策略——设备清凉时全力输出,设备发热时优雅降级,为用户提供既流畅又安全的体验。这也是鸿蒙系统"分布式软总线 + 统一热管理"能力的应用层体现。


Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐