Flutter for OpenHarmony 实战:三方库 environment_sensors 的鸿蒙化适配指南
环境搭建指引:https://atomgit.com/CPF-Flutter/flutter_samples/blob/master/ohos/getting-started/flutter-oh-env-setup.md
environment_sensors 把设备的四类环境传感器封成四条 Dart 流:环境温度、相对湿度、环境光、气压,另外提供一个"这个传感器到底有没有"的可用性查询。它的 0.3.0 版支持 Android、iOS,没有 OpenHarmony。
它在鸿蒙适配里暴露了一个非常本质的问题:“同一个传感器,两家平台的编号不一样”。上游 Dart 直接把安卓的 Sensor.TYPE_* 常量(13 / 12 / 5 / 6)发给原生,而鸿蒙的 sensor.SensorId 里 13 是"湿度"、8 是"气压"——照抄数字会订到完全不相干的传感器。这个坑不靠翻译表是躲不过去的。
适配对象:上游 environment_sensors 0.3.0(MIT);适配产物 TAG 0.3.0-ohos-1.0.0-beta.1。
一、这个库要解决什么
1.1 上游 API
final sensors = EnvironmentSensors();
// 可用性
final bool hasTemp = await sensors.getSensorAvailable(SensorType.AmbientTemperature);
// 四条流(拿到的是 double)
sensors.temperature.listen((v) => print('温度 $v'));
sensors.humidity.listen((v) => print('湿度 $v'));
sensors.light.listen((v) => print('光照 $v'));
sensors.pressure.listen((v) => print('气压 $v'));
1.2 契约:一条方法通道 + 四条事件通道
const _methodChannel = MethodChannel('environment_sensors/method');
const _temperatureEventChannel = EventChannel('environment_sensors/temperature');
const _humidityEventChannel = EventChannel('environment_sensors/humidity');
const _lightEventChannel = EventChannel('environment_sensors/light');
const _pressureEventChannel = EventChannel('environment_sensors/pressure');
// 注意:参数是"裸数字"(位置参数),不是 map
Future<bool> getSensorAvailable(SensorType t) {
if (t == SensorType.AmbientTemperature) return _methodChannel.invokeMethod('isSensorAvailable', 13);
if (t == SensorType.Humidity) return _methodChannel.invokeMethod('isSensorAvailable', 12);
if (t == SensorType.Light) return _methodChannel.invokeMethod('isSensorAvailable', 5);
if (t == SensorType.Pressure) return _methodChannel.invokeMethod('isSensorAvailable', 6);
return false;
}
// 四条流都用 double.parse(event.toString())
_temperatureEvents = _temperatureEventChannel.receiveBroadcastStream()
.map((event) => double.parse(event.toString()));
两个细节决定了适配的形状:
isSensorAvailable传的是安卓常量(13/12/5/6),且是位置参数;- 事件通道必须送"能
double.parse的值",鸿蒙侧要送数字(编码成 FLOAT64),不能送字符串。
Dart 层没有平台门,pubspec.yaml 只声明 android / ios。
1.3 基线
node .agents/tools/tree-diff.mjs _probe/cand14/environment_sensors _probe/es_work
# 相同: 78 内容不同: 0 仅 B 有: .gitignore / .metadata 等工程文件
上游 master(6d6ba7e)与 pub.dev 上的 0.3.0 一致。
二、选库:四道筛 + 在线查重
| 筛子 | 检查 | 结果 |
|---|---|---|
| ① pub.dev 平台列表 | 有 ohos 吗 | [android, ios] → 需要适配 |
| ② 兄弟包 | <lib>_ohos / pub.dev 上 environment_sensors_ohos | 都没有 |
| ③ Dart 平台门 | Platform.is* / defaultTargetPlatform | 无 |
| ④ 依赖体检 | dep-ohos-check.mjs environment_sensors | deps ok: -(零依赖) |
在线查重(831 个组织仓库快照精确匹配 ---- environment_sensors):干净。同轮被排除的 ambient_light(hxa-flutter 已适配)。
三、六步适配流程
- 建仓 → 2. 克隆(
_probe/es_work)→ 3. 建分支feat/ohos_environment_sensors_0.3.0并flutter create -t plugin --platforms ohos .→ 4. 写实现 → 5. 补三份文档 + 根 README + 示例改自检台 → 6. 推送并打 TAG0.3.0-ohos-1.0.0-beta.1。

