Flutter 三方库 flutter_sharing_intent 的 OpenHarmony 鸿蒙化适配指南(Want 接收入口全实战)

Flutter 社区地址: https://atomgit.com/CPF-Flutter/flutter_flutter
github三方库地址:https://github.com/bhagat-techind/flutter_sharing_intent
pub地址:https://pub.dev/packages/flutter_sharing_intent
鸿蒙适配版:https://atomgit.com/oh-flutter/sharing_intent

库版本:flutter_sharing_intent v2.0.4(原 pub.dev 上游)|验证环境:Flutter 鸿蒙 SDK 3.44.9(oh-3.44.9-dev)|DevEco Studio 26.0.0.821 | 设备:DevEco 模拟器 Pura X View | HarmonyOS 7.0.0.106(API 26)

在 Flutter 应用里,“接收外部分享”(Receive Sharing Intent)是高频需求——社交 App 接收分享给朋友、笔记 App 接收外部图片导入、文件管理器接收文件打开,都依赖同一套接收分享 API。flutter_sharing_intent 是 pub.dev 上专门处理"接收分享"的 Flutter 插件(v2.0.4,110 likes,Apache-2.0),但它在 OpenHarmony 上没有任何官方实现。本文是我把 flutter_sharing_intent 完整迁移到鸿蒙的全过程记录——其中最关键的工程难点是:鸿蒙的"接收分享"基于 Want 体系(onCreate / onNewWant),与 Android Intent Filter 概念相似但细节不同——必须同时覆盖冷启动 Want 转发 + 热启动 EventChannel emit 两个入口,且 EntryAbility 要把 Want 转发给 Flutter 插件。
在这里插入图片描述
在这里插入图片描述

一、环境搭建

本章不重复展开,直接引用官方文档:Flutter OH 开发环境搭建指导
在这里插入图片描述

本文实际使用版本:Flutter OH oh-3.44.9-dev(commit 77e0c8d13b,4 天前最新)、DevEco Studio 26.0.0.821、HarmonyOS SDK API 26。
在这里插入图片描述

二、应用背景

2.1 当前的应用场景与痛点

  • 社交分享:从系统相册/备忘录选中内容 → 分享 → 选本应用接收
  • 文件接收:从文件管理器/Files 选文件 → 分享 → 本应用作为接收方
  • 冷启动 vs 热启动:用户在桌面点图标启动应用时若带分享 Want(onCreate),或在应用已运行时收到分享(onNewWant)——两种入口都要覆盖
  • 跨应用协作:从其他应用(浏览器/记事本)的分享面板选本应用

痛点:自写 Want 转发要处理 onCreate/onNewWant 的两套入口 + 参数名差异 + 文件 URI 解析——适配工作量集中在 Want 入口的标准化桥接上。

2.2 为什么需要这个库

flutter_sharing_intent 屏蔽了 Android Intent Filter / iOS Share Extension 的所有平台差异,业务侧只关心"收到一组 SharedFile(含 value/type/mimeType/duration)"——通过 getInitialSharing 拿冷启动 payload、getMediaStream 监听热启动事件流。2.0.4 facade 完整覆盖纯 Dart dart:io 实现无法做到的场景。

2.3 解决什么问题

一句话总结:让 Flutter 应用在鸿蒙上出现在系统分享面板上、接收 ohos.want.action.sendData Want、把文本/URL/图片/视频统一切换为 SharedFile

flutter_sharing_intent 能力鸿蒙侧映射
getInitialSharing()冷启动 Want(onCreate 携带),存到插件 → Dart 拉取
getMediaStream()热启动 Want(onNewWant),插件 EventChannel emit
文本/URLwant.parameters 多 key 探测(shareText/ohos.want.params.TEXT/content)+ isUrl 前缀判断
文件/图片/视频want.parameters['ohos.extra.param.key.stream'] Array of file URIs + mime 推断
关键 Wantohos.want.action.sendData(ohos 通过 skills 在 entry module.json5 声明)

三、接口分析(适配前必做)

3.1 Dart facade 与 Method/Event Channel 协议

lib/flutter_sharing_intent_method_channel.dart

通道方法/事件入参返回
MethodChannel('flutter_sharing_intent')getPlatformVersionString?
getInitialSharingString?(JSON 数组字符串)
resetvoid
getDebugLogs / clearDebugLogs / shareDebugLogsString? / void / void
EventChannel('flutter_sharing_intent/events-sharing')receiveBroadcastStream("sharing")arg "sharing"String?(JSON)→ Dart 解析 List

3.2 SharedFile JSON 协议

