Flutter 三方库 Pedometer 的鸿蒙化适配指南

AI工具 码道 推荐: https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwatomgit1&sourcead=dmzntgwatomgiths

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

本文配套仓库: https://atomgit.com/oh-flutter/OH-pedometer

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
真机验证 累计步数更新、walkingstopped 切换均通过

这次适配保持了插件原有的两个公开数据流:

  • Pedometer.stepCountStream:返回设备自最近一次系统启动以来的累计步数;
  • Pedometer.pedestrianStatusStream:返回 walkingstoppedunknown
  • Android、iOS 和 HarmonyOS 共用同一套 Flutter 业务代码。

二、真机运行验证视频

下面的视频由本文配套仓库中的 example 工程在 HarmonyOS 真机上直接运行录制,不是模拟器画面,也不是后期制作的示意素材。
当前页面不支持内嵌播放,请打开下方视频链接查看。

视频文件: 点击查看或下载真机运行视频

从视频中可以看到:

  1. Pedometer Example 能够在 HarmonyOS 真机正常启动;
  2. 累计步数从 44951 更新到 44965,说明计步传感器事件已通过通道传到 Flutter;
  3. 连续产生步伐事件时,页面显示 walking
  4. 停止行走约 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);
  }
}

这里有三个值得保留的细节:

  1. 新订阅开始前先调用 stopStepCount(),避免重复监听;
  2. 使用 Math.trunc 把系统返回值转换为 Dart 层预期的整数;
  3. 同时保存 isStepCountListeningEventSink,便于插件销毁时正确释放资源。

七、实现步伐检测事件通道

步伐检测通道的生命周期与累计步数通道相同,但底层传感器 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。walkingstopped 的状态转换仍沿用插件原有 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 的本机绝对路径;
  • keyPasswordstorePassword 等口令;
  • .p12 私钥文件、.cer 证书和 .p7b Profile。

十二、从 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>

建议按下面的顺序做真机回归:

  1. 首次启动应用,允许运动权限;
  2. 记录页面初始累计步数;
  3. 持手机连续步行,确认步数递增并显示 walking
  4. 停止行走并等待至少 2 秒,确认状态变为 stopped
  5. 退出并重新进入页面,确认没有重复订阅或崩溃;
  6. 拒绝权限后重新测试,确认错误能够回传到 Flutter;
  7. 在不支持对应传感器的设备上验证降级提示。

点击展开:常见问题排查

1. 应用启动后一直显示问号

检查 ACTIVITY_MOTION 是否同时完成静态声明和动态授权,并确认订阅发生在授权完成之后。

2. 初始步数很大

这是累计计步传感器的正常语义。若业务需要"本次运动步数",请在开始运动时保存基准值,展示 当前累计值 - 基准值

3. 行走停止后状态没有立刻变化

当前 Dart 实现使用 2 秒定时器判断停止状态,因此存在约 2 秒的设计延迟。

4. 真机可以运行,但模拟器没有数据

计步依赖真实运动传感器。最终验收应以真机为准,模拟器只能用于检查页面和插件注册是否正常。

5. 构建阶段提示 Hvigor 或 SDK 不兼容

确认 compileSdkVersiontargetSdkVersion、DevEco Studio 与 Hvigor 属于同一套兼容版本,不要混用旧版全局 Hvigor。

6. 提示设备不支持传感器

通过 sensor.getSensorListSync() 检查 PEDOMETERPEDOMETER_DETECTION。两个能力应分别判断,业务页面也应允许只展示可用的一项。

十四、适配要点总结

本次 pedometer 鸿蒙化适配没有改变插件的 Dart 公共 API,而是在 OHOS 原生层补齐了相同的数据协议:

  1. pubspec.yaml 注册 ohos 插件入口;
  2. 新建 HAR 模块并导出 PedometerPlugin
  3. 用两个 EventChannel 分别桥接累计步数和步伐检测;
  4. 通过 SensorId.PEDOMETERSensorId.PEDOMETER_DETECTION 订阅系统传感器;
  5. 在应用侧声明并动态申请 ACTIVITY_MOTION 权限;
  6. 复用 Dart 层的 2 秒状态判断逻辑,维持跨平台 API 一致性;
  7. 使用真实 HarmonyOS 设备完成步数递增和行走状态切换验证。

最终,原有 Flutter 业务代码可以无感复用,HarmonyOS 平台也能持续获得累计步数与步行状态。


相关链接

本文代码、配置和验证结果以配套仓库当前 main 分支为准。Flutter OHOS 工具链、HarmonyOS SDK 与设备传感器能力会持续演进,升级依赖后请重新执行静态检查、构建和真机回归。

相关推荐
Android-Flutter17 小时前
flutter GetX 详解
android·flutter
AI备忘录20 小时前
(十九)华为华三锐捷迈普思科 交换机链路聚合配置命令(LACP/静态聚合五厂商对照)
运维·服务器·网络·网络协议·tcp/ip·华为
贾伟康21 小时前
【时光清单|19】HarmonyOS ArkTS 回归测试实战:覆盖启动、空数据、异常输入和重复点击
软件测试·harmonyos·arkts·回归测试·hypium
坚果的博客21 小时前
Flutter OHOS 环境搭建实战:oh-3.44.9-dev 从 0 到 1 完整记录
flutter·华为·harmonyos
坚果的博客21 小时前
认识 CPF-RN:React Native 鸿蒙官方社区与四大核心仓库
react native·react.js·harmonyos
大雷神1 天前
HarmonyOS ArkGraphics 2D可变帧率实操——同屏对比30、60和90 FPS动画
华为·harmonyos
OH_TPC1 天前
【鸿蒙优选三方库】@react-native-ohos/react-native-fast-image:高性能图片加载与缓存组件
缓存·华为·harmonyos·鸿蒙
Crazy_MT1 天前
Android Studio 运行 Flutter iOS 真机白屏,但 Xcode 和命令行正常的排查记录
flutter·android studio
贾伟康1 天前
【句匠|03】HarmonyOS ArkTS 每日打卡实战:实现连续天数、补签边界和本地日期判断
harmonyos·arkts·本地存储·每日打卡·学习应用