AI工具 码道 推荐: https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwatomgit1&sourcead=dmzntgwatomgiths
欢迎加入CPF-Flutter 鸿蒙社区: https://atomgit.com/CPF-Flutter

pedometer 是一个 Flutter 计步插件,原生支持 Android 和 iOS,可持续返回设备累计步数,并根据步伐检测事件判断用户处于 walking 还是 stopped 状态。本文以 pedometer 4.2.0 为例,完整介绍它在 HarmonyOS 上的适配思路、工程配置、权限处理、ArkTS 实现和真机验证过程。
!NOTE
本文所说的"鸿蒙化适配"是为 Flutter 插件增加
ohos平台实现。业务侧继续使用原有 Dart API,不需要为 HarmonyOS 单独维护一套调用代码。
一、适配结果速览
| 项目 | 适配结果 |
|---|---|
| 插件名称 | pedometer |
| 插件版本 | 4.2.0 |
| Flutter | 3.44.9 OpenHarmony 版本 |
| HarmonyOS | 7.0.0(API 26) |
| 累计步数 | 已适配 SensorId.PEDOMETER |
| 步伐检测 | 已适配 SensorId.PEDOMETER_DETECTION |
| 运行权限 | ohos.permission.ACTIVITY_MOTION |
| 通信方式 | Flutter EventChannel |
| 真机验证 | 累计步数更新、walking 与 stopped 切换均通过 |
这次适配保持了插件原有的两个公开数据流:
Pedometer.stepCountStream:返回设备自最近一次系统启动以来的累计步数;Pedometer.pedestrianStatusStream:返回walking、stopped或unknown;- Android、iOS 和 HarmonyOS 共用同一套 Flutter 业务代码。
二、真机运行验证视频
下面的视频由本文配套仓库中的 example 工程在 HarmonyOS 真机上直接运行录制,不是模拟器画面,也不是后期制作的示意素材。
当前页面不支持内嵌播放,请打开下方视频链接查看。
视频文件: 点击查看或下载真机运行视频
从视频中可以看到:
Pedometer Example能够在 HarmonyOS 真机正常启动;- 累计步数从
44951更新到44965,说明计步传感器事件已通过通道传到 Flutter; - 连续产生步伐事件时,页面显示
walking; - 停止行走约 2 秒后,页面自动切换为
stopped。
为什么首次显示的是 44951,而不是 0? 这是设备从最近一次系统启动开始累计的步数,并非应用启动后的相对步数。该行为与插件原有 API 语义一致。
真机验收记录
视频可直接确认:
- 应用能够在真机启动
- Flutter 页面能够收到并更新累计步数
- 行走时能够显示
walking - 停止行走后能够显示
stopped
此外,源码检查确认插件在取消订阅和引擎分离时会调用 sensor.off,用于释放两个传感器监听。权限拒绝、传感器缺失等异常分支仍建议在正式发布前按本文第十三节的步骤逐项回归。
三、适配前先梳理插件的数据链路
pedometer 不是一次请求、一次响应的普通方法调用,而是持续监听传感器数据。因此,HarmonyOS 侧应使用 EventChannel,而不是 MethodChannel。
text
HarmonyOS 计步传感器
│
├── SensorId.PEDOMETER ──────────> step_count
│ │
└── SensorId.PEDOMETER_DETECTION ─> step_detection
│
Flutter EventChannel
│
┌───────────────────┴───────────────────┐
│ │
Stream<StepCount> Stream<PedestrianStatus>
两个通道名必须与 Dart 层完全一致:
| Dart 通道 | HarmonyOS 传感器 | 输出数据 |
|---|---|---|
step_count |
SensorId.PEDOMETER |
累计步数整数 |
step_detection |
SensorId.PEDOMETER_DETECTION |
单步检测脉冲 |
适配工作的关键不是改业务 API,而是让 HarmonyOS 原生层按照这份既有协议发送相同类型的数据。
四、声明 Flutter 插件的 OHOS 平台入口
首先在插件的 pubspec.yaml 中增加 ohos 平台声明:
yaml
flutter:
config:
enable-swift-package-manager: true
plugin:
platforms:
android:
package: com.example.pedometer
pluginClass: PedometerPlugin
ios:
pluginClass: PedometerPlugin
ohos:
pluginClass: PedometerPlugin
这一步告诉 Flutter OHOS 工具链:当前包包含一个名为 PedometerPlugin 的鸿蒙插件实现。完成声明后,插件目录需要增加以下结构:
text
ohos/
├── build-profile.json5
├── hvigorfile.ts
├── index.ets
├── oh-package.json5
└── src/main/
├── module.json5
├── ets/components/plugin/PedometerPlugin.ets
└── resources/base/element/string.json
其中 ohos 模块类型为 har,因为它会作为库被业务应用的 entry 模块依赖:
json5
{
"module": {
"name": "pedometer",
"type": "har",
"deviceTypes": [
"default",
"tablet"
],
"requestPermissions": [
{
"name": "ohos.permission.ACTIVITY_MOTION",
"reason": "$string:activity_motion_reason"
}
]
}
}
五、导出并注册插件
ohos/index.ets 是模块对 Flutter 工具链暴露的入口:
typescript
import PedometerPlugin from './src/main/ets/components/plugin/PedometerPlugin';
export default PedometerPlugin;
PedometerPlugin 实现 FlutterPlugin 接口,并提供唯一类名:
typescript
export default class PedometerPlugin implements FlutterPlugin {
getUniqueClassName(): string {
return 'PedometerPlugin';
}
onAttachedToEngine(binding: FlutterPluginBinding): void {
// 创建 EventChannel 并注册 StreamHandler。
}
onDetachedFromEngine(_binding: FlutterPluginBinding): void {
// 停止传感器监听并释放通道。
}
}
示例应用中的 EntryAbility 通过自动生成的注册器加载插件:
typescript
configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine)
}
GeneratedPluginRegistrant.ets是 Flutter 工具链生成的文件,不应手工维护,也不应作为适配代码复制到其他项目。
六、实现累计步数事件通道
先定义与 Dart 层一致的通道名,并保存通道、事件出口和监听状态:
typescript
const STEP_COUNT_CHANNEL = 'step_count';
private stepCountChannel: EventChannel | null = null;
private stepCountSink: EventSink | null = null;
private isStepCountListening: boolean = false;
private readonly stepCountCallback = (
data: sensor.PedometerResponse,
): void => {
this.stepCountSink?.success(Math.trunc(data.steps));
};
Flutter 开始监听时订阅系统传感器,取消监听时解除订阅:
typescript
this.stepCountChannel = new EventChannel(
binding.getBinaryMessenger(),
STEP_COUNT_CHANNEL,
);
this.stepCountChannel.setStreamHandler({
onListen: (_arguments: Object, events: EventSink): void => {
this.startStepCount(events);
},
onCancel: (_arguments: Object): void => {
this.stopStepCount();
},
});
核心订阅逻辑如下:
typescript
private startStepCount(events: EventSink): void {
this.stopStepCount();
if (!this.isSensorAvailable(
sensor.SensorId.PEDOMETER,
events,
'Step Count',
)) {
return;
}
this.stepCountSink = events;
try {
sensor.on(sensor.SensorId.PEDOMETER, this.stepCountCallback);
this.isStepCountListening = true;
} catch (error) {
this.stepCountSink = null;
this.reportError(events, 'Step Count', error as BusinessError);
}
}
这里有三个值得保留的细节:
- 新订阅开始前先调用
stopStepCount(),避免重复监听; - 使用
Math.trunc把系统返回值转换为 Dart 层预期的整数; - 同时保存
isStepCountListening与EventSink,便于插件销毁时正确释放资源。
七、实现步伐检测事件通道
步伐检测通道的生命周期与累计步数通道相同,但底层传感器 ID 不同:
typescript
const STEP_DETECTION_CHANNEL = 'step_detection';
private readonly stepDetectionCallback = (
data: sensor.PedometerDetectionResponse,
): void => {
this.stepDetectionSink?.success(Math.trunc(data.scalar));
};
typescript
private startStepDetection(events: EventSink): void {
this.stopStepDetection();
if (!this.isSensorAvailable(
sensor.SensorId.PEDOMETER_DETECTION,
events,
'Step Detection',
)) {
return;
}
this.stepDetectionSink = events;
try {
sensor.on(
sensor.SensorId.PEDOMETER_DETECTION,
this.stepDetectionCallback,
);
this.isStepDetectionListening = true;
} catch (error) {
this.stepDetectionSink = null;
this.reportError(events, 'Step Detection', error as BusinessError);
}
}
HarmonyOS 原生层只负责把检测脉冲送到 Flutter。walking 与 stopped 的状态转换仍沿用插件原有 Dart 逻辑:持续收到脉冲时为 walking,连续 2 秒没有新脉冲则切换为 stopped。这样可以保持 Android 与 HarmonyOS 的表现一致。
八、检查设备能力并统一错误返回
不同设备不一定同时提供累计计步和步伐检测传感器,因此不能直接假设传感器存在。订阅前应查询设备传感器列表:
typescript
private isSensorAvailable(
type: sensor.SensorId,
events: EventSink,
label: string,
): boolean {
try {
const available = sensor.getSensorListSync().some(
(item: sensor.Sensor) => item.sensorId === type,
);
if (!available) {
events.error(
'UNAVAILABLE',
`${label} is not available on this device`,
null,
);
}
return available;
} catch (error) {
this.reportError(events, label, error as BusinessError);
return false;
}
}
错误统一通过 EventSink.error 回传:
typescript
private reportError(
events: EventSink,
label: string,
error: BusinessError,
): void {
const code = error.code === 201
? 'PERMISSION_DENIED'
: 'UNAVAILABLE';
const message = error.message ?? `${label} is not available`;
hilog.error(0, TAG, `${label} error: ${error.code} ${message}`);
events.error(code, message, null);
}
| 错误码 | 含义 | 业务侧建议 |
|---|---|---|
PERMISSION_DENIED |
用户未授权运动权限 | 引导用户授权后重新订阅 |
UNAVAILABLE |
设备无对应传感器或系统调用失败 | 降级显示并停止依赖该能力 |
九、在 Dart 层识别 OHOS 平台
原插件在 Android 上会把单步检测脉冲转换为行走状态。HarmonyOS 的 PEDOMETER_DETECTION 也提供同类脉冲,因此只需让 OHOS 复用这段逻辑:
dart
import 'dart:io' show Platform;
static Stream<PedestrianStatus> get pedestrianStatusStream {
Stream<PedestrianStatus> stream = _stepDetectionChannel
.receiveBroadcastStream()
.map((event) => PedestrianStatus._(event));
if (Platform.isAndroid || Platform.operatingSystem == 'ohos') {
return _stepDetectionStream(stream);
}
return stream;
}
累计步数 API 无需增加平台分支,因为原生层已经确保发送的是整数:
dart
static Stream<StepCount> get stepCountStream => _stepCountChannel
.receiveBroadcastStream()
.map((event) => StepCount._(event));
这种做法保留了插件的公共接口,已有业务代码升级到鸿蒙适配版本后无需修改调用方式。
十、配置运动权限
计步能力属于受保护的运动数据。仅在 HAR 模块里声明权限还不够,最终应用的 entry 模块也要声明权限并在运行时向用户申请。
10.1 在 entry 模块声明权限
编辑业务应用的 ohos/entry/src/main/module.json5:
json5
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.ACTIVITY_MOTION",
"reason": "$string:activity_motion_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
然后在资源文件中补充权限说明,例如:
json
{
"string": [
{
"name": "activity_motion_reason",
"value": "用于计步和检测步行状态"
}
]
}
10.2 在 EntryAbility 动态申请权限
示例工程在 EntryAbility.onCreate 中申请权限:
typescript
private async requestActivityMotionPermission(): Promise<void> {
const permissions: Array<Permissions> = [
'ohos.permission.ACTIVITY_MOTION',
];
try {
const result = await abilityAccessCtrl.createAtManager()
.requestPermissionsFromUser(this.context, permissions);
if (result.authResults.length === 0 || result.authResults[0] !== 0) {
console.warn('ACTIVITY_MOTION permission was not granted.');
}
} catch (error) {
const businessError = error as BusinessError;
console.error(
`Failed to request ACTIVITY_MOTION permission: ` +
`${businessError.code} ${businessError.message}`,
);
}
}
!IMPORTANT
必须先完成授权,再订阅两个事件流。权限被拒绝时,不要循环弹窗;应在 Flutter 页面说明功能不可用,并允许用户稍后重新授权。
十一、配置 HarmonyOS 构建工程
示例工程使用以下 SDK 配置:
json5
{
"app": {
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.1.0(18)",
"compileSdkVersion": "26.0.0",
"targetSdkVersion": "26.0.0",
"runtimeOS": "HarmonyOS"
}
]
}
}
使用 API 26 构建时,应安装与该 SDK 匹配的 DevEco Studio,并使用它自带的 Hvigor 6.26.x 工具链。Flutter OHOS SDK 可从社区仓库获取:
bash
git clone https://atomgit.com/CPF-Flutter/flutter_flutter.git \
-b oh-3.44.9-dev
export PATH="$PWD/flutter_flutter/bin:$PATH"
flutter --version
flutter doctor -v
调试签名
使用 DevEco Studio 打开 example/ohos,在 File > Project Structure > Signing Configs 中配置本机调试签名。不要把以下信息写进文章或提交到公共仓库:
- 证书和 Profile 的本机绝对路径;
keyPassword、storePassword等口令;.p12私钥文件、.cer证书和.p7bProfile。
十二、从 AtomGit 引入适配后的插件
业务项目可在 pubspec.yaml 中直接依赖配套仓库的 main 分支:
yaml
dependencies:
flutter:
sdk: flutter
pedometer:
git:
url: https://atomgit.com/oh-flutter/OH-pedometer.git
ref: main
执行依赖解析:
bash
flutter pub get
Flutter 侧调用方式与 Android、iOS 保持一致:
dart
late Stream<StepCount> _stepCountStream;
late Stream<PedestrianStatus> _pedestrianStatusStream;
void onStepCount(StepCount event) {
final int steps = event.steps;
final DateTime timeStamp = event.timeStamp;
print('steps=$steps, time=$timeStamp');
}
void onPedestrianStatusChanged(PedestrianStatus event) {
final String status = event.status;
final DateTime timeStamp = event.timeStamp;
print('status=$status, time=$timeStamp');
}
void initPedometer() {
_stepCountStream = Pedometer.stepCountStream;
_pedestrianStatusStream = Pedometer.pedestrianStatusStream;
_stepCountStream
.listen(onStepCount)
.onError((error) => print('step count error: $error'));
_pedestrianStatusStream
.listen(onPedestrianStatusChanged)
.onError((error) => print('status error: $error'));
}
十三、构建与验证
进入示例工程后依次执行:
bash
cd example
flutter pub get
flutter analyze
flutter test
flutter build hap --debug
flutter devices
flutter run -d <device-id>
建议按下面的顺序做真机回归:
- 首次启动应用,允许运动权限;
- 记录页面初始累计步数;
- 持手机连续步行,确认步数递增并显示
walking; - 停止行走并等待至少 2 秒,确认状态变为
stopped; - 退出并重新进入页面,确认没有重复订阅或崩溃;
- 拒绝权限后重新测试,确认错误能够回传到 Flutter;
- 在不支持对应传感器的设备上验证降级提示。
点击展开:常见问题排查
1. 应用启动后一直显示问号
检查 ACTIVITY_MOTION 是否同时完成静态声明和动态授权,并确认订阅发生在授权完成之后。
2. 初始步数很大
这是累计计步传感器的正常语义。若业务需要"本次运动步数",请在开始运动时保存基准值,展示 当前累计值 - 基准值。
3. 行走停止后状态没有立刻变化
当前 Dart 实现使用 2 秒定时器判断停止状态,因此存在约 2 秒的设计延迟。
4. 真机可以运行,但模拟器没有数据
计步依赖真实运动传感器。最终验收应以真机为准,模拟器只能用于检查页面和插件注册是否正常。
5. 构建阶段提示 Hvigor 或 SDK 不兼容
确认 compileSdkVersion、targetSdkVersion、DevEco Studio 与 Hvigor 属于同一套兼容版本,不要混用旧版全局 Hvigor。
6. 提示设备不支持传感器
通过 sensor.getSensorListSync() 检查 PEDOMETER 和 PEDOMETER_DETECTION。两个能力应分别判断,业务页面也应允许只展示可用的一项。
十四、适配要点总结
本次 pedometer 鸿蒙化适配没有改变插件的 Dart 公共 API,而是在 OHOS 原生层补齐了相同的数据协议:
- 在
pubspec.yaml注册ohos插件入口; - 新建 HAR 模块并导出
PedometerPlugin; - 用两个
EventChannel分别桥接累计步数和步伐检测; - 通过
SensorId.PEDOMETER与SensorId.PEDOMETER_DETECTION订阅系统传感器; - 在应用侧声明并动态申请
ACTIVITY_MOTION权限; - 复用 Dart 层的 2 秒状态判断逻辑,维持跨平台 API 一致性;
- 使用真实 HarmonyOS 设备完成步数递增和行走状态切换验证。
最终,原有 Flutter 业务代码可以无感复用,HarmonyOS 平台也能持续获得累计步数与步行状态。
相关链接
- CPF-Flutter 鸿蒙社区: https://atomgit.com/CPF-Flutter
- Flutter OHOS SDK: https://atomgit.com/CPF-Flutter/flutter_flutter
- Pedometer 适配仓库: https://atomgit.com/oh-flutter/OH-pedometer
- Pedometer Issues: https://atomgit.com/oh-flutter/OH-pedometer/issues
- AI工具 码道: https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwatomgit1&sourcead=dmzntgwatomgiths
本文代码、配置和验证结果以配套仓库当前
main分支为准。Flutter OHOS 工具链、HarmonyOS SDK 与设备传感器能力会持续演进,升级依赖后请重新执行静态检查、构建和真机回归。