Flutter share_plus 库在鸿蒙 OHOS 平台的分享功能适配实践

引言

最近 OpenHarmony(OHOS)生态进展挺快,不少 Flutter 开发者开始考虑把应用往鸿蒙平台上迁移,希望能覆盖更多的用户。Flutter 本身凭借高效的渲染和丰富的三方库,做跨平台开发确实很方便。不过,一旦涉及调用系统原生能力——比如分享、传感器或文件操作——那些在 Android/iOS 上好好的插件,在 OHOS 上就可能直接“罢工”了。

share_plus 是 Flutter 里一个特别常用的分享插件,它封装了 Android(用 Intent)和 iOS(用 UIActivityViewController)的系统分享接口,用起来一套代码就行。但到了 OHOS 上,分享机制是基于 ArkUI 的 Ability 和 Want 实现的,跟 Android/iOS 底层完全不同,所以原版插件根本跑不起来。

这篇文章就想跟大家聊聊,怎么给 share_plus 这类强依赖原生能力的插件做鸿蒙适配。我会从原理开始,一直写到具体的代码实现,中间也会聊到一些性能优化和实际调试的经验。最终目标是提供一个能直接参考、甚至复用的适配方案,帮大家更顺利地把 Flutter 生态带到鸿蒙上。


一、 理解适配背景:Flutter 插件如何与鸿蒙通信

1.1 Flutter 插件的通信原理

Flutter 和原生平台(Android/iOS)打交道,主要靠平台通道(Platform Channel)。流程其实挺直观:

  1. Flutter 侧通过 MethodChannel 调用某个方法,并把参数传过去。
  2. 原生侧(Android 或 iOS)提前注册好一个处理器,收到调用后,解析参数。
  3. 调用原生 API,完成实际功能,比如调起系统分享面板。
  4. 结果回传,通过同一条通道把成功或错误信息异步返回给 Flutter。

1.2 鸿蒙的分享是怎么做的

OpenHarmony 的系统分享围绕 AbilityWant 两个核心概念展开:

  • Want 可以理解为“意图”的载体,里面包含操作类型、数据 URI、数据类型等信息。在分享场景里,Want 就用来描述“我要分享”这个动作,以及要分享的数据是什么。
  • Ability 是对应用能力的抽象。分享时,我们通过 StartAbility 接口,传入一个配置好分享意图(action: "ohos.want.action.sendData")的 Want,系统就会帮我们调起分享界面。
  • DataAbilityHelper 主要用于处理数据(比如文件)的访问。如果你想分享文件,通常得先把文件存到应用沙箱里,然后通过这个 Helper 获得一个合法的 URI 给 Want 用。

1.3 我们的适配思路

直接去改 share_plus 官方仓库不太现实,以后升级维护会很麻烦。比较合适的做法是:单独做一个 OHOS 专属的平台实现

我们打算创建一个叫 share_plus_ohos 的新插件,结构大致这样:

  • lib/share_plus_ohos.dart:提供和 share_plus 一样的 Dart API,内部走 MethodChannel 调用原生代码。
  • ohos/:放鸿蒙的原生实现,包括 Ability、分享处理器等等。
  • pubspec.yaml:声明插件依赖和原生代码路径。

这样既保持了和原 API 的兼容,又把鸿蒙相关的代码独立出来,后续维护起来清晰很多。


二、 动手实现:从 Dart 接口到鸿蒙原生代码

2.1 Dart 层:保持 API 一致

首先在 Dart 侧定义接口,尽量让开发者无感切换。

// lib/share_plus_ohos.dart
import 'package:flutter/services.dart';

class SharePlusOhos {
  // 这里定义的和原生侧注册的通道名要一致
  static const MethodChannel _channel =
      const MethodChannel('com.example/share_plus_ohos');

  /// 分享文本
  /// [text] 要分享的文字内容
  /// [subject] 主题(可选,部分场景会显示)
  static Future<void> shareText(String text, {String? subject}) async {
    try {
      await _channel.invokeMethod('shareText', {
        'text': text,
        'subject': subject ?? '',
      });
    } on PlatformException catch (e) {
      print('分享文本失败: ${e.message}');
      // 这里可以选择把错误抛出去,或者做个降级处理
      rethrow;
    }
  }