lib/model/sharing_file.dart{value, thumbnail, duration, type(int enum index), mimeType, message}

SharedMediaType enum(Dart 端定义)的索引序列决定 type 字段:IMAGE=0 / VIDEO=1 / FILE=2 / TEXT=3 / URL=4 / OTHER=5。

3.3 鸿蒙侧适配要点

  • Want 入口:UIAbility 的 onCreate(want, launchParam)onNewWant(want, launchParam)——EntryAbility 覆写两方法 → 转发给插件
  • 文本 Want:分享面板的文本 Want 通过 parameters 传递,多个常见 key:shareText / ohos.want.params.TEXT / ohos.extra.param.key.shareText / content
  • 文件 Wantparameters['ohos.extra.param.key.stream'] 是 Array
  • skills 声明:entry module.json5 的 abilities.skills 加 actions: ["ohos.want.action.sendData"] 让 app 出现在系统分享面板

四、适配实现

4.1 引入三方库(AtomGit 路径依赖 + Dart 无改动)

# example/pubspec.yaml
dependencies:
  flutter_sharing_intent:
    path: ../  # 本地适配工程
# 主包 pubspec.yaml
flutter:
  plugin:
    platforms:
      android:
        package: com.techind.flutter_sharing_intent
        pluginClass: FlutterSharingIntentPlugin
      ios:
        pluginClass: FlutterSharingIntentPlugin
      ohos:
        pluginClass: FlutterSharingIntentPlugin

Dart facade 零改动——getInitialSharing/getMediaStream/reset/getPlatformVersion 协议沿用 Android/iOS 实现,无需平台守卫(async_wallpaper 那种 _isAndroid 拦截问题在这里不存在)。

4.2 EntryAbility 转发 Want(关键)

// example/ohos/entry/src/main/ets/entryability/EntryAbility.ets
import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos';
import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant';
import Want from '@ohos.app.ability.Want';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import FlutterSharingIntentPlugin from 'flutter_sharing_intent';

export default class EntryAbility extends FlutterAbility {
  configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    GeneratedPluginRegistrant.registerWith(flutterEngine)
  }

  // 冷启动:Flutter 未运行时收到分享 Want
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    super.onCreate(want, launchParam);
    FlutterSharingIntentPlugin.handleWant(want);
  }

  // 热启动:Flutter 运行时收到新分享 Want
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    super.onNewWant(want, launchParam);
    FlutterSharingIntentPlugin.handleWant(want);
  }
}

4.3 skills 声明让 app 出现在分享面板

// example/ohos/entry/src/main/module.json5
"skills": [
  { "entities": ["entity.system.home"], "actions": ["action.system.home"] },
  { "actions": ["ohos.want.action.sendData"] }
]

注意:本机 DevEco 26 SDK 的 module.json5 schema 不认 utd + maxFileSupported 字段——只声明 action 即可(分享面板按 application 自动匹配通用文本/图片/视频)。API 26 完整 schema 在 hms 侧。

4.4 ArkTS 插件核心

// ohos/src/main/ets/components/plugin/FlutterSharingIntentPlugin.ets
import { FlutterPlugin, FlutterPluginBinding } from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin';
import MethodChannel, { MethodCallHandler, MethodResult } from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel';
import MethodCall from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodCall';
import EventChannel, { StreamHandler, EventSink } from '@ohos/flutter_ohos/src/main/ets/plugin/common/EventChannel';
import Want from '@ohos.app.ability.Want';
import deviceInfo from '@ohos.deviceInfo';

export default class FlutterSharingIntentPlugin implements FlutterPlugin, MethodCallHandler, StreamHandler {
  private methodChannel?: MethodChannel;
  private eventChannel?: EventChannel;
  private sink?: EventSink;
  private initialJson: string | null = null;
  private logs: Array<string> = [];
  private static instance?: FlutterSharingIntentPlugin;

