Flutter 三方库 gesturesshare 从 0 到 1:鸿蒙隔空传送分享插件的开发、升级与发布实战
本文完整记录了 HarmonyOS 隔空传送分享 Flutter 三方库 gesturesshare 从零创建、双端开发、升级到 HarmonyOS 26(API 26)、真机验证,到发布到 Pub 的全过程。
包含生态价值分析、接口设计、Dart/ArkTS 双端代码对照、关键决策、踩坑复盘与发布流程,供未来其他开发者快速入门鸿蒙 Flutter 三方库开发。
一、背景与生态价值
1.1 三方库简介
gesturesshare(atomgit.com/oh-flutter/gesturesshare)是一个 HarmonyOS/OpenHarmony 专属的 Flutter 隔空传送分享插件,基于系统分享能力(@kit.ShareKit 的 harmonyShare.on('gesturesShare'))实现:用户在设备上做出隔空传送(Grab & drop,握拳抓取)手势时,应用自动分享指定的本地图片与 App Linking 链接。
提供以下核心能力:
- registerGesturesShare — 注册隔空传送分享监听,支持图片路径、App Linking、标题、描述四个参数
- unregisterGesturesShare — 注销监听,清理资源
- onShareTriggered —
Stream<GesturesShareResult>事件流,监听实际触发分享时的成功/失败结果 - coordinate(0.0.2 / API 26 新增) — 分享成功后返回目标设备的屏幕坐标(来自
SharableTarget.getInfo())
项目地址:https://atomgit.com/oh-flutter/gesturesshare
Issue 反馈:https://atomgit.com/oh-flutter/gesturesshare/issues
1.2 生态价值
为什么值得为一个系统手势专门写插件? 隔空传送是 HarmonyOS 独有的多设备交互能力(跨设备「碰一碰/抓一抓」分享生态的一部分),它带来的价值体现在三个层面:
| 层面 | 价值 |
|---|---|
| 用户体验 | 无需弹窗、无需点按钮,一个握拳手势即可把内容「隔空」传到另一台设备,是 HarmonyOS 区别于 iOS/Android 的差异化交互 |
| 应用生态 | 福卡赠送、图片分享、跨设备接力等场景可以直接复用系统级分享面板与目标选择,无需自建传输通道 |
| Flutter 鸿蒙生态 | 截至 2026 年,鸿蒙 Flutter(Flutter for OpenHarmony)三方库数量仍远少于 iOS/Android 生态,每一个系统 Kit 的插件化都在扩大「Flutter 应用上鸿蒙」的可行边界——隔空传送此前只有 uni-app(UTS)实现,Flutter 侧是空白 |
插件化沉淀的意义:将 harmonyShare.on('gesturesShare') + 窗口注册 + SharedData 构建这「三件套」封装成 Pub 包,应用侧只需两行 Dart 代码即可获得系统能力,避免每个应用重复踩 window.getLastWindow 需要 UIAbility.context、getInfo() 低版本兼容这类坑。
1.3 从 uni-app 参考实现到 Flutter 插件
业务侧已有 uni-app 的隔空传送分享能力(uni.registerGesturesShare(),UTS 实现),本插件的目标是在 Flutter for OpenHarmony 上复用相同语义:
| uni-app / UTS 语义 | Flutter 插件设计 |
|---|---|
uni.registerGesturesShare(options) + success/fail/complete | Future<void> registerGesturesShare(RegisterGesturesShareOptions),成功 resolve,失败 throw PlatformException |
| 隔空传送触发后的结果回调 | Stream<GesturesShareResult> onShareTriggered |
uni.unregisterGesturesShare() | Future<void> unregisterGesturesShare() |
这样既保留「注册/注销」的同步语义,又用 Stream 表达「触发分享」的异步事件,贴合 Flutter 习惯。
1.4 适配目标
| 维度 | 要求 |
|---|---|
| 功能一致性 | 与 uni-app 版行为对齐(注册成功文案、失败提示、页面销毁时注销) |
| Dart 层可用性 | 提供 PlatformInterface 抽象,默认 MethodChannel 实现,便于测试替换 mock |
| 平台覆盖 | 仅 OHOS 平台;其他平台调用相关接口未实现或报错 |
| 工程规范 | README / 使用说明 / CHANGELOG / API 支持表齐全,仓库地址全部使用 AtomGit |
二、适配路线图
整个开发-升级-发布过程分为六个阶段:
第 1 阶段:工程创建 ── flutter create 插件模板 + pubspec 注册 ohos 平台
第 2 阶段:双端实现 ── Dart 抽象层/MethodChannel + ArkTS 原生(Share Kit 注册分享)
第 3 阶段:示例与文档 ── example 三场景 + README/使用说明/CHANGELOG
第 4 阶段:API 26 升级 ── SDK 26.0.0 工程配置升级 + SharableTarget.getInfo() 新能力接入
第 5 阶段:真机验证 ── hvigor assembleHap + flutter run 真机运行
第 6 阶段:发布 Pub ── dry-run 校验 + git 提交 + dart pub publish
三、逐步适配过程
第 1 阶段:工程创建
使用 Flutter 命令行生成 OHOS 插件模板(需已安装 Flutter for OpenHarmony SDK,Flutter 引擎仓库:atomgit.com/oh-flutter/flutter_flutter):
flutter create --template=plugin --platforms=ohos gesturesshare
cd gesturesshare
该命令生成 ohos/ 目录的标准模板结构:
ohos/
├── index.ets # 模块入口,导出插件类
├── oh-package.json5 # HAR 包配置
├── build-profile.json5 # 构建配置
├── hvigorfile.ts
└── src/main/
├── module.json5 # HAR 模块配置
└── ets/components/plugin/
└── GesturessharePlugin.ets # 原生插件实现(核心)
关键配置文件:
index.ets(入口导出文件)
import GesturessharePlugin from './src/main/ets/components/plugin/GesturessharePlugin';
export default GesturessharePlugin;
module.json5(HAR 模块配置)
{
"module": {
"name": "gesturesshare",
"type": "har",
"deviceTypes": ["default", "tablet"]
}
}
注:
@ohos/flutter_ohos由 Flutter 引擎在构建时自动链接,无需在oh-package.json5的dependencies中显式声明。
第 2 阶段:双端实现(核心)
这是插件开发的核心工作。gesturesshare 属于 「方法调用 + 原生回调」混合型插件:Dart 主动调用原生(注册/注销,
MethodChannel+MethodCallHandler),原生在分享触发时主动回调 Dart(channel.invokeMethod('onShareTriggered', ...))。
2.1 整体架构
Dart (Flutter) OHOS (ArkTS)
─────────────────────── ───────────────────────
class Gesturesshare class GesturessharePlugin
→ GesturessharePlatform.instance implements FlutterPlugin,
→ MethodChannelGesturesshare MethodCallHandler,
MethodChannel('gesturesshare') AbilityAware
import { FlutterPlugin,
registerGesturesShare(...) ──────────→ FlutterPluginBinding, MethodCall,
MethodCallHandler, MethodChannel,
←────────────────────── invokeMethod( MethodResult, AbilityAware,
'onShareTriggered', AbilityPluginBinding
{...}) } from '@ohos/flutter_ohos'
harmonyShare.on('gesturesShare',...)
为什么实现 AbilityAware? window.getLastWindow(context) 需要 UIAbility 的 context 而非 ApplicationContext,因此插件必须实现 AbilityAware,在 onAttachedToAbility 中保存 binding.getAbility(),注册/注销时用 this.ability.context 获取当前窗口。否则会报「Get top window failed」(如 1300002)。
2.2 Dart 层设计
Dart 层分三个文件,职责清晰:
| 文件 | 职责 |
|---|---|
lib/gesturesshare.dart | 对外 API(单例转发到 Platform instance),导出数据类型 |
lib/gesturesshare_platform_interface.dart | 平台抽象(PlatformInterface + token 校验)+ 数据类 |
lib/gesturesshare_method_channel.dart | MethodChannel 实现,处理原生回调转 Stream |
数据类(gesturesshare_platform_interface.dart):
/// 隔空传送分享事件结果
class GesturesShareResult {
final bool success;
final int? errCode;
final String errMsg;
/// 分享目标设备坐标(API 26 新增,低版本或目标未提供时为 null)
final ShareTargetCoordinate? coordinate;
}
/// 注册参数
class RegisterGesturesShareOptions {
final String imagePath; // 必填
final String? appLink; // App Linking 链接
final String? title;
final String? description;
Map<String, dynamic> toMap() => {
'imagePath': imagePath,
if (appLink != null) 'appLink': appLink,
if (title != null) 'title': title,
if (description != null) 'description': description,
};
}
原生回调转 Stream(gesturesshare_method_channel.dart)——Dart 侧不关心原生是「同步回调」还是「异步事件」,统一用 Stream 暴露:
MethodChannelGesturesshare() {
methodChannel.setMethodCallHandler(_handleNativeCall);
}
Future<dynamic> _handleNativeCall(MethodCall call) async {
switch (call.method) {
case 'onShareTriggered':
final args = call.arguments as Map<dynamic, dynamic>;
ShareTargetCoordinate? coordinate;
final coordMap = args['targetCoordinate'] as Map<dynamic, dynamic>?;
if (coordMap != null) {
coordinate = ShareTargetCoordinate(
screenX: (coordMap['screenX'] as num?)?.toInt() ?? -1,
screenY: (coordMap['screenY'] as num?)?.toInt() ?? -1,
);
}
_shareTriggeredController.add(GesturesShareResult(
success: args['success'] as bool,
errCode: args['errCode'] as int?,
errMsg: args['errMsg'] as String? ?? '',
coordinate: coordinate,
));
break;
}
}
跨语言类型注意:ArkTS 端回传的整数经 StandardMethodCodec 到达 Dart 后是
num,直接as int在某些路径会抛类型错误,应使用(coordMap['screenX'] as num?)?.toInt()兜底。
2.3 ArkTS 原生实现
ohos/src/main/ets/components/plugin/GesturessharePlugin.ets 是插件的原生核心,imports 与类声明:
import {
FlutterPlugin, FlutterPluginBinding, MethodCall, MethodCallHandler,
MethodChannel, MethodResult, AbilityAware, AbilityPluginBinding,
} from '@ohos/flutter_ohos';
import { harmonyShare, systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { fileUri } from '@kit.CoreFileKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { UIAbility } from '@kit.AbilityKit';
export default class GesturessharePlugin
implements FlutterPlugin, MethodCallHandler, AbilityAware {
private channel: MethodChannel | null = null;
private ability: UIAbility | null = null;
// 隔空传送回调函数引用与当前分享参数
private gesturesShareCallback: ((target: harmonyShare.SharableTarget) => void) | null = null;
private currentImagePath: string = '';
private currentAppLink: string = '';
private currentTitle: string = '';
private currentDescription: string = '';
getUniqueClassName(): string {
return "GesturessharePlugin"; // 必须与 pubspec.yaml 的 pluginClass 一致
}
}
注册隔空传送(核心流程:保存参数 → 获取窗口 → 构造 SendCapabilityRegistry → harmonyShare.on):
private handleRegisterGesturesShare(call: MethodCall, result: MethodResult): void {
try {
const args = call.args as Map<string, Object>;
this.currentImagePath = args.get('imagePath') as string;
this.currentAppLink = (args.get('appLink') as string | undefined) ?? '';
this.currentTitle = (args.get('title') as string | undefined) ?? '';
this.currentDescription = (args.get('description') as string | undefined) ?? '';
if (this.ability == null) {
result.error('NO_ABILITY', '无法获取 Ability 上下文', null);
return;
}
window.getLastWindow(this.ability.context).then((windowStage) => {
const mainWindowID: number = windowStage.getWindowProperties().id;
// 隔空传送触发回调
this.gesturesShareCallback = (sharableTarget: harmonyShare.SharableTarget) => {
this.doShare(sharableTarget);
};
// 构造注册能力对象(绑定窗口)
const capability: harmonyShare.SendCapabilityRegistry = {
windowId: mainWindowID
};
harmonyShare.on('gesturesShare', capability, this.gesturesShareCallback);
result.success(null);
}).catch((error: BusinessError) => {
result.error(error.code.toString(), error.message ?? '获取窗口失败', null);
});
} catch (error) {
const err = error as BusinessError;
result.error(err.code.toString(), err.message ?? '注册失败', null);
}
}
ArkTS 严格模式:
call.args必须显式断言为Map<string, Object>;传给 Flutter 的参数 Map 需用new Map<string, Object>()+set(),避免any/ESObject导致编译不通过。
分享触发(原生回调里执行分享并回传结果):
private doShare(sharableTarget: harmonyShare.SharableTarget): void {
try {
let filePath = this.currentImagePath;
if (filePath.startsWith('file://')) {
filePath = filePath.substring(7);
}
const imageUri = fileUri.getUriFromPath(filePath);
// 根据是否带 App Linking 选择分享类型
let shareDataRecord: ShareDataRecord;
if (this.currentAppLink.length > 0) {
// HYPERLINK:链接 + 缩略图 + 标题/描述
shareDataRecord = {
utd: utd.UniformDataType.HYPERLINK,
content: this.currentAppLink,
thumbnailUri: imageUri,
title: this.currentTitle.length > 0 ? this.currentTitle : '分享',
description: this.currentDescription
};
} else {
// 纯图片
shareDataRecord = {
utd: utd.UniformDataType.IMAGE,
uri: imageUri,
thumbnailUri: imageUri
};
}
const shareData = new systemShare.SharedData(shareDataRecord as systemShare.SharedRecord);
sharableTarget.share(shareData);
this.notifyShareResult(true, null, '分享成功');
} catch (error) {
const err = error as BusinessError;
this.notifyShareResult(false, err.code, err.message ?? '分享失败');
}
}
注销与兜底清理:
private doUnregister(): void {
if (this.gesturesShareCallback != null && this.ability != null) {
window.getLastWindow(this.ability.context).then((windowStage) => {
const capability: harmonyShare.SendCapabilityRegistry = {
windowId: windowStage.getWindowProperties().id
};
harmonyShare.off('gesturesShare', capability);
this.gesturesShareCallback = null;
// 清空分享参数...
});
}
}
### 第 3 阶段:示例与文档
**Example 三场景设计**([example/lib/main.dart](../example/lib/main.dart)):
- **分享福卡**:模拟赠送福卡场景(固定图片 + App Linking + 标题/描述),注册后提示「请使用隔空传送手势分享」,页面 dispose 时注销
- **分享图片**:仅传 `imagePath`,验证纯图片(IMAGE 类型)分享
- **自定义参数**:完整表单(图片路径、App Linking、标题、描述)+ 运行日志,便于调试各种参数组合
**文档体系**(参考同类 OHOS 插件规范):
| 文档 | 定位 |
|------|------|
| [README.md](../README.md) | 概览与速查:安装三方式、兼容性、API/属性表(含「ohos Support」列)、实现原理 |
| [docs/使用说明.md](使用说明.md) | 面向接入方:环境要求、逐步安装、完整 Dart 示例、生命周期建议 |
| [CHANGELOG.md](../CHANGELOG.md) | Keep a Changelog 风格,按版本分段,底部带 AtomGit release 链接 |
### 第 4 阶段:升级到 HarmonyOS 26(API 26)
> 0.0.2 版本的核心工作。DevEco Studio 26.0.0 已切换到**年份版本号**(HarmonyOS 26,对应 API 26),与 `5.1.0(18)` 时代的括号版本串规则不同。
#### 4.1 Share Kit 在 API 26 的接口变化盘点
对照本机 SDK 的 `@hms.collaboration.harmonyShare.d.ts`(路径:`<DevEco>/Contents/sdk/default/hms/ets/api/`)逐一核对插件用到的接口:
| 插件用到的接口 | API 26 状态 |
|----------------|-------------|
| `harmonyShare.on('gesturesShare', capability, callback)` / `off` | ✅ 签名无变化(`@since 6.0.0(20)`,纯版本串时代继续可用) |
| `systemShare.SharedData` + `utd.UniformDataType.IMAGE/HYPERLINK` | ✅ 无变化 |
| `fileUri.getUriFromPath` | ✅ 无变化 |
| `SendCapabilityRegistry` | ✅ 无变化 |
| **`SharableTarget.getInfo()` → `SharableTargetInfo`** | 🆕 **`@since 26.0.0` 新增**:返回目标设备信息,含 `coordinate?: CoordinateInfo {screenX, screenY}` |
结论:核心注册/注销逻辑保持兼容,**升级插件的同时顺势接入 `getInfo()` 新能力**——分享成功后把目标设备屏幕坐标回传 Flutter。
#### 4.2 工程配置升级(SDK 26 版本串格式)
| 文件 | 0.0.1(API 18) | 0.0.2(API 26) |
|------|------------------|------------------|
| `example/ohos/build-profile.json5` | `"compatibleSdkVersion": "5.1.0(18)"` | `"compatibleSdkVersion": "26.0.0"` |
| `example/ohos/oh-package.json5` | `"modelVersion": "5.1.0"` | `"modelVersion": "26.0.0"` |
| `example/ohos/hvigor/hvigor-config.json5` | `"modelVersion": "5.1.0"` | `"modelVersion": "26.0.0"` |
> ⚠️ **版本串格式变化**:DevEco 26 自带 hvigor 6.26.4,其版本正则为 `newDotSeparatedVersion=/^([2-9][0-9])\.[1-9]?[0-9]\.[1-9]?[0-9]$/`——两位数大版本(26.0.0)**必须用纯版本串**,继续写 `26.0.0(26)` 会报 `api version parameter is illegal! Expected format: "17", "17.2", "17.2.3"`。
#### 4.3 接入 `getInfo()` 新能力(ArkTS 端)
```typescript
// API 26 新增:获取分享目标设备坐标信息(SharableTargetInfo.coordinate)
let coordinate: harmonyShare.CoordinateInfo | undefined = undefined;
try {
const targetInfo = sharableTarget.getInfo(); // @since 26.0.0
coordinate = targetInfo.coordinate;
} catch (err) {
// 低版本 ROM 不支持 getInfo,做静默兼容
console.warn('[GesturesShare Native] getInfo 失败(低版本 ROM 不支持)');
}
this.notifyShareResult(true, null, '分享成功', coordinate);
回传 Flutter 时通过 notifyShareResult 附加 targetCoordinate 字段({screenX, screenY}),Dart 端解析为 GesturesShareResult.coordinate(见 2.2 节)。低版本兼容策略:getInfo() 包在 try/catch 中,API 26 以下设备该字段为 null,不崩溃、不影响分享主流程。
第 5 阶段:真机验证
hdc list targets # 确认设备在线(如 2LQ0224129000383)
cd example
flutter run -d <device-id> --debug # hvigor assembleHap → 安装 → 拉起
构建成功产出 build/ohos/hap/entry-default-signed.hap,安装后即真机运行(验证要点见第六章)。
第 6 阶段:发布到 Pub(实际发布于官方 pub.dev)
# 1. 确认 pubspec.yaml 版本号与 CHANGELOG 一致(0.0.2)
# 2. 干净 git 状态(pub 要求,否则拒绝发布)
git add -A && git commit -m "feat: adapt HarmonyOS 26 (API 26) and expose share target coordinate"
git tag v0.0.2 && git push origin main v0.0.2
# 3. 预发布校验(检查包内容、依赖解析、文档完备性)
dart pub publish --dry-run
# 4. 正式发布(中国大陆网络需继承系统代理,否则依赖解析无限挂起)
PUB_HOSTED_URL=https://pub.dev \
https_proxy=http://127.0.0.1:7897 http_proxy=http://127.0.0.1:7897 \
dart pub publish --force
实际发布结果:Successfully uploaded https://pub.dev/packages/gesturesshare version 0.0.2(包页约 10 分钟内生效),应用侧即可:
dependencies:
gesturesshare: ^0.0.2
四、完整代码对照
4.1 uni-app / Android 语义 → OHOS ArkTS 对照
gesturesshare 是 OHOS 专属插件(无 Android/iOS 原生实现),代码对照以 uni-app 的 UTS 参考实现为基准:
| 维度 | uni-app (UTS 参考) | Flutter OHOS (ArkTS) |
|---|---|---|
| 生命周期 | 无(全局注册) | FlutterPlugin + AbilityAware(窗口上下文) |
| 窗口获取 | 内部封装 | window.getLastWindow(ability.context) |
| 能力注册 | harmonyShare.on('gesturesShare', capability, callback) | 同左(同一系统 Kit) |
| 分享数据 | systemShare.SharedData | 同左 + utd.UniformDataType.IMAGE/HYPERLINK |
| 文件 URI | 内部处理 | fileUri.getUriFromPath(需先剥 file:// 前缀) |
| 结果回传 | success/fail/complete 回调 | channel.invokeMethod('onShareTriggered', ...) → Dart Stream |
| 引擎卸载清理 | 无 | onDetachedFromEngine 兜底 doUnregister() |
4.2 关键 ArkTS 语法差异(Kotlin/Java 开发者速查)
| Android/Java 习惯 | ArkTS 写法 | 备注 |
|---|---|---|
import io.flutter... | import { ... } from '@ohos/flutter_ohos' | 模块化导入,引擎自动链接 |
Activity / getActivity() | UIAbility / binding.getAbility() | AbilityAware 注入 |
context 直接用 | ability.context(UIAbilityContext) | ApplicationContext 不含窗口 API |
| 匿名监听器 | 函数引用 + 成员保存 | 注销时需同一 SendCapabilityRegistry |
Map<String, Object> 随意传递 | 必须显式类型,禁 any/ESObject | ArkTS 严格模式编译不过 |
五、关键决策说明
决策 1:仅 OHOS 平台的单一包结构
gesturesshare 的能力完全依赖 HarmonyOS Share Kit,Android/iOS 无对应系统手势,做成「仅 ohos 平台」的单一包(flutter.plugin.platforms 只声明 ohos: {pluginClass: GesturessharePlugin})最简洁。维护策略:若未来出现跨平台需求(如 Android 的 Nearby Share),再演进为 federated 结构(gesturesshare_platform_interface + gesturesshare_ohos)。
决策 2:Future 注册/注销 + Stream 结果
uni-app 版用 success/fail/complete 回调,Flutter 版拆成 Future(注册结果)+ Stream(触发结果)两个通道。注册是「一次调用」语义用 Future;触发是「多次事件」语义用 Stream。契约:通道名 gesturesshare,方法名 registerGesturesShare / unregisterGesturesShare / onShareTriggered 三方(Dart 抽象层、MethodChannel 实现、ArkTS 端)完全一致。
决策 3:AbilityAware 而非硬编码窗口
窗口是注册 gesturesShare 能力的必要参数(SendCapabilityRegistry.windowId),且必须用 UIAbility.context 获取。实现 AbilityAware 由引擎注入 Ability,比「注册时要求应用传 windowId」对使用者更友好。注意:Ability 未就绪时注册会返回 NO_ABILITY 错误,应用侧应在首页 initState 后再注册。
决策 4:getInfo() 低版本静默兼容(API 26 升级策略)
SharableTarget.getInfo() 是 @since 26.0.0 新增,旧设备调用会抛异常。方案对比:
| 方案 | 优点 | 缺点 |
|---|---|---|
| try/catch 静默降级 ✅ | 不崩溃、低版本可用、无需 ROM 判断 | 低版本用户拿不到坐标(返回 null,可接受) |
| 运行时 ROM 版本判断 | 显式可控 | 增加分支复杂度,ROM 版本号与 API level 映射易错 |
维护策略:新增 API 26+ 专属能力时都沿用此模式(try/catch + 可空回传),保证插件在 API 18~26 区间持续可用。
决策 5:整数跨端统一走 num?.toInt()
ArkTS 端 coordinate.screenX 是 number,经 StandardMethodCodec 到 Dart 是 num。统一用 (x as num?)?.toInt() ?? -1 解析,规避 as int 在大整数或 int64 路径上的类型异常(SDK 26 中 Coordinates 应为整数,未知时 -1)。
六、测试与验证
测试环境(实际采集)
| 项目 | 版本 | 获取方式 |
|---|---|---|
| Flutter | 3.47.9-ohos-1.0.0(channel oh-3.47-dev) | flutter --version |
| Dart | 3.9.x(pubspec environment.sdk: ^3.9.2) | flutter --version |
| HarmonyOS SDK | 26.0.0(apiVersion 26,build 26.0.0.105) | example/ohos/build-profile.json5 + <DevEco>/Contents/sdk/default/sdk-pkg.json |
| IDE | DevEco Studio 26.0.0 | defaults read /Applications/DevEco-Studio.app/Contents/Info.plist CFBundleShortVersionString |
| 设备 | HUAWEI Mate 60 Pro(ALN-AL00),ROM 7.0.0.111(SP10C00E111R4P3),API 26 | hdc list targets + hdc shell param get const.ohos.apiversion |
| 构建工具 | hvigor 6.26.4 | <DevEco>/Contents/tools/hvigor/hvigor-ohos-plugin/package.json |
| Pub 服务器 | PUB_HOSTED_URL=https://pub.flutter-io.cn | env | grep PUB_HOSTED |
验证要点(对应 Example 三场景)
- 插件注册 —
GeneratedPluginRegistrant.ets由工具链根据pubspec.yaml的ohos配置自动生成,无需手写 - 注册流程 — 首页进入「分享福卡」,注册成功后 SnackBar 提示「请使用隔空传送手势分享」(与 uni-app 版文案对齐)
- 触发分享 — 真机上做出隔空传送手势,系统分享面板拉起,
onShareTriggered推送success: true事件,API 26 设备可读取result.coordinate - 生命周期 — 页面返回(dispose)自动调用
unregisterGesturesShare(),再次进入可重新注册 - 异常路径 — Ability 未就绪注册返回
NO_ABILITY;注销后触发手势无分享面板 - 静态检查 —
flutter analyze无 error(0.0.2 修复了 mock 平台缺实现的报错) - 构建验证 — hvigor
assembleHap产出entry-default-signed.hap,flutter run真机安装运行
Note:本机 flutter tester 存在「Can’t load Kernel binary: Invalid SDK hash」问题导致 widget 测试无法在宿主机跑通(详见第八章踩坑 4),插件级验证以真机运行为准。
七、运行效果
适配完成后在 OpenHarmony 真机上截取运行截图:
flutter screenshot -d <device-id>
# 或
hdc shell "snapshot_display -f /data/local/tmp/gs_running.jpeg"
hdc file recv /data/local/tmp/gs_running.jpeg docs/screenshots/running.jpeg
Example 首页(三场景入口)在 Mate 60 Pro(API 26)真机运行效果:

八、踩坑复盘与已知问题
踩坑复盘
本次开发 + API 26 升级过程中实际踩到的坑,以表格复盘:
| 踩坑点 | 现象 / 报错 | 根因与解法 |
|---|---|---|
| 1. SDK 26 版本串格式 | hvigor 报 00308018 api version parameter is illegal! Expected format: "17", "17.2", "17.2.3" | hvigor 6.x 对两位数大版本要求纯版本串:26.0.0,不能沿用 5.1.0(18) 式括号格式;oldVersionReg 只匹配个位数主版本 |
| 2. 签名 bundleName 不匹配 | 报 00303107 Invalid storeFile / The bundleName in app.json5 ... does not match the bundleName in the generated SigningConfigs | 调试 profile(.p7b)与 bundleName 一一绑定;grep bundle-name *.p7b 可查 profile 支持的包名。跨机器/跨项目复用调试签名时需临时对齐 bundleName,或为应用包名重新自动签名 |
| 3. 真机息屏无法拉起 | aa start 报 10106102 The device screen is locked ... unlock screen failed | 开发者模式下系统不会远程自动解锁;解锁手机后重试即可。远程唤醒 power-shell wakeup + uitest uiInput swipe 只能点亮屏幕,不能过锁屏密码 |
| 4. flutter tester 内核加载失败 | 宿主机跑 widget 测试报 Dart Error: Can't load Kernel binary: Invalid SDK hash,测试卡在 loading | Flutter OHOS fork(3.47.9-ohos)的 tester 产物与 SDK hash 校验不匹配;flutter clean + pub get 无效,属 SDK 缓存/产物问题。规避:以真机运行为验证主路径,宿主机仅跑 flutter analyze |
| 5. 测试 mock 缺实现 | flutter analyze 报 non_abstract_class_inherits_abstract_member | 平台抽象新增方法/字段后,测试里的 MockPlatformInterfaceMixin mock 类必须同步补全实现 |
| 6. Example 测试目录命名 | dry-run 警告 Rename the top-level "docs" directory to "doc" | pub 布局惯例用单数目录名;warning 不阻塞发布,可保留 docs/ 或改名 doc/ |
| 7. pub.dev 直连不可达 | PUB_HOSTED_URL=https://pub.dev dart pub publish 卡在 Resolving dependencies... 超过 5 分钟无输出(本机默认 PUB_HOSTED_URL=pub.flutter-io.cn 镜像,直连 pub.dev 被网络阻断) | shell 未继承系统代理(127.0.0.1:7897)。先 scutil --proxy 取端口并 curl -x http://127.0.0.1:<port> https://pub.dev 验证 200,再带 https_proxy=http://127.0.0.1:<port> 环境变量重试即可发布成功 |
已知问题
- 宿主机 widget 测试不可用 — 受踩坑 4 影响,
flutter test在当前 Flutter OHOS fork 上无法稳定执行;插件已提供 mock 平台,待上游修复 tester 产物后可直接跑通 - coordinate 依赖目标设备 —
SharableTargetInfo.coordinate由分享目标端提供,部分设备/场景返回 null(screenX/screenY = -1)
未来优化
- federated 化 — 若接入 Android/iOS 对等能力(如 Quick Share),拆分为
gesturesshare_platform_interface+ 平台实现包 - 双向接收 — Share Kit API 26 还有
ReceivableTarget(沙箱接收,@since 6.0.0(20))与ReceivableTargetInfo(@since 26.0.0),可扩展「隔空传送接收」能力,做成完整的双向插件 - CI 门禁 — 用 ohos-dcp-codecheck / DCP 构建单在 AtomGit 上跑
assembleHap门禁,避免 tester 缺位导致的回归盲区
九、总结
从 0 到 1 完成一个鸿蒙 Flutter 三方库并跟进大版本升级,核心路径可以概括为三步走:
1. 找对应 ── 把业务语义翻译成「Future 注册/注销 + Stream 事件」的 Flutter 惯用形态,
原生侧找到系统 Kit 的等价 API(harmonyShare.on/off + SharedData)
2. 保契约 ── 通道名、方法名、参数/回传结构三方一致;ArkTS 严格类型 + 错误码透传
3. 补缺口 ── OHOS 特有约束(AbilityAware 窗口上下文、num/toInt、低版本兼容)
用合理方案弥补,新增大版本 API 用 try/catch 静默降级
对于 gesturesshare:0.0.1 完成「隔空传送分享」全链路(创建工程 → 双端实现 → 真机验证 → 发布),0.0.2 跟进 HarmonyOS 26 升级(工程配置迁移纯版本串 + 接入 SharableTarget.getInfo() 新能力 + 真机回归)。Dart 层 API 在两次版本间保持向后兼容(coordinate 为可空新增字段),已接入应用升级插件无感。
给后续开发者的建议:升级到新 SDK 时先 grep @since <新版本> <kit>.d.ts 盘点接口变化,再改工程版本串,最后真机回归——三步都不缺,升级就不翻车。
参考文档
- gesturesshare 仓库:atomgit.com/oh-flutter/gesturesshare(Issues)
- Flutter for OpenHarmony 引擎:atomgit.com/oh-flutter/flutter_flutter
- Flutter 三方库鸿蒙适配工作流:atomgit.com/oh-flutter 组织
- Share Kit 官方文档(隔空传送 gesturesShare):developer.huawei.com/consumer/cn/doc/harmonyos-references/share-kit
- 本机接口对照:
<DevEco>/Contents/sdk/default/hms/ets/api/@hms.collaboration.harmonyShare.d.ts(SharableTargetInfo/CoordinateInfo:@since 26.0.0) - Pub 发布文档:dart.dev/tools/pub/publishing
文档与插件版本:gesturesshare 0.0.2(HarmonyOS 26 / API 26 适配),仓库 oh-flutter/gesturesshare。开源协议 Apache License 2.0。
更多推荐




所有评论(0)