  /// 分享文件
  /// [filePaths] 文件的绝对路径列表
  /// [mimeTypes] 对应的 MIME 类型列表,比如 ['image/png', 'application/pdf']
  /// [text] 分享文件时附带的文字说明(可选)
  static Future<void> shareFiles(
    List<String> filePaths, {
    List<String>? mimeTypes,
    String? text,
  }) async {
    assert(filePaths.isNotEmpty, '文件路径列表不能为空');
    assert(mimeTypes == null || mimeTypes.length == filePaths.length,
        'MIME 类型列表的长度必须和文件路径列表一致');

    try {
      await _channel.invokeMethod('shareFiles', {
        'filePaths': filePaths,
        'mimeTypes': mimeTypes ?? [],
        'text': text ?? '',
      });
    } on PlatformException catch (e) {
      print('分享文件失败: ${e.message}');
      rethrow;
    }
  }
}

2.2 鸿蒙原生层:实现分享核心

这里是适配的关键。我们需要在鸿蒙工程里创建一个 Ability 来处理来自 Flutter 的分享请求。

1. 创建 ShareServiceAbility(作为中转)

分享功能其实不需要一个常驻的 UI 界面,所以用一个轻量的 Service Ability 来中转就很合适。

// ohos/src/main/java/com/example/shareplusohos/ShareServiceAbility.java
package com.example.shareplusohos;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.app.Context;
import ohos.utils.net.Uri;
import java.util.HashMap;
import java.util.Map;

public class ShareServiceAbility extends Ability {
    private static final String CHANNEL_NAME = "com.example/share_plus_ohos";
    private static final String METHOD_SHARE_TEXT = "shareText";
    private static final String METHOD_SHARE_FILES = "shareFiles";

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        // 注册插件
        FlutterOhosPlugin.registerWith(registrar);
    }

    // 实际触发分享的方法
    public static void shareContent(Context context, String text, String subject, String fileUri, String mimeType) {
        Intent shareIntent = new Intent();
        Operation operation = new Intent.OperationBuilder()
                .withAction("ohos.want.action.sendData") // 系统分享的固定 Action
                .withFlags(Intent.FLAG_ABILITY_NEW_MISSION)
                .build();

        shareIntent.setOperation(operation);
        shareIntent.setParam(Intent.PARAM_INTENT, text); // 分享的文本

        if (fileUri != null && !fileUri.isEmpty()) {
            // 如果有文件,把 URI 放到 Want 里
            shareIntent.setUri(Uri.parse(fileUri));
            if (mimeType != null && !mimeType.isEmpty()) {
                shareIntent.setType(mimeType);
            }
        }

        try {
            context.startAbility(shareIntent, 0);
        } catch (Exception e) {
            Log.error("ShareServiceAbility", "调起分享失败: " + e.getMessage());
        }
    }
}

2. 实现 MethodChannel 处理器

这个类负责接收 Dart 层的调用,并转发给上面的分享能力。

// ohos/src/main/java/com/example/shareplusohos/FlutterSharePlusOhosPlugin.java
package com.example.shareplusohos;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import ohos.app.Context;

public class FlutterSharePlusOhosPlugin implements FlutterPlugin, MethodCallHandler {
  private MethodChannel channel;
  private Context applicationContext;

  @Override
  public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {
    applicationContext = flutterPluginBinding.getApplicationContext();
    channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "com.example/share_plus_ohos");
    channel.setMethodCallHandler(this);
  }

  @Override
  public void onMethodCall(MethodCall call, Result result) {
    switch (call.method) {
      case "shareText":
        {
          String text = call.argument("text");
          String subject = call.argument("subject");
          ShareServiceAbility.shareContent(applicationContext, text, subject, null, null);
          result.success(null); // 分享已触发,不需要等待结果
          break;
        }
      case "shareFiles":
        {
          java.util.ArrayList<String> filePaths = call.argument("filePaths");
          java.util.ArrayList<String> mimeTypes = call.argument("mimeTypes");
          String text = call.argument("text");

          // 实际开发中要注意:鸿蒙分享文件需要可访问的 URI。
          // 这里简化处理,假设 filePaths 已经是应用能直接读的路径。
          if (filePaths != null && !filePaths.isEmpty()) {
            String primaryFileUri = "file://" + filePaths.get(0); // 先处理第一个文件
            String primaryMimeType = (mimeTypes != null && !mimeTypes.isEmpty()) ? mimeTypes.get(0) : "*/*";
            ShareServiceAbility.shareContent(applicationContext, text, "", primaryFileUri, primaryMimeType);
          }
          result.success(null);
          break;
        }
      default:
        result.notImplemented();
        break;
    }
  }

  @Override
  public void onDetachedFromEngine(FlutterPluginBinding binding) {
    channel.setMethodCallHandler(null);
  }
}