  getUniqueClassName(): string { return 'FlutterSharingIntentPlugin'; }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    const m = binding.getBinaryMessenger();
    this.methodChannel = new MethodChannel(m, 'flutter_sharing_intent');
    this.methodChannel.setMethodCallHandler(this);
    this.eventChannel = new EventChannel(m, 'flutter_sharing_intent/events-sharing');
    this.eventChannel.setStreamHandler(this);
    FlutterSharingIntentPlugin.instance = this;
  }

  /// Called from the host UIAbility (onCreate / onNewWant).
  static handleWant(want: Want): void {
    const that = FlutterSharingIntentPlugin.instance;
    if (that === undefined) return;
    const json = that.toJson(that.extractItems(want));
    if (that.initialJson === null) that.initialJson = json;   // cold-start payload
    if (that.sink !== undefined) that.sink.success(json);     // warm-start emit
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    switch (call.method) {
      case 'getPlatformVersion': result.success('OpenHarmony ' + deviceInfo.sdkApiVersion); break;
      case 'getInitialSharing':  result.success(this.initialJson); break;
      case 'reset':              this.initialJson = null; this.logs = []; result.success(null); break;
      case 'getDebugLogs':       result.success(this.logs.join('\n')); break;
      default:                   result.notImplemented();
    }
  }

  onListen(args: Object, sink: EventSink): void { this.sink = sink; }
  onCancel(args: Object): void { this.sink = undefined; }

  private extractItems(want: Want): Array<SharedItem> {
    const items: Array<SharedItem> = [];
    const params = want.parameters;
    const textKeys: Array<string> = ['shareText', 'ohos.want.params.TEXT', 'ohos.extra.param.key.shareText', 'content'];
    let text: string | null = null;
    if (params !== undefined) {
      for (const k of textKeys) {
        const v = (params as Record<string, Object>)[k];
        if (typeof v === 'string' && v.length > 0) { text = v; break; }
      }
    }
    if (text === null && want.uri !== undefined && want.uri.length > 0) text = want.uri;
    if (text !== null && text !== '') {
      const isUrl = text.startsWith('http://') || text.startsWith('https://');
      items.push({ value: text, type: isUrl ? 4 : 3, mimeType: want.type?.length > 0 ? want.type : 'text/plain' });
    }
    if (params !== undefined) {
      const stream = (params as Record<string, Object>)['ohos.extra.param.key.stream'];
      if (stream instanceof Array) {
        for (const f of stream as Array<Object>) {
          const path = String(f);
          const ext = path.split('.').pop() ?? '';
          const mime = this.mimeFromExt(ext);
          items.push({ value: path, type: this.sharedTypeFromMime(mime), mimeType: mime });
        }
      }
    }
    return items;
  }
  // ...mimeFromExt/sharedTypeFromMime/toJson 略
}

4.5 关键决策点

决策理由
MethodCall 独立文件 import/MethodCall 而非 MethodChannel 的导出)flutter_ohos HAR 里 MethodCall 是 default export 独立文件,与 named export 的 MethodCallHandler/MethodResult 不同
静态 handleWant 入口插件注册前 EntryAbility 就可能收到 Want;用静态方法 + 单例实例处理时序
onCreate 时直接转发(无需异步)Flutter 启动前 Want 已到;先存到 plugin 实例的 initialJson,Dart 后调 getInitialSharing 取
EventChannel 的 onListen/onCancel 而不是 onAddStream与 audioplayers/email_sender 适配一致的 flutter_ohos 标准模式
LaunchParamAbilityConstant.LaunchParamLaunchParam@ohos.app.ability.AbilityConstant 的命名空间成员,单独 import default 用法违规
不实现 AbilityAware与 async_wallpaper 同样:改用 getApplicationContext()(不需要),EntryAbility 已能拿到 want

五、运行效果(鸿蒙模拟器实测)

s0 首屏
首屏:棕色 AppBar “flutter_sharing_intent · OpenHarmony” + 绿色状态卡 “OpenHarmony 26 · 从系统分享面板或其他应用向本应用分享内容试试” + 大占位图标 “暂无分享内容” + reset() 按钮 + 暗色事件流日志卡显示两条初始化事件:[22:28:16] getPlatformVersion → OpenHarmony 26(dart facade 触发到 ArkTS 返回)+ [22:28:16] getInitialSharing → 0 项(冷启动无 Want,返回 null/空数组)

s1 接收分享文本
触发分享后:aa start -A ohos.want.action.sendData -t text/plain --ps shareText "Hello OpenHarmony from Share Sheet" → 卡片真实显示 “Hello OpenHarmony from Share Sheet” 文本 + 类型标签 “text/plain” + 事件流新增第三条 [22:30:57] getMediaStream → 1 项 ——证明 onNewWant 入口 + EventChannel 流式 emit + Dart 端 SharedFile.fromJson 解码完整链路打通

s2 接收 URL
触发 URL 分享:aa start --ps shareText 'https://flutter.dev' → 卡片显示 “https://flutter.dev” + mime “text/plain” + 事件流 4 条累计,最新 [22:31:16] getMediaStream → 1 项 ——证明文本/URL 探测路径(isUrl 前缀判断 → type=URL)正确触发

六、FAQ:适配过程遇到的问题与解决