四、代码写在哪个文件
ohos/src/main/ets/components/plugin/EnvironmentSensorsPlugin.ets # 本篇唯一新增的实现文件
4.1 关键点一:安卓编号 ≠ 鸿蒙编号,必须做翻译表
| 传感器 | 安卓常量 | 鸿蒙 sensor.SensorId |
|---|---|---|
| 环境温度 | 13 | AMBIENT_TEMPERATURE = 260 |
| 相对湿度 | 12 | HUMIDITY = 13 |
| 环境光 | 5 | AMBIENT_LIGHT = 5 |
| 气压 | 6 | BAROMETER = 8 |
ANDROID_TO_OHOS_SENSOR.set(13, sensor.SensorId.AMBIENT_TEMPERATURE);
ANDROID_TO_OHOS_SENSOR.set(12, sensor.SensorId.HUMIDITY);
ANDROID_TO_OHOS_SENSOR.set(5, sensor.SensorId.AMBIENT_LIGHT);
ANDROID_TO_OHOS_SENSOR.set(6, sensor.SensorId.BAROMETER);
不翻译会怎样:安卓的"温度 13"在鸿蒙是湿度,安卓的"气压 6"在鸿蒙是计步之类完全无关的传感器——订阅上去不报错,但拿到的是错的数据。这种"静默错误"最难查。
isSensorAvailable 的参数是裸数字,实现里两种形态都兼容:
const raw: Object | null = call.args;
if (typeof raw === 'number') return raw as number; // 位置参数
const map = raw as Map<string, Object>; // 兼容 {type: n}
const value = map.get?.('type');
4.2 关键点二:sensor.on/off 的每个重载都要求字面量枚举
这样写编译不过:
sensor.on(this.sensorId, (r) => …); // No overload matches this call
因为每个重载都把类型写成具体字面量(SensorId.AMBIENT_LIGHT、SensorId.BAROMETER …),传一个 SensorId 变量匹配不上任何一个。解决办法是在四个闭包里各自写死字面量,交给同一个 handler 调度:
addChannel(LIGHT_CHANNEL,
(emit) => { sensor.on(sensor.SensorId.AMBIENT_LIGHT, (r: sensor.LightResponse) => emit(r.intensity)); },
() => { sensor.off(sensor.SensorId.AMBIENT_LIGHT); });
四条流各自取自己的字段:AmbientTemperatureResponse.temperature / HumidityResponse.humidity / LightResponse.intensity / BarometerResponse.pressure。
4.3 关键点三:可用性判断就是"真的去解析一次"
鸿蒙没有 SensorManager.getDefaultSensor(type) != null 的直接写法,但 sensor.getSingleSensorSync(id) 在传感器不存在时会抛异常:
try {
const found = sensor.getSingleSensorSync(sensorId);
Log.i(TAG, `sensor found: name=${found.sensorName} id=${found.sensorId}`);
return true;
} catch (error) {
Log.w(TAG, `sensor ${sensorId} unavailable: ${(error as BusinessError).message}`);
return false;
}
4.4 事件频率与日志
系统回调频率由 sensor.on 的 options 决定(本实现不传 options),为避免 hilog 被冲爆,每 20 条才打一行日志;Dart 侧拿到的仍是全部事件(不节流)。
五、真机(模拟器)验证
示例是自检台:一个"重新查询四种传感器是否可用"按钮 + 每种传感器一个"订阅/退订"按钮,界面显示可用性与已收事件数,日志前缀 [ES-CHECK],原生日志按 EnvironmentSensorsPlugin 过滤。
| 项 | 值 |
|---|---|
| 设备 | Pura 90 模拟器,HarmonyOS 6.1.1(24) / API 24,ohos-x64 |
| 操作 | 设备侧结果 |
|---|---|
getSensorAvailable(AmbientTemperature)(安卓 13) | isSensorAvailable(android type 13) -> false;日志 sensor 260 unavailable: The sensor is not supported by the device. |
getSensorAvailable(Humidity)(安卓 12) | isSensorAvailable(android type 12) -> false |
getSensorAvailable(Light)(安卓 5) | sensor found: name=light id=5 → isSensorAvailable(android type 5) -> true |
getSensorAvailable(Pressure)(安卓 6) | isSensorAvailable(android type 6) -> false;日志 sensor 8 unavailable: …(鸿蒙气压 id 是 8) |
| 订阅环境光 | sensor listening started,随后 sensor event #1 = 0、#21 = 0、#41 = 0 …(该模拟器光感默认读数 0) |
| 注入光照后数值变化 | devecocli emulator sensor --target "Pura 90" --light-intensity 30000 之后,下一条事件变成 sensor event #81 = 30000 |
| 三个不可用的传感器 | 不产生任何事件(getSingleSensorSync 抛异常 → 界面显示"不可用") |
最后一条是本次验证里最有说服力的一步:注入 → 事件值变化,证明事件通道推的是真实传感器数据,而不是常量或缓存。
API 26(HarmonyOS 7.0.0 Beta2)上的差异(重要)
在最新模拟器 Pura X View(API 26)上复测时发现:
- 可用性判定一致:
sensor found: name=light id=5→isSensorAvailable(android type 5) -> true,其余三种为false; - 但订阅失败:
sensor.on failed code=401 message=The parameter invalid.
也就是说,同一份代码在 API 24 上能订阅成功并收到数据,在 API 26 上 sensor.on 直接报 401(参数错误)。这属于跨版本行为差异,不是"没跑通":getSingleSensorSync 证明传感器在、sensor.on 在 API 26 上需要不同/额外的参数(大概率要显式传 Options,例如采样间隔)。这一点已如实记录,修复与复试安排在下一轮(改带 Options 的调用,并在失败时保留原调用作为回退)。



