引言

手机里藏着多少传感器?加速度计、陀螺仪、环境光传感器、接近光传感器、方向传感器、重力传感器、气压计、湿度计、霍尔传感器、心率传感器、计步器……这些微型元件构成了设备感知物理世界的"感官系统"。HarmonyOS NEXT 通过 @ohos.sensor 模块将这些传感器的数据流统一暴露给开发者,让我们可以在应用内实时获取设备周围的环境信息和运动状态。

@ohos.sensor 属于 @kit.SensorServiceKit,提供了 20 多种传感器类型的统一访问接口。最让人惊喜的是它的权限模型——光照、接近光、方向、重力、磁场、气压、湿度、霍尔、温度等传感器无需任何权限即可订阅。这意味着你可以自由地读取环境光强度来自动调节 UI 亮度、检测设备方向来适配横竖屏、监听接近事件来优化通话体验——这些都不需要用户授权。

本文将深入讲解 @ohos.sensor 的传感器订阅机制、数据响应结构、上报频率控制,并构建一个"传感器实验室"Demo,在一个页面中同时监控 4 种免权限传感器的实时数据流。

一、API 架构:基于回调的传感器数据流

1.1 核心设计理念

@ohos.sensor 的核心设计可以概括为三个字:退

  • :通过 sensor.on(type, callback, options?) 订阅指定传感器类型的数据流
  • :通过 callback 回调函数持续接收传感器事件,每个事件携带测量值和元数据
  • 退:通过 sensor.off(type, callback?) 取消订阅,释放传感器资源

这三个操作构成了完整的传感器交互闭环:先订阅感兴趣的传感器类型,在回调中处理实时数据,页面销毁时取消订阅释放资源。

1.2 SensorId 枚举——传感器类型全景图

sensor.SensorId 枚举定义了所有可用的传感器类型:

枚举值 数值 说明 需要权限
ACCELEROMETER 1 加速度传感器 ACCELEROMETER
GYROSCOPE 2 陀螺仪传感器 GYROSCOPE
AMBIENT_LIGHT 5 环境光传感器
MAGNETIC_FIELD 6 磁场传感器
BAROMETER 8 气压传感器
HALL 10 霍尔传感器
PROXIMITY 12 接近光传感器
HUMIDITY 13 湿度传感器
ORIENTATION 256 方向传感器
GRAVITY 257 重力传感器
LINEAR_ACCELEROMETER 258 线性加速度 ACCELEROMETER
ROTATION_VECTOR 259 旋转矢量传感器
AMBIENT_TEMPERATURE 260 环境温度传感器
HEART_RATE 心率传感器 READ_HEALTH_DATA
PEDOMETER 计步器传感器 ACTIVITY_MOTION

在 Demo 页面中,我们选择了 4 种既无需权限又在大多数设备上可用的传感器:环境光、接近光、方向和重力。

1.3 数据响应结构——Response 继承体系

所有传感器数据对象都继承自 Response 基类:

interface Response {
  timestamp: number;   // 事件时间戳(Unix 毫秒)
  accuracy: number;    // 数据精度等级
}

各传感器类型在此基础上扩展各自的测量字段。我们重点介绍 4 种免权限传感器的响应结构:

LightResponse(环境光)——光照强度和色温:

interface LightResponse extends Response {
  intensity: number;              // 光照强度,单位 lux
  colorTemperature?: number;      // 色温,单位 kelvin(API 12+)
  infraredLuminance?: number;     // 红外亮度(API 12+)
}

光照强度的典型值:暗室 < 10 lux,室内照明 100-500 lux,阴天户外 1000-5000 lux,晴天户外 10000-25000 lux,直射阳光 > 50000 lux。应用可以根据 intensity 值来动态调整 UI 亮度或切换深色/浅色主题。

ProximityResponse(接近光)——物体与屏幕的距离:

interface ProximityResponse extends Response {
  distance: number;   // 0 表示贴近(如打电话时耳朵靠近),> 0 表示远离
}

接近传感器最常见的应用场景是通话时检测到手机贴近耳朵后自动息屏,防止面部误触。在 Demo 中我们将其二值化为"贴近"和"远离"两种状态。

OrientationResponse(方向)——设备在三维空间中的旋转角度:

interface OrientationResponse extends Response {
  alpha: number;   // 绕 Z 轴旋转角度(方位角)
  beta: number;    // 绕 X 轴旋转角度(俯仰角)
  gamma: number;   // 绕 Y 轴旋转角度(翻转角)
}

三个角度对应欧拉角旋转,可以用于指南针、水平仪、AR 定位等场景。

GravityResponse(重力)——重力加速度在三个轴上的分量:

interface GravityResponse extends Response {
  x: number;   // 重力在 X 轴的分量(m/s²)
  y: number;   // 重力在 Y 轴的分量(m/s²)
  z: number;   // 重力在 Z 轴的分量(m/s²)
}

当手机平放在桌面上时,x≈0, y≈0, z≈-9.8。通过重力分量的变化可以判断设备的摆放姿态。

1.4 SensorFrequency——上报频率控制

sensor.on() 的第三个参数 options 可以控制传感器数据的上报频率:

interface Options {
  interval?: number | SensorFrequency;
}

type SensorFrequency = 'game' | 'ui' | 'normal';
  • 'game':最高频率,适合游戏等需要高实时性的场景
  • 'ui':中等频率,适合 UI 动画等场景
  • 'normal':普通频率,适合一般监控场景,也是默认值
  • 也可以直接指定毫秒数值,如 { interval: 200 } 表示每 200ms 上报一次

在 Demo 中我们使用 'ui' 频率,平衡实时性和性能:

sensor.on(sensor.SensorId.AMBIENT_LIGHT, (data: sensor.LightResponse) => {
  this.currentLight = data.intensity;
}, { interval: 'ui' });

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、核心 API 详解

2.1 sensor.on() —— 订阅传感器数据流

function on(
  type: SensorId,
  callback: Callback<T>,
  options?: Options
): void;

on() 接受传感器类型、回调函数和可选配置。订阅成功后,传感器数据会通过 callback 持续推送。回调函数的参数类型依赖于 type 的具体值——TypeScript 的类型系统会确保你传入正确的回调签名:

// 光照传感器 → LightResponse
sensor.on(sensor.SensorId.AMBIENT_LIGHT, (data: sensor.LightResponse) => {
  console.log('光照强度:', data.intensity, 'lux');
});

// 接近光传感器 → ProximityResponse
sensor.on(sensor.SensorId.PROXIMITY, (data: sensor.ProximityResponse) => {
  console.log('距离状态:', data.distance === 0 ? '贴近' : '远离');
});

// 方向传感器 → OrientationResponse
sensor.on(sensor.SensorId.ORIENTATION, (data: sensor.OrientationResponse) => {
  console.log('方位角:', data.alpha, '俯仰:', data.beta, '翻转:', data.gamma);
});

需要注意的是,如果设备上没有对应的传感器硬件,on() 会抛出 BusinessError 14500101(服务异常)。在实际开发中,建议使用 try-catch 包裹订阅操作,并对不支持的传感器做降级处理。

2.2 sensor.off() —— 取消订阅

function off(type: SensorId, callback?: Callback<T>): void;

off() 用于停止接收指定传感器类型的数据。如果传入了 callback,则只取消该特定回调;如果不传 callback,则取消该传感器类型的所有订阅。

这非常关键——传感器是共享的硬件资源,持续监听会消耗电量。必须在页面销毁时(aboutToDisappear)调用 off() 释放资源:

aboutToDisappear(): void {
  // 遍历所有活跃的传感器订阅,逐一取消
  for (let i = 0; i < this.channels.length; i++) {
    if (this.channels[i].active) {
      sensor.off(this.channels[i].id, this.callbacks.get(this.channels[i].id));
    }
  }
}

2.3 回调管理模式

由于 on()off() 需要传入同一个回调引用才能精确取消订阅,推荐使用 Map 来管理回调函数:

private callbacks: Map<number, Callback<Object>> = new Map();

// 订阅时保存回调引用
private subscribeSensor(id: number): void {
  const cb: Callback<Object> = (data: Object) => {
    this.onSensorData(id, data);
  };
  this.callbacks.set(id, cb);
  sensor.on(id, cb, { interval: 'ui' });
}

// 取消订阅时使用保存的回调引用
private unsubscribeSensor(id: number): void {
  const cb = this.callbacks.get(id);
  sensor.off(id, cb);
  this.callbacks.delete(id);
}

这个模式确保每个传感器的订阅和取消订阅使用完全相同的回调函数引用,避免资源泄漏。

三、权限模型深度解析

3.1 免权限传感器

以下传感器无需任何权限即可订阅:

  • 环境感知类:AMBIENT_LIGHT(光照)、AMBIENT_TEMPERATURE(温度)、BAROMETER(气压)、HUMIDITY(湿度)
  • 运动姿态类:ORIENTATION(方向)、GRAVITY(重力)、ROTATION_VECTOR(旋转矢量)
  • 状态检测类:PROXIMITY(接近)、HALL(霍尔)、MAGNETIC_FIELD(磁场)

这些传感器提供的是设备周围环境信息和自身运动状态,不属于用户隐私数据。HarmonyOS 的设计原则是:只要不涉及用户可被识别的个人数据,就开放自由访问。

3.2 需要权限的传感器

以下传感器需要声明对应权限:

传感器 所需权限 说明
ACCELEROMETER(加速度) ohos.permission.ACCELEROMETER 高频运动数据,可推测用户行为
GYROSCOPE(陀螺仪) ohos.permission.GYROSCOPE 精确旋转数据,可用于定位追踪
HEART_RATE(心率) ohos.permission.READ_HEALTH_DATA 健康数据,高度敏感
PEDOMETER(计步器) ohos.permission.ACTIVITY_MOTION 运动活动数据

3.3 权限声明方式

如需使用需要权限的传感器,在 module.json5 中声明:

{
  "requestPermissions": [
    {
      "name": "ohos.permission.ACCELEROMETER",
      "reason": "用于检测设备运动状态以提供体感交互功能",
      "usedScene": {
        "abilities": ["EntryAbility"],
        "when": "inuse"
      }
    }
  ]
}

对于 Demo 中的 4 种免权限传感器,不需要任何声明即可直接使用。

四、实战 Demo:传感器实验室

本节构建一个完整的传感器实验室,在一个页面中同时监控 4 种传感器的实时数据流。

4.1 页面设计

页面分为五个功能区域:

  1. 传感器监控:4 个传感器卡片,每个卡片显示传感器名称、图标、实时数据和订阅状态。通过 Toggle 开关独立控制每个传感器的订阅/取消订阅。

  2. 全部订阅/取消:两个快捷按钮,一键订阅全部传感器或取消全部订阅。

  3. 权限说明:展示免权限传感器列表和需要权限的传感器列表,帮助开发者理解权限模型。

  4. 数据日志:毫秒级时间戳 + 传感器名称 + 实时数据值的完整事件记录。

  5. API 概述:页面底部的知识卡片,总结核心 API 和使用要点。

4.2 核心实现

数据结构定义——SensorChannel 描述每个传感器通道:

interface SensorChannel {
  id: number;       // SensorId 枚举值
  label: string;    // 中文标签
  unit: string;     // 单位
  value: string;    // 当前值(格式化后)
  active: boolean;  // 是否已订阅
  detail: string;   // 详细数据(如方向的 beta/gamma)
}

订阅/取消订阅切换——Toggle 开关驱动:

private toggleSensor(id: number): void {
  const idx = this.getChannelIndex(id);
  const ch = this.channels[idx];
  if (ch.active) {
    this.unsubscribeSensor(id);
  } else {
    this.subscribeSensor(id);
  }
}

传感器数据路由——根据传感器类型分派到对应的格式化逻辑:

private onSensorData(id: number, data: Object): void {
  let valStr = '';
  let detailStr = '';

  if (id === sensor.SensorId.AMBIENT_LIGHT) {
    const d = data as sensor.LightResponse;
    valStr = d.intensity.toFixed(0) + ' lux';
    if (d.colorTemperature !== undefined) {
      detailStr = '色温: ' + d.colorTemperature + 'K';
    }
  } else if (id === sensor.SensorId.PROXIMITY) {
    const d = data as sensor.ProximityResponse;
    valStr = d.distance === 0 ? '贴近' : '远离(' + d.distance.toFixed(1) + 'cm)';
  } else if (id === sensor.SensorId.ORIENTATION) {
    const d = data as sensor.OrientationResponse;
    valStr = d.alpha.toFixed(1) + '°';
    detailStr = 'β: ' + d.beta.toFixed(1) + '° γ: ' + d.gamma.toFixed(1) + '°';
  } else if (id === sensor.SensorId.GRAVITY) {
    const d = data as sensor.GravityResponse;
    valStr = 'X:' + d.x.toFixed(2) + ' Y:' + d.y.toFixed(2);
    detailStr = 'Z: ' + d.z.toFixed(2);
  }

  // 更新对应的通道状态
  this.updateChannel(id, valStr, detailStr);
  this.addLog(this.getSensorLabel(id), valStr);
}

这里的关键技巧是使用 as 类型断言将 Object 类型的 data 转换为具体的传感器响应类型,然后访问特定字段。

资源释放——页面销毁时取消所有活跃订阅:

aboutToDisappear(): void {
  for (let i = 0; i < this.channels.length; i++) {
    if (this.channels[i].active) {
      sensor.off(this.channels[i].id, this.callbacks.get(this.channels[i].id));
    }
  }
}

4.3 交互方式

Demo 提供四个核心交互点:

  1. 独立订阅切换:每个传感器卡片右侧有一个 Toggle 开关,点击即可开启/关闭该传感器的数据流。开启后卡片下方显示"● 监听中"状态指示,实时数据动态更新。

  2. 一键全部订阅/取消:两个快捷按钮让用户可以一次性操作所有传感器,免去逐个点击的繁琐。

  3. 实时数据观察:开启的传感器会持续推送数据,卡片上的数值实时刷新。环境光以 lux 为单位显示,接近传感器区分"贴近"和"远离",方向传感器显示三轴欧拉角,重力传感器显示三轴加速度分量。

  4. 数据日志追踪:页面底部的日志区域记录每一次传感器事件,包含毫秒级时间戳、传感器标签和数据值。用户可以直观地看到传感器的数据变化频率。

