Flutter 3.47 鸿蒙三方库适配实战:从 0 到 1 处理 environment_sensors

环境传感器在智能家居、健康监测、户外工具类应用里很常见:读室温、湿度、气压、环境光。environment_sensors 把安卓的 SensorManager 封装成四条 Stream<double>,用起来很顺手。

但这个库的鸿蒙适配有一个非常隐蔽的坑:Dart 侧写死的是安卓的传感器 type 常量,而鸿蒙的 SensorId 数值完全不同。如果直接拿数字去订阅,会把「温度」订成「湿度」、把「气压」订成「计步」——不报错,但数据全是错的。

本文以 environment_sensors 0.3.0 为例,记录这个坑的成因与解法,并在 HarmonyOS API 26 真机上验证到真实光照数据流。

适配仓库: oh-flutter/environment_sensors

适配分支: feat/ohos_environment_sensors_0.3.0

适配 TAG: 0.3.0-ohos-1.0.0-beta.1

一、先看最终效果

在这里插入图片描述

图 1:HarmonyOS API 26 真机启动自检台。四种传感器可用性一并列出:环境温度「不可用」、相对湿度「不可用」、环境光「可用,未订阅」、气压「不可用」;下方日志显示 getSensorAvailable(Light) → true,其余三项为 false。

在这里插入图片描述

图 2:点击「环境光 · Light」后,按钮文字变成「订阅中(7 条,最新 646.00)」——7 条真实光照数据,最新一条 646.00 lux。

验证点实测结果证据
Flutter OH 3.47 工程构建通过entry-default-signed.hap
安装、启动(真机)通过图 1
isSensorAvailable(13) 环境温度false(设备无此传感器)图 1
isSensorAvailable(12) 相对湿度false图 1
isSensorAvailable(5) 环境光true图 1
isSensorAvailable(6) 气压false图 1
环境光事件流7 条数据,最新 646.00 lux图 2

二、这个库原来是怎么工作的

environment_sensors 用一条方法通道 + 四条事件通道:

Dart EnvironmentSensors
  -> MethodChannel('environment_sensors/method')
       isSensorAvailable(<安卓 type id>)   // 位置参数,不是 map
  -> EventChannel('environment_sensors/temperature')  -> Stream<double>
  -> EventChannel('environment_sensors/humidity')     -> Stream<double>
  -> EventChannel('environment_sensors/light')        -> Stream<double>
  -> EventChannel('environment_sensors/pressure')     -> Stream<double>

关键在 isSensorAvailable 的参数:Dart 侧传的是安卓的 Sensor.TYPE_* 常量,而且是裸数字(位置参数),不是命名参数:

传感器安卓 Sensor.TYPE_* 常量值
环境温度13
相对湿度12
环境光5
气压6

三、实际环境

Flutter 鸿蒙环境搭建不在本文重复展开,直接参考 Flutter OH 环境搭建指南。

组件版本
Flutter OH3.47.4-ohos-1.0.4
Dart3.13.3
DevEco Studio26.0.0 Release
HarmonyOS SDKAPI 26
测试设备华为真机 6UMBB26319007180,HarmonyOS 7.0.0.105(API 26)
目标库environment_sensors 0.3.0
flutter --version
hdc list targets

四、确认上游版本并同步到 AtomGit

上游包地址是 https://github.com/nhandrew/environment_sensors,版本 0.3.0,使用 MIT License。适配前我在 AtomGit 目标组织中搜索了同名库,没有找到可以直接使用的 OHOS 实现。

git clone https://atomgit.com/oh-flutter/environment_sensors.git
cd environment_sensors
git switch feat/ohos_environment_sensors_0.3.0
git log --oneline -5

五、创建适配分支并检查工程结构

git switch -c feat/ohos_environment_sensors_0.3.0

关键目录如下:

environment_sensors/
├── lib/environment_sensors.dart                    # 上游 Dart API,一行未改
├── ohos/
│   ├── index.ets                                   # OHOS 插件导出入口
│   └── src/main/ets/components/plugin/
│       └── EnvironmentSensorsPlugin.ets            # 1 方法 + 4 事件通道实现
├── example/lib/main.dart                           # 真机验证自检台
├── example/ohos/                                   # Flutter OH 宿主工程
├── pubspec.yaml                                    # 声明 ohos 平台
└── README.OpenHarmony_CN.md                        # 鸿蒙适配说明

六、适配过程:传感器 ID 必须翻译

6.1 核心坑:安卓常量与鸿蒙 SensorId 数值不同

这是整个适配最关键的一处。看这张对照表:

传感器安卓常量值鸿蒙 SensorId鸿蒙实际数值
环境温度13AMBIENT_TEMPERATURE260
相对湿度12HUMIDITY13
环境光5AMBIENT_LIGHT5
气压6BAROMETER8

注意「13」这个数字在两边含义完全相反:安卓的 13 是环境温度,鸿蒙的 13 是相对湿度。如果直接把安卓的 13 传给 sensor.on(13, ...),你会订到湿度传感器,而代码不会报任何错——拿到的数据是湿度,却当成温度用。

同样,安卓的 6 是气压,鸿蒙的 6 是计步(PEDOMETER),订错的话会拿到步数。

所以必须建一张翻译表:

const ANDROID_TO_OHOS_SENSOR: Map<number, sensor.SensorId> = new Map();

function registerSensorIds(): void {
  ANDROID_TO_OHOS_SENSOR.set(13, sensor.SensorId.AMBIENT_TEMPERATURE);  // 安卓13 → 鸿蒙260
  ANDROID_TO_OHOS_SENSOR.set(12, sensor.SensorId.HUMIDITY);             // 安卓12 → 鸿蒙13
  ANDROID_TO_OHOS_SENSOR.set(5, sensor.SensorId.AMBIENT_LIGHT);         // 安卓5  → 鸿蒙5(恰好相同)
  ANDROID_TO_OHOS_SENSOR.set(6, sensor.SensorId.BAROMETER);             // 安卓6  → 鸿蒙8
}

只有环境光(5)两边恰好相同,其余三个全部错位。

6.2 sensor.on 的每个重载都要求字面量枚举

这是第二个坑,属于 TypeScript 层面的限制:

// ❌ 这样写会报错:No overload matches this call
const id: sensor.SensorId = sensor.SensorId.AMBIENT_LIGHT;
sensor.on(id, callback);

// ✅ 必须写死字面量
sensor.on(sensor.SensorId.AMBIENT_LIGHT, callback);

因为 sensor.on 的每个传感器重载签名不同(回调参数类型不一样:LightResponse / HumidityResponse / BarometerResponse…),传变量会让类型推断失效。所以实现里四个传感器各自在闭包里写死字面量:

addChannel(LIGHT_CHANNEL,
  (emit: (value: number) => void): void => {
    sensor.on(sensor.SensorId.AMBIENT_LIGHT,
      (response: sensor.LightResponse): void => emit(response.intensity));
  },
  (): void => {
    sensor.off(sensor.SensorId.AMBIENT_LIGHT);
  });

6.3 isSensorAvailable 的参数是位置参数

上游传的是裸数字,不是 {type: n}。所以要兼容两种形态:

private static readTypeArgument(call: MethodCall): number {
  const raw: Object | null = call.args;
  if (typeof raw === 'number') {
    return raw as number;        // 上游实际用法:裸数字
  }
  if (raw !== null && raw !== undefined) {
    const map = raw as Record<string, Object>;
    const type = map['type'];
    if (typeof type === 'number') {
      return type as number;     // 兼容 {type: n} 形态
    }
  }
  return -1;
}

6.4 可用性判断用 getSingleSensorSync

private static checkAvailable(androidType: number): boolean {
  const id = ANDROID_TO_OHOS_SENSOR.get(androidType);
  if (id === undefined) {
    return false;
  }
  try {
    // 传感器不存在时会抛异常
    sensor.getSingleSensorSync(id);
    return true;
  } catch (error) {
    return false;
  }
}
方法OpenHarmony 实现
isSensorAvailable(安卓type)翻译成鸿蒙 SensorId → getSingleSensorSync 探测
温度流sensor.on(AMBIENT_TEMPERATURE) → response.temperature
湿度流sensor.on(HUMIDITY) → response.humidity
光照流sensor.on(AMBIENT_LIGHT) → response.intensity
气压流sensor.on(BAROMETER) → response.pressure

七、构建配置与签名

同批库共有的两个坑。

7.1 compatibleSdkVersion 必须写 "26.0.0"

00306004 Specification Limit Violation
The project's compatibleSdkVersion: 18 cannot be lower than
the minimum compatible version 24 required by the dependencies: @ohos/flutter_ohos.

正确写法是不带括号的 "26.0.0"。

7.2 签名证书绑定了 bundleName

00303074 Configuration Error
The bundleName in app.json5/hvigorfile.ts does not match the bundleName
in the generated SigningConfigs.

本库示例原本是 com.julow.environment_sensors_example,改成证书对应的 com.example.flutter_oh_demo 后通过。

八、通过 AtomGit 引入适配版

dependencies:
  environment_sensors:
    git:
      url: https://atomgit.com/oh-flutter/environment_sensors.git
      ref: 0.3.0-ohos-1.0.0-beta.1