2.3 别忘了配置文件

在鸿蒙模块的 config.json 里注册这个 Service Ability 和必要的权限。

// entry/src/main/config.json
{
  "module": {
    "name": "entry",
    "type": "entry",
    "abilities": [
      {
        "name": "ShareServiceAbility",
        "srcEntry": "./ets/shareability/ShareServiceAbility.ets",
        "icon": "$media:icon",
        "description": "$string:shareability_description",
        "type": "service", // 注意 type 是 service
        "visible": true
      }
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_MEDIA", // 如果需要分享图片等媒体文件
        "reason": "$string:reason_share_files",
        "usedScene": {
          "abilities": ["ShareServiceAbility"],
          "when": "always"
        }
      }
    ]
  }
}

三、 一些优化和调试的建议

3.1 可以优化的地方

  1. 文件处理效率
    • 尽量避免重复拷贝文件。如果文件来自网络,最好直接下载到应用沙箱目录(比如 context.getFilesDir())里。
    • 分享大文件时,建议用 DataAbilityHelper 提供 URI,比直接扔 file:// 路径更安全、也更符合鸿蒙的规范。
  2. 内存管理
    • ShareServiceAbility 做完分享触发就可以退出了,别让它一直驻留后台。可以在调完 startAbility 后跟着调用 terminateAbility()
  3. 别阻塞 UI
    • 如果有文件预处理(比如格式转换、压缩),一定要放到后台线程去做,别卡住 Flutter 的主线程。

3.2 调试时怎么查问题

  • Flutter 侧:用 try-catch_channel.invokeMethod 包起来,仔细看 PlatformException 里的信息。
  • 鸿蒙侧:在关键节点加日志,比如收到调用、构建 Want、调起分享的时候,用 HiLogLog 输出状态。
  • 利用 DevEco Studio 的调试功能,在 Java/JS 代码里设断点,一步步跟踪 Want 的传递过程,特别管用。

3.3 记得做兼容性测试

最好在多款不同 API 版本的 OHOS 设备(模拟器和真机都行)上跑一下:

  1. 分享纯文本到备忘录、短信、邮件等应用。
  2. 分享单张、多张图片到图库或微信等社交应用。
  3. 分享 PDF 或文档到文件管理器、WPS。
  4. 试试没有接收应用时的表现(比如能否给出友好提示)。

四、 写在最后

通过上面这些步骤,我们基本上把 share_plus 的核心功能搬到了 OpenHarmony 上。整个过程不仅涉及代码适配,更需要理解两边平台的设计思路——Flutter 的通道机制和鸿蒙的 Want 模型。

简单回顾一下关键点:

  1. 结构要清晰:独立插件方案,不和原插件耦合,以后好维护。
  2. 原理得吃透:明白 Flutter 怎么和原生通信,鸿蒙分享怎么工作,适配起来才不盲目。
  3. 代码要健壮:参数校验、异常处理、资源释放,Dart 和原生两边都得考虑周全。
  4. 体验不能差:分享是用户高频操作,速度和成功率都很影响口碑。

还能做得更好吗?当然可以:

  • 加入结果回调:鸿蒙的 startAbilityForResult 可以知道用户最后选了什么应用来分享,把这个信息传回 Flutter 侧会更有用。
  • 定制分享面板:研究一下鸿蒙的 Picker 或者自己画个 UI,做出更符合应用风格的分享选择器。
  • 贡献给社区:如果代码稳定了,可以尝试提交回 fluttercommunity/plus_plugins 仓库,或者单独发 share_plus_ohos 到 Pub.dev,让更多开发者受益。

这次适配算是一个具体的例子,希望能为其他 Flutter 插件迁移到鸿蒙提供一点参考。随着 OpenHarmony 生态越来越成熟,相信会有更多插件完成原生适配,到时候 Flutter 开发者在鸿蒙平台上的体验也会越来越顺畅。

Logo

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

更多推荐