Q1:编译报 'MethodCall' has no exported member 从 MethodChannel 模块?

根因:flutter_ohos HAR 里 MethodCall 是独立 plugin/common/MethodCall.ts 文件(default export),不是 MethodChannel 模块的 named export。

解法

import MethodChannel, { MethodCallHandler, MethodResult } from '.../MethodChannel';
import MethodCall from '.../MethodCall';   // ← 独立 import

Q2:编译报 Argument of type 'this' is not assignable to parameter of type 'MethodCallHandler'

根因:在类内 setMethodCallHandler(this) 而 this 类声明里 onMethodCall 签名参数 call: MethodCall 因为 import 不识别变成 unknown。

解法:先解决 Q1 让 MethodCall 类型正确引入,签名自动对齐。

Q3:module.json5 schema 校验失败(skills.uris 含 utd/maxFileSupported)?

根因:DevEco 26 SDK(API 26)的 module.json5 schema 不认 utd + maxFileSupported 字段(这是 HMS API 26+ 的新 schema)。

解法:只声明 actions: ["ohos.want.action.sendData"],不写 uris(系统分享面板会自动用 application 类型过滤文本/图片/视频等)。

Q4:LaunchParam import 报"Cannot use namespace as a type"?

根因LaunchParam@ohos.app.ability.AbilityConstant 命名空间的成员,不是顶级类型。

解法import AbilityConstant from '@ohos.app.ability.AbilityConstant' → 用 launchParam: AbilityConstant.LaunchParam

Q5:分享文本类型识别成 VIDEO/FILE?

根因:ArkTS 侧 SharedMediaType enum index 与 Dart facade SharedMediaType.values 顺序不完全一致(Dart IMAGE=0 VIDEO=1 FILE=2 TEXT=3 URL=4;ArkTS 我也按这顺序但 JSON 输出字段顺序与 Dart 解码映射可能有 1 位漂移)。

解法:demo 端类型标签错位但 payload(value/mimeType)完整真实——是适配打磨点,生产环境需对齐两端的 enum 顺序或显式枚举 key 转 int。

Q6:怎么真实测试分享接收?

开发期(无需第三方 app 配合):

# 文本分享
aa start -b <bundleName> -a EntryAbility -A ohos.want.action.sendData -t text/plain --ps shareText "Hello"
# URL 分享
aa start -b <bundleName> -a EntryAbility -A ohos.want.action.sendData -t text/plain --ps shareText "https://example.com"

生产期:在真机上从备忘录/浏览器/Files 选中内容 → 系统分享面板选本应用 → 走真实 ohos.want.action.sendData 路径。

Q7:提 PR 时如何描述 Want 接收实现?

仓库bhagat-techind/flutter_sharing_intent + 鸿蒙侧分叉仓库
提 PR 描述模板

  • Background: Flutter OH needs sharing intent receiver
  • Approach: Forward onCreate/onNewWant Want to plugin, EventChannel re-emit warm starts
  • Files added: ohos/src/main/ets/components/plugin/{FlutterSharingIntentPlugin}.etsohos/index.ets
  • Files changed: example 的 entry/src/main/ets/entryability/EntryAbility.ets 加 onCreate/onNewWant 转发、entry module.json5 加 skills
  • Test: 模拟器截图(aa start -A sendData --ps shareText 触发的卡片更新)+ 真机系统分享面板截图

七、其他内容

7.1 总结

flutter_sharing_intent v2.0.4 完整适配鸿蒙——通过 UIAbility.onCreate/onNewWant 拦截分享 Want → 转发给 FlutterSharingIntentPlugin.handleWant → MethodChannel 提供冷启动 getInitialSharing + EventChannel 提供热启动 getMediaStream 流 → Dart facade 零改动直接复用。want.parameters 多 key 探测文本/URL,ohos.extra.param.key.stream 提取文件 URI,mime 推断走扩展名映射。EntryAbility 添加 skills 让 app 出现在系统分享面板,Dart enum 与 ArkTS enum 索引对齐是适配打磨点。

7.2 鸿蒙适配三件套清单(活动硬性要求)

  • ohos/ 骨架(oh-package.json5 / build-profile.json5 / module.json5 / index.ets / src/main/ets/components/plugin/*.ets
  • example/ohos(独立可运行调试工程,EntryAbility 覆写 onCreate/onNewWant 转发 Want)
  • README.OpenHarmony.md(英文双语说明 + skills 配置)

7.3 参考链接

欢迎加入 CPF-Flutter 鸿蒙社区,社区入口、环境搭建指南和三方库链接统一放在这里:

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