flutter pub get

本库读取环境传感器不需要申请权限——环境光、温湿度、气压都属于普通传感器,sensor.on 直接可用。

九、构建、安装和真机验证

cd example
flutter pub get
flutter analyze
flutter build hap --debug
hdc install -r build/ohos/hap/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.example.flutter_oh_demo

真机结果(图 1、图 2):

传感器isSensorAvailable实际订阅结果
环境温度(安卓 13)false设备无此传感器
相对湿度(安卓 12)false设备无此传感器
环境光(安卓 5)true订阅中,已收 7 条,最新 646.00 lux
气压(安卓 6)false设备无此传感器

这个结果本身就是映射表生效的证明。 如果翻译表写错了(比如把安卓 5 直接当鸿蒙 5 用,虽然恰好对;或者把安卓 13 直接传给鸿蒙),那么「环境光可用」这个判断结果就会不同——要么全 false,要么订到别的传感器上。

而订阅后收到 7 条真实数据、最新值 646.00 lux,说明 sensor.on(AMBIENT_LIGHT) 的数据回调链路完全打通,response.intensity 的字段名也取对了(取错字段会得到 undefined 而不是数字)。

补充说明:测试设备是手机,只配备了环境光传感器,没有温湿度和气压传感器——所以另外三项返回 false 是设备能力的真实反映,不是实现缺陷。这类「不可用」恰恰说明 getSingleSensorSync 探测是有效的:如果实现里无脑返回 true,这三项就会假报为可用。

十、适配结果和当前边界

1 个方法 + 4 条事件通道全部接通,Dart 层一行未改。

已知边界:

  • 设备能力决定可用项:手机上通常只有环境光,温湿度/气压多在 IoT 设备上。返回 false 是真实设备状态;
  • 传感器 ID 必须走翻译表:安卓 Sensor.TYPE_* 与鸿蒙 SensorId 数值不通用,13 和 6 在两边含义不同,这是必须做翻译的根本原因;
  • sensor.on 要求字面量:无法用变量传 SensorId,四个传感器需分别写死;
  • 采样频率未暴露:上游没有频率参数,实现用系统默认采样率,未做定制。

十一、FAQ

Q1:为什么不能直接把安卓的传感器 ID 传给鸿蒙?

  • 现象: 安卓的 13 是环境温度,鸿蒙的 13 是相对湿度;安卓的 6 是气压,鸿蒙的 6 是计步。
  • 解决方法: 建 ANDROID_TO_OHOS_SENSOR 翻译表,把安卓常量映射到鸿蒙 SensorId。
  • 验证结果: 环境光(两边都是 5)正常出数;其余三项返回 false(设备确实没有)。

Q2:为什么温湿度、气压都显示「不可用」?

  • 现象: 四个传感器只有环境光可用。
  • 解决方法: 这是设备能力差异——手机只有环境光,温湿度/气压需要 IoT 设备。用 sensor.getSingleSensorSync 探测,不存在就返回 false。
  • 验证结果: 与环境光真实出数形成对照,证明探测逻辑有效。

Q3:sensor.on 为什么不能传变量?

  • 现象: const id = sensor.SensorId.AMBIENT_LIGHT; sensor.on(id, cb) 报 No overload matches this call。
  • 解决方法: 每个传感器重载的回调类型不同,必须写死字面量让编译器选中正确重载。
  • 验证结果: 四个传感器各自写死字面量后编译通过、运行正常。

Q4:光照值 646.00 是什么单位?

  • 现象: 界面显示 最新 646.00。
  • 解决方法: 鸿蒙 LightResponse.intensity 单位是 lux(勒克斯),646 lux 大致相当于室内明亮环境。
  • 验证结果: 数值随遮挡/照射变化,说明是真实传感器读数。

十二、总结

environment_sensors 0.3.0 已完整接入鸿蒙:1 个可用性方法 + 4 条事件流全部接通,核心是解决了安卓传感器常量与鸿蒙 SensorId 数值错位的问题(13 和 6 在两个平台含义完全不同)。真机上环境光订阅收到 7 条数据、最新 646.00 lux,另外三项如实返回不可用(设备确实没有)。

这个库最值得记的一条经验是:跨平台适配里「数字常量」比「字符串」危险得多。字符串传错通常立刻报错,数字传错往往静默返回错误的数据——你订到了湿度却以为是温度,代码不报错,业务逻辑全错。所以看到上游传裸数字做类型标识时,第一件事就是查两边常量定义,确认是否真的可以通用。

十三、参考资料和仓库地址

欢迎加入CPF-Flutter 鸿蒙社区:https://atomgit.com/CPF-Flutter

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