验证环境说明:完整验证在 API 24(
Pura 90)完成;API 26(Pura X View,7.0.0 Beta2)上完成了可用性复测并发现上述 401 差异。该 Beta 镜像在本机存活窗口只有 20–60 秒,故采用"一窗口一应用 + 断点续传"的方式取证。
六、编译与构建踩坑
6.1 No overload matches this call(两次)
第一次是 sensor.on/off 传变量(见 4.2);修完后还有一次,根因相同——SDK 里这类"按枚举成员重载"的 API 都不接受变量。遇到这个报错,先想"是不是该把字面量写进闭包"。
6.2 示例侧
- 上游示例把
_tempAvailable用来判断湿度流、_humidityAvailable判断温度流(上游自身的笔误),鸿蒙示例改成"每种传感器各自判断、各自订阅"; flutter create生成的lib/environment_sensors_method_channel.dart、lib/environment_sensors_platform_interface.dart、test/environment_sensors_method_channel_test.dart、example/**/*.kts、example/integration_test/、ios/Classes/EnvironmentSensorsPlugin.swift等全部删除(本库不是联邦式插件,上游只有一个 Dart 文件)。
七、已知限制
- 只映射上游暴露的四种传感器,其它 id 一律返回
false; - API 26 上
sensor.on报 401(见第五节),修复前应以 API 24 为准; - 回调频率由系统决定,插件只对日志做节流,不对数据节流;
- 示例的可用性/订阅逻辑已按"每种传感器独立"重写,与上游示例的写法不同(上游存在笔误)。
八、常见问题
Q1:为什么不直接拿 Dart 传来的数字去 sensor.on?
因为两家编号体系不同(见 4.1 表格)。鸿蒙的 13 是湿度、8 是气压,直接传会订到无关传感器——不报错但数据全错,是最难查的一类问题。
Q2:isSensorAvailable 的参数为什么是裸数字?
上游 Dart 就是这么写的:invokeMethod('isSensorAvailable', 13)。第二个位置参数会被编码成数字而不是 map,实现里必须兼容这种形态。
Q3:鸿蒙有没有"列出所有可用传感器"的接口?
有(getSensorListSync),但本库只需要"某一个在不在",用 getSingleSensorSync 更直接,而且它抛异常的行为正好可以当判据。
Q4:为什么事件值要送数字而不是字符串?
上游 Dart 是 double.parse(event.toString())。送数字会编码成 FLOAT64,toString() 后形如 0.0/30000.0,能正常解析;送字符串虽也能解析,但不如数字直接、也避免精度/格式歧义。
Q5:光照为什么一直是 0?
这台模拟器的光感默认读数就是 0("漆黑"档)。用 devecocli emulator sensor --target "<实例名>" --light-intensity N 注入即可看到数值变化(实测 30000)。
Q6:其它三种传感器为什么不可用?
模拟器只虚拟了光感。可用性判定如实返回 false,正是它该有的行为——不要把"没有传感器"实现成"返回 0",那会让调用方误判。
Q7:API 26 的 401 会影响使用吗?
在 API 26 设备上目前会影响订阅(可用性查询正常)。修复方向是显式传 Options;在 API 24 上功能完整。
Q8:需要声明权限吗?
读取这些环境传感器不需要权限声明。
九、本篇用到的库
| 项 | 值 |
|---|---|
| 适配仓库 | https://atomgit.com/oh-flutter/environment_sensors |
| 上游仓库 | https://github.com/nhandrew/environment_sensors |
| 上游版本 | 0.3.0(MIT,master 6d6ba7e 与发布版逐文件一致) |
| 适配 TAG | 0.3.0-ohos-1.0.0-beta.1 |
| 适配分支 | feat/ohos_environment_sensors_0.3.0 |
| 通道 | 方法通道 environment_sensors/method;事件通道 …/temperature、…/humidity、…/light、…/pressure |
| 鸿蒙侧依赖 | @kit.SensorServiceKit(sensor.getSingleSensorSync / sensor.on / sensor.off) |
dependencies:
environment_sensors:
git:
url: https://atomgit.com/oh-flutter/environment_sensors.git
ref: 0.3.0-ohos-1.0.0-beta.1
验证环境
| 项 | 值 |
|---|---|
| Flutter for OpenHarmony SDK | 3.44.9+ohos-0.0.1-canary1(Dart 3.12.2) |
| DevEco Studio | 26.0.0.621 |
| 设备 | Pura 90 模拟器,HarmonyOS 6.1.1(24) / API 24,ohos-x64(完整验证);Pura X View,HarmonyOS 7.0.0(26.0.0) Beta2 / API 26(可用性复测) |
| 构建产物 | example/build/ohos/hap/entry-default-signed.hap |
复现命令
$env:PUB_CACHE = "E:\pub-cache"
cd _probe/es_work/example/ohos
devecocli signature generate # 首次需要;提交前清空 signingConfigs
cd ..
flutter build hap --debug --target-platform ohos-x64
hdc install -r build/ohos/hap/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.julow.environment_sensors_example
# 1) 点"重新查询四种传感器是否可用" 2) 点"环境光"订阅
devecocli emulator sensor --target "Pura 90" --light-intensity 30000 # 观察事件值 0 -> 30000
hdc shell snapshot_display -f /data/local/tmp/es.jpeg
hdc file recv /data/local/tmp/es.jpeg .
hdc shell hilog -x | Select-String "EnvironmentSensorsPlugin"
欢迎加入 CPF-Flutter 鸿蒙社区:https://atomgit.com/CPF-Flutter
更多推荐




所有评论(0)