五、ArkTS 严格模式注意事项

5.1 Callback 类型标注

sensor.on() 的回调参数类型必须是具体的传感器响应类型:

// 正确:明确指定回调参数类型
sensor.on(sensor.SensorId.AMBIENT_LIGHT, (data: sensor.LightResponse) => {
  this.intensity = data.intensity;
});

// 错误:使用 any 或 unknown
sensor.on(sensor.SensorId.AMBIENT_LIGHT, (data) => {  // arkts-no-any-unknown
  this.intensity = data.intensity;
});

5.2 数组状态更新

ArkTS 严格模式下,更新 @State 数组需要构建完整的新数组:

// 正确:构建新数组
const updated: SensorChannel[] = [];
for (let i = 0; i < this.channels.length; i++) {
  if (i === idx) {
    updated.push({ id: ch.id, label: ch.label, unit: ch.unit,
      value: valStr, active: true, detail: detailStr });
  } else {
    updated.push(this.channels[i]);
  }
}
this.channels = updated;

5.3 类型化变量先声明

ForEach 和回调中使用局部变量时,必须先声明类型:

// 正确:显式声明类型
const entry: SensorLog = { time: ts, sensor: label, value: msg };
const newLogs: SensorLog[] = [entry];
this.logs = newLogs.concat(this.logs).slice(0, 50);

六、实际应用场景

6.1 自动亮度适配 UI

private adjustUIForLight(intensity: number): void {
  if (intensity < 50) {
    // 暗光环境:启用深色主题 + 降低屏幕亮度
    this.isDarkMode = true;
    this.brightnessLevel = 0.3;
  } else if (intensity > 10000) {
    // 强光环境:提高对比度 + 增大字体
    this.isHighContrast = true;
    this.fontScale = 1.2;
  } else {
    // 正常环境:使用默认设置
    this.isDarkMode = false;
    this.isHighContrast = false;
  }
}

6.2 通话时自动息屏

sensor.on(sensor.SensorId.PROXIMITY, (data: sensor.ProximityResponse) => {
  if (data.distance === 0 && this.isInCall) {
    // 贴近耳朵,关闭屏幕
    this.screenOn = false;
  } else if (data.distance > 0 && this.isInCall) {
    // 离开耳朵,点亮屏幕
    this.screenOn = true;
  }
});

6.3 水平仪效果

sensor.on(sensor.SensorId.GRAVITY, (data: sensor.GravityResponse) => {
  // 计算设备与水平面的夹角
  const tiltAngle = Math.abs(Math.atan2(
    Math.sqrt(data.x * data.x + data.y * data.y), Math.abs(data.z)
  ) * (180 / Math.PI));
  if (tiltAngle < 5) {
    this.levelStatus = '水平';
  } else {
    this.levelStatus = '倾斜 ' + tiltAngle.toFixed(1) + '°';
  }
});

七、总结

@ohos.sensor 是 HarmonyOS NEXT 中连接应用与设备传感器硬件的桥梁。通过本文的学习,你应该已经掌握:

  1. 传感器全景:通过 SensorId 枚举访问 20 多种传感器类型,其中光照、接近光、方向、重力等 10 种无需任何权限即可订阅
  2. 订阅与取消sensor.on(type, callback, options?) 订阅数据流,sensor.off(type, callback?) 释放资源,回调管理使用 Map 存储引用
  3. 数据响应结构:各传感器类型的响应对象继承 Response 基类(timestamp + accuracy),并扩展各自的测量字段
  4. 频率控制:通过 Options.interval 配置上报频率,支持 'game' / 'ui' / 'normal' 三种预设或自定义毫秒值
  5. 权限模型:免权限传感器可直接使用,加速度/陀螺仪/心率/计步器需要声明对应权限

@ohos.sensor 的最佳使用模式可以总结为:

按需订阅,回调处理数据,页面销毁即取消,永远不要忘记 off()。

这个"订阅-处理-释放"的三步模式,覆盖了传感器使用的完整生命周期。传感器是共享硬件资源,持续监听会消耗电量。在实际开发中,建议只在需要时订阅(如页面可见时),在不需要时(如页面切后台)立即取消。将传感器数据作为应用体验增强的辅助信号,而非核心依赖——因为不是所有设备都搭载了全部传感器类型。做好降级处理,你的应用将在各种设备环境下展现出更智能、更贴心的体验品质。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