本文完整记录了 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/completeFuture<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~3 阶段对应仓库 0.0.1,第 4~6 阶段对应 0.0.2(API 26 升级版)。


三、逐步适配过程

第 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.dartMethodChannel 实现,处理原生回调转 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/ESObjectArkTS 严格模式编译不过

五、关键决策说明

决策 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)。


六、测试与验证

测试环境(实际采集)

项目版本获取方式
Flutter3.47.9-ohos-1.0.0(channel oh-3.47-dev)flutter --version
Dart3.9.x(pubspec environment.sdk: ^3.9.2)flutter --version
HarmonyOS SDK26.0.0(apiVersion 26,build 26.0.0.105)example/ohos/build-profile.json5 + <DevEco>/Contents/sdk/default/sdk-pkg.json
IDEDevEco Studio 26.0.0defaults read /Applications/DevEco-Studio.app/Contents/Info.plist CFBundleShortVersionString
设备HUAWEI Mate 60 Pro(ALN-AL00),ROM 7.0.0.111(SP10C00E111R4P3),API 26hdc 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.cnenv | grep PUB_HOSTED

验证要点(对应 Example 三场景)

  1. 插件注册 — GeneratedPluginRegistrant.ets 由工具链根据 pubspec.yaml 的 ohos 配置自动生成,无需手写
  2. 注册流程 — 首页进入「分享福卡」,注册成功后 SnackBar 提示「请使用隔空传送手势分享」(与 uni-app 版文案对齐)
  3. 触发分享 — 真机上做出隔空传送手势,系统分享面板拉起,onShareTriggered 推送 success: true 事件,API 26 设备可读取 result.coordinate
  4. 生命周期 — 页面返回(dispose)自动调用 unregisterGesturesShare(),再次进入可重新注册
  5. 异常路径 — Ability 未就绪注册返回 NO_ABILITY;注销后触发手势无分享面板
  6. 静态检查 — flutter analyze 无 error(0.0.2 修复了 mock 平台缺实现的报错)
  7. 构建验证 — 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,测试卡在 loadingFlutter 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> 环境变量重试即可发布成功

已知问题

  1. 宿主机 widget 测试不可用 — 受踩坑 4 影响,flutter test 在当前 Flutter OHOS fork 上无法稳定执行;插件已提供 mock 平台,待上游修复 tester 产物后可直接跑通
  2. 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 0.0.2(HarmonyOS 26 / API 26 适配),仓库 oh-flutter/gesturesshare。开源协议 Apache License 2.0。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