holding 0.0.2
holding: ^0.0.2 copied to clipboard
Flutter 握持手感知插件,支持 HarmonyOS 与 OpenHarmony,订阅握持手与操作手状态变化,并可查询最近操作手状态。
holding #
holding 是面向 HarmonyOS/OpenHarmony Flutter 应用的握持手感知插件。插件通过
@kit.MultimodalAwarenessKit 的 motion 能力监听
holdingHandChanged 事件,并向 Dart 层提供未握持、左手、右手、双手和未识别五种状态。
- Pub 包:holding 0.0.2
- 源码仓库:oh-flutter/holding-harmony
- 问题反馈:Issues
- 开源协议:Apache License 2.0
目录 #
功能特性 #
- 订阅和取消订阅系统握持手状态变化。
- 将鸿蒙系统状态码转换为类型安全的
HoldingHandStatus枚举。 - 提供
success、fail、complete操作回调和持续的onChange状态回调。 - 使用 MethodChannel 调用原生能力,使用 EventChannel 持续传递状态事件。
- 原生监听重复订阅保护,并在取消监听或 Flutter 引擎解绑时释放资源。
- 提供可直接运行的示例应用、单元测试和完整的 OHOS 原生实现。
平台与版本 #
当前 main 分支 #
| 项目 | 当前配置 | 说明 |
|---|---|---|
| 插件版本 | 0.0.2 |
pubspec.yaml 中的当前包版本 |
| Flutter | 3.47.9-ohos-1.0.0 |
oh-3.47-dev 分支 |
| Dart | 3.13.4 |
随上述 Flutter SDK 提供 |
| HarmonyOS 产品版本 | 7.0.0 (API 26) |
当前适配目标 |
compileSdkVersion |
26.0.0 |
使用 API 26 SDK 编译 |
targetSdkVersion |
26.0.0 |
面向 API 26 行为构建 |
compatibleSdkVersion |
26.0.0 |
最低兼容 API 26(随 @ohos/flutter_ohos 最低要求) |
| 支持设备类型 | default、tablet |
示例 entry 当前配置为 phone |
HarmonyOS 7.0.0 对应 API 26。新版 DevEco/Hvigor 的工程配置使用
26.0.0 作为 compileSdkVersion 和 targetSdkVersion,不要在这两个字段中填写旧格式
7.0.0(26)。
当前只实现 ohos 平台。Android、iOS、macOS、Windows、Linux 和 Web 没有平台实现。
此外,运行设备必须提供 holdingHandChanged 系统能力;即使系统 API 版本满足要求,设备硬件或系统产品未开放该能力时也可能无法产生状态事件。
pub.dev 上的
0.0.2已包含 Flutter 3.47.9/API 26 构建配置,推荐直接使用 Pub 依赖;需要验证main分支最新改动时,再使用 Git 依赖。
安装 #
Pub 依赖 #
dependencies:
holding: ^0.0.1
Git 依赖 #
当前需要使用 Flutter 3.44.9/API 26 配置时,推荐直接依赖仓库 main:
dependencies:
holding:
git:
url: https://atomgit.com/oh-flutter/holding-harmony.git
ref: main
本地路径依赖 #
dependencies:
holding:
path: ../holding-harmony
添加依赖后执行:
flutter pub get
开发环境 #
Flutter OHOS SDK #
当前仓库使用 Flutter OHOS 的 oh-3.44.9-dev 分支:
git clone https://atomgit.com/CPF-Flutter/flutter_flutter.git \
-b oh-3.44.9-dev flutter_flutter
export PATH="$(pwd)/flutter_flutter/bin:$PATH"
flutter --version
预期 Flutter 主版本为 3.44.9,并带有 OHOS 版本后缀。CPF-Flutter 与
openharmony-tpc 仓库中该分支当前指向同一提交,也可以使用对应镜像地址。
HarmonyOS/OpenHarmony SDK #
在 DevEco Studio 的 SDK Manager 中安装 API 26 的以下组件:
- ArkTS
- JS
- Native
- Previewer
- Toolchains
然后配置 Flutter 使用本机 OpenHarmony SDK:
flutter config --ohos-sdk /path/to/OpenHarmony/Sdk
flutter doctor
应用工程的 ohos/build-profile.json5 应包含:
{
"app": {
"products": [
{
"name": "default",
"compatibleSdkVersion": "5.1.0(18)",
"compileSdkVersion": "26.0.0",
"targetSdkVersion": "26.0.0",
"runtimeOS": "HarmonyOS"
}
]
}
}
编译 API 与设备 API 是两个概念。项目使用 API 26 SDK 编译,但
compatibleSdkVersion 为 API 18,因此 API 18 及以上设备满足安装版本要求。例如 API 24
真机可以安装,前提是签名、权限及目标设备系统能力均有效。
应用权限配置 #
插件 HAR 已声明以下权限:
ohos.permission.DETECT_GESTUREohos.permission.ACTIVITY_MOTION
宿主应用仍应在 entry 模块的 src/main/module.json5 中声明权限及使用场景:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.DETECT_GESTURE",
"reason": "$string:detect_gesture_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.ACTIVITY_MOTION",
"reason": "$string:activity_motion_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
同时在 src/main/resources/base/element/string.json 中提供权限说明:
{
"string": [
{
"name": "detect_gesture_reason",
"value": "用于识别设备握持手状态"
},
{
"name": "activity_motion_reason",
"value": "用于获取活动与运动感知状态"
}
]
}
具体权限等级、授权方式及上架要求以目标 HarmonyOS 版本的官方文档为准。
快速开始 #
import 'package:flutter/material.dart';
import 'package:holding/holding.dart';
class HoldingPage extends StatefulWidget {
const HoldingPage({super.key});
@override
State<HoldingPage> createState() => _HoldingPageState();
}
class _HoldingPageState extends State<HoldingPage> {
final Holding _holding = Holding();
HoldingHandStatus _status = HoldingHandStatus.none;
bool _subscribed = false;
void _subscribe() {
_holding.subscribeHoldingHand(
SubscribeHoldingHandOptions(
onChange: (HoldingHandStatus status) {
if (!mounted) return;
setState(() => _status = status);
},
success: () {
if (!mounted) return;
setState(() => _subscribed = true);
},
fail: (String message) {
if (!mounted) return;
setState(() => _subscribed = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('订阅失败: $message')),
);
},
complete: (String message) {
debugPrint('订阅完成: $message');
},
),
);
}
void _unsubscribe() {
_holding.unsubscribeHoldingHand(
UnsubscribeHoldingHandOptions(
success: () {
if (!mounted) return;
setState(() => _subscribed = false);
},
fail: (String message) {
debugPrint('取消订阅失败: $message');
},
),
);
}
@override
void dispose() {
_holding.unsubscribeHoldingHand();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('握持手状态')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('状态: ${_status.name} (${_status.code})'),
const SizedBox(height: 12),
FilledButton(
onPressed: _subscribed ? _unsubscribe : _subscribe,
child: Text(_subscribed ? '取消订阅' : '开始订阅'),
),
],
),
),
);
}
}
页面不再需要感知能力时应调用 unsubscribeHoldingHand。建议在业务层集中管理一个订阅,
并在页面 dispose、功能关闭或账号退出等生命周期节点释放监听。
API 参考 #
Holding #
| 方法 | 返回值 | 说明 |
|---|---|---|
subscribeHoldingHand(options) |
void |
建立事件流监听,并通知原生侧订阅握持手状态 |
unsubscribeHoldingHand([options]) |
void |
通知原生侧停止监听,成功后取消 Dart 事件流订阅 |
subscribeOperatingHand(options) |
void |
建立事件流监听,并通知原生侧订阅操作手状态 |
unsubscribeOperatingHand([options]) |
void |
通知原生侧停止操作手监听,成功后取消 Dart 事件流订阅 |
getRecentOperatingHandStatus(options) |
void |
查询最近一次操作手状态,通过 onSuccess 回调返回 |
这些方法通过回调异步返回操作结果,因此方法本身返回 void。
SubscribeHoldingHandOptions #
| 属性 | 类型 | 必填 | 触发时机 |
|---|---|---|---|
onChange |
void Function(HoldingHandStatus) |
是 | 系统握持手状态变化时,可触发多次 |
success |
void Function()? |
否 | 原生订阅操作成功时触发一次 |
fail |
void Function(String)? |
否 | MethodChannel 调用或 EventChannel 发生错误时触发 |
complete |
void Function(String)? |
否 | 操作成功或失败后触发,参数为结果消息 |
UnsubscribeHoldingHandOptions #
UnsubscribeHoldingHandOptions 是 SubscribeOptionsBase 的类型别名,包含可选的
success、fail、complete 回调。取消订阅参数可以省略:
Holding().unsubscribeHoldingHand();
UnsubscribeOperatingHandOptions 与其相同,用于取消操作手订阅。
SubscribeOperatingHandOptions #
| 属性 | 类型 | 必填 | 触发时机 |
|---|---|---|---|
onChange |
void Function(OperatingHandStatus) |
是 | 系统操作手状态变化时,可触发多次 |
success / fail / complete |
同 SubscribeHoldingHandOptions |
否 | 订阅操作的结果反馈 |
GetRecentOperatingHandStatusOptions #
| 属性 | 类型 | 必填 | 触发时机 |
|---|---|---|---|
onSuccess |
void Function(OperatingHandStatus) |
是 | 查询成功时触发一次,携带最近操作手状态 |
fail |
void Function(String)? |
否 | 查询出错时触发 |
complete |
void Function(String)? |
否 | 查询成功或失败后触发 |
HoldingHandStatus #
| 枚举值 | 系统码 | 说明 |
|---|---|---|
HoldingHandStatus.none |
0 |
未握持 |
HoldingHandStatus.left |
1 |
左手握持 |
HoldingHandStatus.right |
2 |
右手握持 |
HoldingHandStatus.both |
3 |
双手握持 |
HoldingHandStatus.unknown |
16 |
未识别 |
HoldingHandStatus.code 可读取原始整数值;HoldingHandStatus.fromCode(int) 可将系统码
转换为枚举。未定义的整数值以及非整数事件都会按 unknown 处理。
OperatingHandStatus #
| 枚举值 | 系统码 | 说明 |
|---|---|---|
OperatingHandStatus.unknown |
0 |
未识别 |
OperatingHandStatus.left |
1 |
左手操作 |
OperatingHandStatus.right |
2 |
右手操作 |
对应鸿蒙 motion.OperatingHandStatus(since API 15)。code 与 fromCode(int) 的行为
与 HoldingHandStatus 一致,未定义整数值按 unknown 处理。
回调与订阅行为 #
- Dart 侧会先建立 EventChannel 监听,再通过 MethodChannel 调用原生订阅,减少首个事件丢失的可能。
- 同一平台实例只维护一个 EventChannel 订阅。再次调用
subscribeHoldingHand不会创建第二条原生监听。 - 重复订阅会更新当前
SubscribeHoldingHandOptions,后续事件交给最近一次传入的onChange。 success表示订阅或取消订阅操作完成,不表示设备已经产生握持手状态事件。- 取消订阅成功后,Dart 侧会取消事件流并清理当前回调;取消失败时会保留当前监听状态。
- Flutter 引擎解绑或 EventChannel 被取消时,OHOS 侧会调用
motion.off释放系统监听。
由于插件当前采用单例平台实现,不建议多个页面分别维护并竞争订阅。复杂应用可在状态管理层或服务层统一订阅,再将状态分发给多个页面。
工作原理 #
Flutter 页面
|
| subscribeHoldingHand / unsubscribeHoldingHand
v
Holding -> HoldingPlatform -> MethodChannel("holding")
|
v
HoldingPlugin.ets
|
| motion.on / motion.off
v
MultimodalAwarenessKit
|
| HoldingHandStatus 整数状态
v
EventChannel("holding/events") -> Dart 枚举 -> onChange
通道约定:
| 类型 | 名称 | 内容 |
|---|---|---|
| MethodChannel | holding |
subscribeHoldingHand、unsubscribeHoldingHand、subscribeOperatingHand、unsubscribeOperatingHand、getRecentOperatingHandStatus |
| EventChannel | holding/events |
鸿蒙 motion.HoldingHandStatus 整数值 |
| EventChannel | holding/operating/events |
鸿蒙 motion.OperatingHandStatus 整数值 |
运行示例 #
git clone https://atomgit.com/oh-flutter/holding-harmony.git
cd holding-harmony
flutter pub get
flutter analyze
flutter test
cd example
flutter pub get
flutter devices
flutter run -d <device-id>
也可以在 DevEco Studio 中单独打开 example/ohos。项目同步完成后配置 entry 的调试签名,
再选择目标设备运行。签名配置通常包含本机绝对路径和凭据,不应提交到公共仓库。
常见问题 #
Missing SDK components #
典型错误:
Missing SDK components. SDK path: ..., missing components:
toolchains,ets,js,native,previewer
这是电脑端 SDK 目录或组件布局问题,与连接手机的 API 版本无关。请检查:
- DevEco SDK Manager 中 API 26 的 ArkTS、JS、Native、Previewer、Toolchains 是否全部安装。
- Hvigor 使用的 SDK 根目录是否能够找到同一 API 版本的
openharmony和hms组件。 - 修改 SDK 路径后是否完整退出 DevEco Studio,并停止旧 Hvigor daemon 后重新同步。
compileSdkVersion是否使用当前 Hvigor 接受的26.0.0格式。
同步失败时 DevEco 可能暂时不显示 entry 模块,签名页面也无法选择模块。先修复 SDK 并重新同步,
不要把该现象误判为 entry 目录被删除。
手机 API 不是 26,能否运行 #
可以,只要设备 API 不低于 compatibleSdkVersion。本项目最低兼容 API 18,因此 API 24
设备满足版本条件。compileSdkVersion 只决定电脑使用哪版 SDK 编译,不要求手机必须是相同 API。
最终能否正常产生握持事件还取决于设备是否支持该系统能力。
无法安装未签名 HAP #
真机安装需要有效的 HarmonyOS 调试或发布签名。先确保项目同步成功,再在 DevEco Studio 的
File > Project Structure > Signing Configs 中为 entry 配置签名。不要提交证书私钥、口令或
指向个人目录的签名配置。
订阅成功但没有状态事件 #
依次检查:
- entry 是否声明
DETECT_GESTURE和ACTIVITY_MOTION权限。 - 应用权限是否已授权,设备是否支持握持手感知能力。
- 页面是否仍处于订阅状态,是否被其他页面重复订阅替换了
onChange。 - 使用
hilog检查HoldingPlugin是否收到holdingHandChanged回调。
取消后仍收到回调 #
确认业务生命周期中确实调用了 unsubscribeHoldingHand,并检查其 fail 回调。只有原生取消成功后,
Dart 侧才会取消 EventChannel 订阅。还应避免多个页面分别订阅同一个平台实例。
其他平台抛出 UnimplementedError #
这是预期行为。当前插件仅声明 ohos 平台,其他平台没有实现
subscribeHoldingHand 和 unsubscribeHoldingHand。
项目结构 #
holding-harmony/
├── lib/
│ ├── holding.dart # 对外入口
│ ├── holding_hand_status.dart # 状态枚举
│ ├── holding_platform_interface.dart # 平台接口与回调类型
│ └── holding_method_channel.dart # MethodChannel/EventChannel 实现
├── ohos/
│ ├── index.ets # OHOS 插件导出入口
│ └── src/main/
│ ├── ets/components/plugin/HoldingPlugin.ets
│ └── module.json5 # HAR 模块与权限声明
├── example/ # 示例 Flutter 应用及 OHOS entry 工程
├── test/ # Dart 单元测试
└── doc/ # Flutter OHOS 插件适配文档
开发文档 #
- Flutter OHOS 三方库适配系列
- Flutter OHOS 环境搭建
- 创建 OHOS 平台插件工程
- 三方库 Dart 层适配
- OHOS 原生实现与通道通信
- 适配验证与常见问题
- 完整实现教程
参与开发 #
flutter pub get
flutter analyze
flutter test
cd example
flutter pub get
flutter test
提交问题时建议附上 Flutter 版本、DevEco Studio 版本、SDK API、设备系统版本、完整错误日志和最小复现步骤。
License #
本项目基于 Apache License 2.0 开源。