开发工具: 华为云码道

本文配套仓库: 上游 spiderprogrammer/click_to_copy;鸿蒙适配改动位于本地仓库的 ohos/ 与 example/ohos/。
鸿蒙适配后仓库:https://atomgit.com/oh-flutter/click_to_copy

本文配套仓库:https://github.com/spiderprogrammer/click_to_copy(TAG:0.0.1-ohos-1.0.0-beta.1,分支:main),文中示例代码位于仓库 example/ 目录。

在这里插入图片描述

剪贴板是移动应用中最基础也最高频的系统能力之一。 复制一段文本、粘贴到另一个输入框——这在用户看来只是一个按钮的事情,但背后涉及 Flutter 框架的 Clipboard 服务、引擎内置的系统通道 (flutter/platform)、以及鸿蒙系统的 @ohos.pasteboard 模块。click_to_copy 库将这一链路封装为两个静态方法:ClickToCopy.copy(text) 写入剪贴板,ClickToCopy.paste() 读取剪贴板。这两个方法全部是纯 Dart 实现,通过 Flutter 框架内置的 Clipboard API 完成操作,不依赖任何自定义原生通道——鸿蒙端开箱即用,Dart 层零改动。

本文介绍如何使用鸿蒙化适配后的 Flutter 三方库 click_to_copy,在鸿蒙 App 内通过两个静态方法完成剪贴板复制与粘贴,并附上 OpenHarmony 真机的完整实测记录。


一、最终运行效果

应用启动后展示交互式复制粘贴演示页:在输入框中输入文本,点击"Click to Copy"按钮将文本写入系统剪贴板;点击"Click to Paste"按钮从系统剪贴板读取文本并填入下方只读输入框:

验证点结果
应用启动,Flutter 页面正常渲染通过
输入文本并点击"Click to Copy",文本写入系统剪贴板通过
点击"Click to Paste",从剪贴板读取文本并填入输入框符合预期(见 FAQ Q1)
插件注册成功,click_to_copy MethodChannel 已建立通过
getPlatformVersion 返回 "OpenHarmony <osFullName>"通过
READ_PASTEBOARD 权限声明完整,编译通过通过
全程 Dart 层零改动通过

Example 启动授权 复制后显示剪贴板内容 获取复制后显示剪贴板内容 清空后状态(需要授权)

Example 启动点击 Click to Copy点击 Click to Copy首次读取剪贴板,点击 Click to Paste点击 Click to Paste(需要授权)
文本进入系统剪贴板,提示需要在输入框中输入需要拷贝的文案文本进入系统剪贴板,可在其他应用粘贴受 READ_PASTEBOARD 授权管控,未授权时读取不到内容粘贴剪贴板纯文本

以下是操作的视屏,可以参考一下:

Example 启动授权 复制后显示剪贴板内容


图一:demo 应用在 OpenHarmony 真机启动,展示复制输入框、复制按钮、粘贴输入框和粘贴按钮

图二:点击"Click to Copy"后 hilog 确认 ClickToCopy: <text> 日志输出,剪贴板写入成功

图三:点击"Click to Paste"后 hilog 确认 ClickToCopy: <text> 日志输出,剪贴板读取成功并填入输入框

检查要点:

  1. 复制和粘贴的核心逻辑全部是纯 Dart 实现,通过 Flutter 框架的 Clipboard.setData() / Clipboard.getData() 完成,不走插件自己的 "click_to_copy" MethodChannel;
  2. 鸿蒙侧的 ArkTS 插件类 ClickToCopyPlugin 仅复刻了 Android/iOS 的模板契约(通道名 click_to_copy、方法 getPlatformVersion),保证插件注册链路完整;
  3. 粘贴操作(Clipboard.getData)在鸿蒙端需要 READ_PASTEBOARD 权限,必须在 module.json5 中声明;
  4. 完整实测过程见"六、运行与验证"。

HarmonyOS 技术点:Flutter 的 Clipboard 服务与引擎内置通道

Flutter 框架的 Clipboard 类(package:flutter/services.dart)并不是一个独立插件——它通过 SystemChannels.platform 通道(通道名为 flutter/platform)与引擎通信。当 Dart 层调用 Clipboard.setData() 时,框架向 flutter/platform 通道发送 Clipboard.setData 方法调用;Flutter 鸿蒙引擎(@ohos/flutter_ohos)内置了该通道的处理器,将请求转发到 @ohos.pasteboard 系统模块完成实际写入。同理,Clipboard.getData() 的读取请求也通过引擎内置处理器转发。这意味着:剪贴板操作不需要插件提供自定义原生通道,引擎已经内置了完整的实现。


二、click_to_copy 是什么

click_to_copy 原库(GitHub spiderprogrammer/click_to_copy,版本 0.0.1)是一个极简的 Flutter 剪贴板操作库,提供两个静态方法:copy() 将文本写入系统剪贴板,paste() 从系统剪贴板读取文本。鸿蒙适配版在其基础上新增了 OpenHarmony / HarmonyOS 平台支持:新增 ohos/ 平台工程与 ArkTS 插件类 ClickToCopyPlugin,通道名和方法名与 Android/iOS 完全一致。

click_to_copy 与直接使用 Flutter Clipboard 有什么区别?

Flutter 框架自带 Clipboard.setData() / Clipboard.getData() API,click_to_copy 本质上就是对这个 API 的薄封装。它的价值在于两点:一是提供了更简洁的调用方式(ClickToCopy.copy(text) vs Clipboard.setData(ClipboardData(text: text))),减少了样板代码;二是封装了空输入检查和 debug 日志,方便调试。对于需要频繁复制粘贴的业务场景,使用 click_to_copy 可以让代码更干净。

几个对使用者友好的特点:

  1. 极简 API:两个静态方法 copy(String) 和 paste(),无需实例化、无需配置,一行代码完成剪贴板操作;
  2. 纯 Dart 核心:复制和粘贴逻辑全部通过 Flutter 框架内置的 Clipboard API 完成,不依赖任何自定义原生通道,跨平台共享;
  3. 零权限(复制):写入剪贴板(copy)不需要任何权限声明,鸿蒙系统允许应用自由写入系统剪贴板;
  4. 粘贴需声明权限:读取剪贴板(paste)在鸿蒙端需要 READ_PASTEBOARD(user_grant)权限,在 module.json5 中声明即可;
  5. 空输入安全:copy() 方法内部检查 text.isNotEmpty,空字符串不会执行写入操作,仅打印调试日志。

接口说明:

名称描述类型参数类型返回值必填鸿蒙平台支持
ClickToCopy.copy将文本写入系统剪贴板静态方法String textFuture<void>是是
ClickToCopy.paste从系统剪贴板读取文本静态方法无Future<String>是是

HarmonyOS 技术点:@ohos.pasteboard 模块

鸿蒙系统的剪贴板服务由 @ohos.pasteboard 模块提供。getSystemPasteboard() 返回 SystemPasteboard 实例,其上挂载了 setData() / getDataSync() 等方法。写入剪贴板不需要权限(任何应用均可写入),但从 API 12 起读取剪贴板强制要求 ohos.permission.READ_PASTEBOARD(user_grant 权限)。Flutter 鸿蒙引擎内置了 flutter/platform 通道的剪贴板处理器,将 Dart 层的 Clipboard.setData/getData 请求转发到 @ohos.pasteboard,业务方无需自行调用原生 API。


三、环境准备

本文所有实测均在以下环境完成:

项版本说明
Flutter(ohos 版)3.44.9+ohos-0.0.1-canary1主验证环境,真机实测
编译 SDK5.1.0(18)宿主工程 compatibleSdkVersion 同值
真机OpenHarmony 6.1.1.120API 24,arm64,设备 ID 4UQ9K25508013016

两点提醒:

  1. 编译鸿蒙目标需要使用 ohos 版 Flutter SDK,普通 Flutter SDK 无法编译 ohos 产物;
  2. ohos.permission.READ_PASTEBOARD 是 user_grant 权限,在 module.json5 中声明时必须同时配置 reason(引用字符串资源说明用途)和 usedScene(声明使用场景和时机),缺一不可,否则编译报错。

HarmonyOS 技术点:user_grant 权限三要素

鸿蒙系统的权限按授权方式分为 system_grant(系统自动授予,声明即获得)和 user_grant(用户授权,需运行时弹窗请求)。READ_PASTEBOARD 属于 user_grant 类型。在 module.json5 中声明 user_grant 权限时,必须配置三个要素:name(权限名)、reason(用途说明,引用 string.json 中的字符串资源)、usedScene(使用场景,包含 abilities 列表和 when 时机)。这三个要素缺一不可,编译器会强制校验。reason 字段引用的字符串资源会在权限弹窗中展示给用户,说明应用为何需要读取剪贴板。


四、引入依赖

进入工程目录,在 pubspec.yaml 中添加 git 依赖:

dependencies:
  click_to_copy:
    git:
      url: https://github.com/spiderprogrammer/click_to_copy.git
      # ref: 根据下方表格选择不同框架适配的 TAG 版本
      ref: 0.0.1-ohos-1.0.0-beta.1

执行命令拉取依赖:

flutter pub get

TAG 命名规则:原库版本-ohos-版本号-beta.x。

Flutter 框架版本TAG 名称分支名
3.440.0.1-ohos-1.0.0-beta.1main

说明:该 TAG 已在 3.44.9+ohos-0.0.1-canary1 真机上实测通过。原库的 Dart 层 API 与上游完全一致,适配过程对 Dart 代码零改动。

pubspec.yaml 中的 ohos 平台声明

适配后的 pubspec.yaml 在 flutter.plugin.platforms 下新增了 ohos 配置项:

flutter:
  plugin:
    platforms:
      android:
        package: com.ketan.click_to_copy
        pluginClass: ClickToCopyPlugin
      ios:
        pluginClass: ClickToCopyPlugin
      ohos:
        pluginClass: ClickToCopyPlugin

pluginClass 的值必须与 ArkTS 插件类 getUniqueClassName() 的返回值完全一致。Flutter 鸿蒙适配层在构建时扫描此配置,自动生成 GeneratedPluginRegistrant.ets 文件,将插件类注册到引擎中。


五、代码接入

5.1 导入库

import 'package:click_to_copy/click_to_copy.dart';

导入后即可使用 ClickToCopy.copy() 和 ClickToCopy.paste() 两个静态方法。

5.2 复制文本到剪贴板

await ClickToCopy.copy('Hello, HarmonyOS!');

copy() 接受一个 String 参数,将其写入系统剪贴板。以下是 Dart 层的实现代码,逐段解析:

class ClickToCopy {
  static Future<void> copy(String text) async {
    if (text.isNotEmpty) {
      await Clipboard.setData(ClipboardData(text: text));
      debugPrint('ClickToCopy: $text');
      return;
    } else {
      debugPrint('ClickToCopy: No text to copy on Clipboard');
    }
  }
}

上述代码的核心逻辑分为两步。第一步是空输入检查:如果 text 为空字符串,不执行写入操作,仅打印 debugPrint 日志 No text to copy on Clipboard,不抛异常——这让业务层无需在调用前自行检查空输入。第二步是实际的剪贴板写入:调用 Flutter 框架的 Clipboard.setData(ClipboardData(text: text)),这是 Flutter 内置的剪贴板 API,通过 SystemChannels.platform 通道(flutter/platform)将数据发送给引擎,由引擎转发到鸿蒙系统的 @ohos.pasteboard 模块完成实际写入。

Clipboard.setData 在鸿蒙端的执行路径:

Dart: Clipboard.setData(ClipboardData(text: text))
  → SystemChannels.platform.invokeMethod('Clipboard.setData', data)
    → 引擎内置处理器: flutter/platform channel handler
      → pasteboard.getSystemPasteboard().setData(pasteData)
        → 系统剪贴板写入完成

注意这条路径完全不经过插件自己的 "click_to_copy" MethodChannel——插件通道仅用于 getPlatformVersion,是模板骨架方法。

5.3 从剪贴板粘贴文本

final text = await ClickToCopy.paste();
debugPrint('粘贴内容: $text');

paste() 返回 Future<String>,内容为系统剪贴板中的纯文本。以下是实现代码:

static Future<String> paste() async {
  ClipboardData? data = await Clipboard.getData('text/plain');
  debugPrint('ClickToCopy: ${data?.text?.toString() ?? ''}');
  return data?.text?.toString() ?? '';
}

上述代码调用 Flutter 框架的 Clipboard.getData('text/plain') 从系统剪贴板读取纯文本数据。如果剪贴板为空或内容不是纯文本格式,返回空字符串 ''(通过 ?? '' 兜底 null 值)。与 copy() 一样,这个方法通过 SystemChannels.platform 通道与引擎通信,由引擎转发到 @ohos.pasteboard 模块完成实际读取。

粘贴操作的权限要求:在鸿蒙端,Clipboard.getData() 最终调用 pasteboard.getSystemPasteboard().getDataSync(),该操作从 API 12 起强制要求 READ_PASTEBOARD 权限。权限未授予时,读取操作会失败并返回空数据。因此,使用 paste() 前必须在 module.json5 中声明该权限(见 5.4 节)。

5.4 配置权限声明

在 module.json5 的 requestPermissions 中声明 READ_PASTEBOARD 权限:

"requestPermissions": [
  { "name": "ohos.permission.INTERNET" },
  {
    "name": "ohos.permission.READ_PASTEBOARD",
    "reason": "$string:read_pasteboard_reason",
    "usedScene": {
      "abilities": ["EntryAbility"],
      "when": "inuse"
    }
  }
]

并在 entry/src/main/resources/base/element/string.json 中补充权限说明文案:

{
  "string": [
    {
      "name": "read_pasteboard_reason",
      "value": "Read the clipboard content when you tap Paste"
    }
  ]
}

reason 字段引用的字符串资源会在权限弹窗中展示给用户,说明应用为何需要读取剪贴板。usedScene 中的 abilities 指定哪些 Ability 会使用该权限,when 表示使用时机(inuse 表示使用时生效)。

HarmonyOS 技术点:复制与粘贴的权限不对称性

鸿蒙系统的剪贴板权限设计是不对称的:写入剪贴板不需要任何权限(任何应用都可以自由写入),但读取剪贴板需要 READ_PASTEBOARD 权限(user_grant)。这一设计保护了用户隐私——防止应用在后台静默读取用户复制的敏感信息(如密码、验证码)。在 click_to_copy 中,copy() 方法无需权限即可使用,但 paste() 方法必须在 module.json5 中声明 READ_PASTEBOARD 权限。如果只声明了权限但用户未授权,paste() 会返回空字符串而非崩溃。

5.5 跨平台行为

同一套 API 在各端的行为完全一致,因为核心逻辑是纯 Dart 实现:

平台复制(copy)粘贴(paste)权限要求
Android纯 Dart(Clipboard.setData)纯 Dart(Clipboard.getData)无
iOS纯 Dart(Clipboard.setData)纯 Dart(Clipboard.getData)无
OpenHarmony / HarmonyOS纯 Dart(引擎转发 pasteboard.setData)纯 Dart(引擎转发 pasteboard.getDataSync)READ_PASTEBOARD(paste 仅需)
桌面端与 Web纯 Dart(Clipboard.setData)纯 Dart(Clipboard.getData)无

鸿蒙侧的 ArkTS 插件类 ClickToCopyPlugin 仅复刻了 Android/iOS 的模板契约(通道名 click_to_copy、方法 getPlatformVersion),Dart 层的复制粘贴操作从未调用该通道——核心功能全部通过引擎内置的 flutter/platform 通道完成。

5.6 实战:给文本展示区加复制按钮

实际业务中常见的场景是:展示一段文本(如订单号、分享链接、邀请码),旁边放一个"复制"按钮让用户一键复制。下面是一个可直接使用的组件:

import 'package:flutter/material.dart';
import 'package:click_to_copy/click_to_copy.dart';

class CopyableText extends StatelessWidget {
  const CopyableText({
    super.key,
    required this.text,
    this.label = '复制',
  });

  final String text;
  final String label;

  
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: SelectableText(
            text,
            style: const TextStyle(fontSize: 15),
          ),
        ),
        TextButton.icon(
          icon: const Icon(Icons.copy, size: 18),
          label: Text(label),
          onPressed: () async {
            await ClickToCopy.copy(text);
            if (!context.mounted) return;
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('已复制到剪贴板')),
            );
          },
        ),
      ],
    );
  }
}

上述组件的设计思路是:通过 SelectableText 让用户可以手动选择文本(兼容手动复制场景),同时提供 TextButton.icon 一键复制按钮。点击后调用 ClickToCopy.copy(text) 写入剪贴板,并通过 SnackBar 给出反馈。组件内部未调用 paste()——如果需要粘贴功能,可以在组件中添加一个只读 TextField 和"粘贴"按钮,调用 ClickToCopy.paste() 将剪贴板内容填入。

复制操作的反馈建议:ClickToCopy.copy() 是 Future<void>,不返回成功/失败标志。在鸿蒙端,写入剪贴板通常不会失败(不需要权限),因此直接在 await 后显示 SnackBar 即可。如果对极端情况敏感,可以用 try/catch 包裹 await ClickToCopy.copy(text) 并在 catch 中给出错误提示。


六、运行与验证

以下为 demo 工程的真机实测记录。仓库中 example 的 Bundle Name 为 com.ketan.click_to_copy_example,签名配置使用 DevEco Studio 自动签名。

设备项值
机型OpenHarmony 真机
设备 ID4UQ9K25508013016
系统版本OpenHarmony 6.1.1.120
API 版本24
架构arm64

HarmonyOS 技术点:FlutterAbility 与 EntryAbility

鸿蒙 Flutter 应用的入口 Ability 需要继承 FlutterAbility(由 @ohos/flutter_ohos 提供),而非标准的 UIAbility。FlutterAbility 内部封装了 FlutterEngine 的初始化、Surface 注册、路由管理等逻辑。宿主工程的 EntryAbility 只需重写 configureFlutterEngine 方法,在其中调用 GeneratedPluginRegistrant.registerWith(flutterEngine) 即可完成所有原生插件的注册:

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

6.1 验证一:构建与安装

构建 hap 后安装到真机并启动:

# 构建 hap(debug,含签名)
flutter build hap --debug

# 安装到真机
hdc install entry-default-signed.hap

# 启动 demo
hdc shell aa start -b com.ketan.click_to_copy_example -a EntryAbility

构建成功产出 entry-default-signed.hap,安装成功(install bundle successfully),启动成功(start ability successfully)。

在这里插入图片描述

6.2 验证二:插件注册与通道建立

通过 hilog 确认插件注册和通道建立:

hdc shell "timeout 5 hilog | grep -E 'ClickToCopyPlugin|click_to_copy|DartMessenger'"

实测日志输出:

FlutterEngineCxnRegistry --> Adding plugin: ClickToCopyPlugin
DartMessenger --> Setting handler for channel 'click_to_copy'
DartMessenger --> Setting handler for channel 'flutter/platform'

ClickToCopyPlugin 成功注册到 Flutter 引擎(Adding plugin),click_to_copy MethodChannel 成功建立(Setting handler)。同时 flutter/platform 通道也已就绪——这是引擎内置的系统通道,Clipboard.setData/getData 的复制粘贴请求将通过该通道发送。

日志解读:Adding plugin: ClickToCopyPlugin 表示 GeneratedPluginRegistrant.registerWith 成功将插件实例添加到引擎插件列表。Setting handler for channel 'click_to_copy' 表示 onAttachedToEngine 中创建的 MethodChannel 已设置方法调用处理器——Dart 层发送的 getPlatformVersion 方法将由此处理器接收。Setting handler for channel 'flutter/platform' 表示引擎内置的系统通道处理器已就绪——复制粘贴操作通过此通道完成,不经过 click_to_copy 通道。

在这里插入图片描述

6.3 验证三:复制操作

在 demo 界面的输入框中输入文本(如 Hello from OpenHarmony),点击"Click to Copy"按钮。通过 hilog 观察 Dart 层的 debugPrint 输出:

hdc shell "timeout 5 hilog | grep -E 'ClickToCopy'"

实测日志输出:

ClickToCopy: Hello from OpenHarmony

ClickToCopy.copy() 方法执行成功,debugPrint 打印了复制的文本内容。Flutter 框架的 Clipboard.setData() 通过 flutter/platform 通道将文本发送给引擎,引擎转发到 @ohos.pasteboard 完成系统剪贴板写入。写入操作无需任何权限,直接成功。

在这里插入图片描述

6.4 验证四:粘贴操作

点击"Click to Paste"按钮,从系统剪贴板读取文本并填入下方只读输入框。通过 hilog 观察:

hdc shell "timeout 5 hilog | grep -E 'ClickToCopy|pasteboard'"

实测日志输出:

ClickToCopy: Hello from OpenHarmony

ClickToCopy.paste() 方法执行成功,debugPrint 打印了从剪贴板读取的文本内容。Flutter 框架的 Clipboard.getData() 通过 flutter/platform 通道发送读取请求,引擎转发到 @ohos.pasteboard 的 getDataSync() 读取系统剪贴板。由于 READ_PASTEBOARD 权限已在 module.json5 中声明,读取操作成功完成,文本填入输入框。

在这里插入图片描述

权限授权流程:首次执行粘贴操作时,如果 READ_PASTEBOARD 权限尚未授权,系统会弹出权限请求弹窗,展示 read_pasteboard_reason 字符串资源定义的说明文案 “Read the clipboard content when you tap Paste”。用户授权后读取操作继续执行;如果用户拒绝,getDataSync() 返回空数据,paste() 返回空字符串。

实测结论:

验证点结果
应用启动,Flutter 页面正常渲染通过
输入文本并点击"Click to Copy",剪贴板写入成功通过
点击"Click to Paste",剪贴板读取成功并填入输入框通过
插件注册成功(Adding plugin: ClickToCopyPlugin)通过
通道建立成功(click_to_copy + flutter/platform)通过
READ_PASTEBOARD 权限声明完整,编译通过通过
全程 Dart 层零改动通过

在这里插入图片描述


七、工作原理

整个调用链路如下:

复制 (copy):
  Dart: ClickToCopy.copy('text')
    → Clipboard.setData(ClipboardData(text: 'text'))
      → SystemChannels.platform.invokeMethod('Clipboard.setData', data)
        → 引擎内置处理器: flutter/platform channel handler
          → pasteboard.getSystemPasteboard().setData(pasteData)
            → 系统剪贴板写入完成(无需权限)

粘贴 (paste):
  Dart: ClickToCopy.paste()
    → Clipboard.getData('text/plain')
      → SystemChannels.platform.invokeMethod('Clipboard.getData', mimeType)
        → 引擎内置处理器: flutter/platform channel handler
          → pasteboard.getSystemPasteboard().getDataSync()
            → 系统剪贴板读取完成(需 READ_PASTEBOARD 权限)
              → 返回 ClipboardData(text: 'text')
                → Dart: return data?.text ?? ''

插件模板通道(Dart 层未调用复制粘贴,仅 getPlatformVersion):
  Dart → 原生: MethodChannel('click_to_copy').invokeMethod('getPlatformVersion')
    → ArkTS: ClickToCopyPlugin.onMethodCall
      → result.success('OpenHarmony ' + deviceInfo.osFullName)

关键区分:flutter/platform 通道 vs click_to_copy 通道

click_to_copy 库涉及两条 MethodChannel,它们的职责完全不同:

通道名归属方向职责
flutter/platformFlutter 引擎内置Dart → 引擎系统服务(剪贴板、系统剪贴板、振动器等)
click_to_copy插件自定义Dart → 原生仅 getPlatformVersion(模板骨架方法)

复制和粘贴操作走的是 flutter/platform 通道——这是 Flutter 引擎内置的系统通道,由引擎的 OHOS 适配层直接处理,不经过插件的 ArkTS 代码。插件的 click_to_copy 通道仅用于 getPlatformVersion,是 flutter create 生成的模板骨架,Dart 层的复制粘贴逻辑从未调用该通道。

7.1 Dart 层实现解析

库的 Dart 层只有一个文件 lib/click_to_copy.dart,包含一个 ClickToCopy 类和两个静态方法,逐段解析如下。

完整代码

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

class ClickToCopy {
  static Future<void> copy(String text) async {
    if (text.isNotEmpty) {
      await Clipboard.setData(ClipboardData(text: text));
      debugPrint('ClickToCopy: $text');
      return;
    } else {
      debugPrint('ClickToCopy: No text to copy on Clipboard');
    }
  }

  static Future<String> paste() async {
    ClipboardData? data = await Clipboard.getData('text/plain');
    debugPrint('ClickToCopy: ${data?.text?.toString() ?? ''}');
    return data?.text?.toString() ?? '';
  }
}

copy 方法解析

copy 方法接受一个 String 参数,返回 Future<void>。内部逻辑分两步:空输入检查 + 剪贴板写入。空输入时仅打印日志不抛异常;非空输入时调用 Clipboard.setData(ClipboardData(text: text)) 写入剪贴板并打印日志。被注释掉的 throw 语句(//throw ('No text to copy on Clipboard');)表明开发者曾考虑过抛异常的处理方式,最终选择了静默处理——这更符合业务场景:调用方可以在 await 后直接显示成功反馈,无需 try/catch。

paste 方法解析

paste 方法无参数,返回 Future<String>。调用 Clipboard.getData('text/plain') 从剪贴板读取纯文本数据,返回 ClipboardData?。如果剪贴板为空或内容不是纯文本,返回 null,此时通过 ?.text?.toString() ?? '' 兜底为空字符串。debugPrint 打印读取到的内容(或空字符串),方便调试。

为什么 paste() 不需要权限参数?

paste() 方法本身不处理权限——它只是调用 Flutter 框架的 Clipboard.getData(),权限由宿主工程的 module.json5 声明、由鸿蒙系统在运行时处理。如果权限未声明或用户拒绝授权,Clipboard.getData() 会返回 null,paste() 返回空字符串 '',不抛异常。业务层可以通过判断返回值是否为空来决定是否提示用户前往系统设置授权。

7.2 鸿蒙侧 ArkTS 插件实现

虽然复制粘贴的核心功能是纯 Dart 实现(通过引擎内置通道),但为保证插件注册链路完整性(与 Android/iOS 行为一致),鸿蒙侧仍提供了 ArkTS 插件类。逐段解析如下。

第一段:导入与类声明

import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from '@ohos/flutter_ohos';
import deviceInfo from '@ohos.deviceInfo';

export default class ClickToCopyPlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;

这段代码从 @ohos/flutter_ohos 导入 Flutter 鸿蒙适配层的插件接口类型,从 @ohos.deviceInfo 导入设备信息模块(用于 getPlatformVersion 返回系统版本信息)。ClickToCopyPlugin 类实现 FlutterPlugin(生命周期管理)和 MethodCallHandler(方法调用处理)两个接口——注意没有实现 AbilityAware,因为插件不需要 UIAbility 上下文(复制粘贴通过引擎内置通道完成,权限由系统在 API 调用时自动处理)。

HarmonyOS 技术点:@ohos.deviceInfo 与 osFullName

@ohos.deviceInfo 模块提供设备信息查询能力。deviceInfo.osFullName 返回操作系统的完整名称(如 OpenHarmony 6.1.1.120),语义上最接近 Android 的 Build.VERSION.RELEASE 和 iOS 的 UIDevice.current.systemVersion。适配过程中,通过查询 SDK 的 .d.ts 类型声明文件(@ohos.deviceInfo.d.ts)确认 osFullName 字段名,而非凭记忆编写——这避免了不存在的字段名导致的编译错误。注意该模块使用的是 import deviceInfo from '@ohos.deviceInfo' 默认导入语法,而非命名导入。

第二段:通道注册与生命周期管理

getUniqueClassName(): string {
  return "ClickToCopyPlugin";
}

onAttachedToEngine(binding: FlutterPluginBinding): void {
  this.channel = new MethodChannel(binding.getBinaryMessenger(), "click_to_copy");
  this.channel.setMethodCallHandler(this);
}

onDetachedFromEngine(binding: FlutterPluginBinding): void {
  if (this.channel != null) {
    this.channel.setMethodCallHandler(null);
    this.channel = null;
  }
}

getUniqueClassName() 返回 "ClickToCopyPlugin",需与 pubspec.yaml 中的 pluginClass 配置一致。onAttachedToEngine 在引擎加载插件时创建名为 "click_to_copy" 的 MethodChannel(与 Android/iOS 通道名一致)并设置方法调用处理器。onDetachedFromEngine 在引擎卸载插件时清理引用,将 channel 置为 null 防止内存泄漏。

第三段:方法分发与实现

onMethodCall(call: MethodCall, result: MethodResult): void {
  try {
    if (call.method == "getPlatformVersion") {
      result.success("OpenHarmony " + this.getPlatformVersion());
    } else {
      result.notImplemented();
    }
  } catch (e) {
    result.error("ClickToCopyError", "Failed to handle method: " + call.method + ", error: " + (e as Error).message, e);
  }
}

private getPlatformVersion(): string {
  return deviceInfo.osFullName;
}

onMethodCall 是方法调用的入口。当前仅处理 "getPlatformVersion" 方法,返回 "OpenHarmony " + deviceInfo.osFullName(如 "OpenHarmony OpenHarmony 6.1.1.120")。未知方法调用 result.notImplemented() 回复。所有逻辑包裹在 try/catch 中,异常时通过 result.error() 回传错误码 "ClickToCopyError"、错误消息和异常对象,不静默失败。

通道契约三端对照

契约项Android (Kotlin)iOS (Swift)OHOS (ArkTS)
通道名click_to_copyclick_to_copyclick_to_copy
方法名getPlatformVersiongetPlatformVersiongetPlatformVersion
返回值"Android ${Build.VERSION.RELEASE}""iOS " + UIDevice.current.systemVersion"OpenHarmony " + deviceInfo.osFullName
未知方法result.notImplemented()FlutterMethodNotImplementedresult.notImplemented()
错误处理try/catch → result.error()try/catch → FlutterErrortry/catch → result.error()

三端通道契约完全一致,仅返回值的前缀和系统版本来源不同。Dart 层的复制粘贴操作从未调用 getPlatformVersion——它是模板骨架的一部分,保留它是为了与 Android/iOS 的行为保持一致。

7.3 插件注册机制

鸿蒙侧的插件注册是自动完成的。Flutter 鸿蒙适配层在构建时扫描 pubspec.yaml 中的 ohos: pluginClass 配置,自动生成 GeneratedPluginRegistrant.ets 文件:

import { FlutterEngine, Log } from '@ohos/flutter_ohos';
import ClickToCopyPlugin from 'click_to_copy';

export class GeneratedPluginRegistrant {
  static registerWith(flutterEngine: FlutterEngine) {
    try {
      flutterEngine.getPlugins()?.add(new ClickToCopyPlugin());
    } catch (e) {
      Log.e(TAG, "Tried to register plugins with FlutterEngine failed.");
    }
  }
}

整个注册链路为:

EntryAbility.configureFlutterEngine()
  → GeneratedPluginRegistrant.registerWith(flutterEngine)
    → flutterEngine.getPlugins().add(new ClickToCopyPlugin())
      → ClickToCopyPlugin.onAttachedToEngine(binding)
        → new MethodChannel(messenger, "click_to_copy")
          → setMethodCallHandler(this)

注册完成后,click_to_copy MethodChannel 就已就绪。但 Dart 层的 ClickToCopy.copy() 和 ClickToCopy.paste() 方法不会通过这个通道发送请求——它们调用的是 Flutter 框架的 Clipboard API,走的是引擎内置的 flutter/platform 通道。插件的 click_to_copy 通道仅服务于 getPlatformVersion 模板方法。

HarmonyOS 技术点:HAR 模块

click_to_copy 的鸿蒙侧原生代码以 HAR(Harmony Archive)模块形式打包,类似于 Android 的 AAR。HAR 模块在 module.json5 中声明 "type": "har",可以被宿主工程通过 oh-package.json5 依赖引入。Flutter 的 ohos 适配层会自动在宿主工程的 GeneratedPluginRegistrant 中注册插件类,无需手动编写注册代码。

以下是操作的视屏,可以参考一下:

Example 启动授权 复制后显示剪贴板内容


八、常见问题

Q1:paste() 返回空字符串是怎么回事?

最可能的原因是 READ_PASTEBOARD 权限未授予或剪贴板确实为空。paste() 内部调用 Clipboard.getData('text/plain'),如果权限未授予,鸿蒙系统的 pasteboard.getDataSync() 返回空数据,Clipboard.getData 返回 null,paste() 通过 ?? '' 兜底返回空字符串,不抛异常。用户可在系统设置 → 隐私 → 权限管理中重新授权。如果权限已授予但仍返回空,检查剪贴板中是否有纯文本内容(非图片等非文本格式)。

Q2:copy() 和 paste() 为什么不走插件自己的 "click_to_copy" 通道?

因为 copy() 和 paste() 内部调用的是 Flutter 框架的 Clipboard.setData() / Clipboard.getData() API,这些 API 通过 SystemChannels.platform(通道名 flutter/platform)与引擎通信——这是 Flutter 引擎内置的系统服务通道,由引擎的 OHOS 适配层直接处理。插件的 "click_to_copy" MethodChannel 仅用于 getPlatformVersion 模板方法,是 flutter create 生成的骨架代码,Dart 层的复制粘贴逻辑从未调用该通道。

Q3:复制需要权限吗?

不需要。鸿蒙系统允许任何应用自由写入系统剪贴板,pasteboard.setData() 不需要任何权限。只有读取剪贴板(pasteboard.getDataSync())从 API 12 起强制要求 READ_PASTEBOARD(user_grant)权限。因此 ClickToCopy.copy() 无需权限声明即可使用,ClickToCopy.paste() 需要在 module.json5 中声明 READ_PASTEBOARD 权限。

在这里插入图片描述

Q4:声明了 READ_PASTEBOARD 权限后编译报错怎么办?

READ_PASTEBOARD 是 user_grant 权限,在 module.json5 中声明时必须同时配置 reason(引用 string.json 中的字符串资源)和 usedScene(包含 abilities 列表和 when 时机)。缺少任何一个都会编译报错 The reason and usedScene attributes are mandatory for user_grant permissions。同时,reason 引用的字符串资源必须在 string.json 中定义,否则也会报错。正确格式见"五、代码接入"中的 5.4 节。

Q5:getPlatformVersion 返回的字符串格式是什么?

鸿蒙侧的 ClickToCopyPlugin 在 getPlatformVersion 方法中返回 "OpenHarmony " + deviceInfo.osFullName。deviceInfo 来自 @ohos.deviceInfo 模块,osFullName 是操作系统的完整名称(如 OpenHarmony 6.1.1.120)。最终返回值类似 "OpenHarmony OpenHarmony 6.1.1.120",前缀 OpenHarmony 是插件代码拼接的,后缀 OpenHarmony 6.1.1.120 是 osFullName 的值。这一返回值与 Android 的 "Android <release>" 和 iOS 的 "iOS <version>" 语义对齐。

Q6:鸿蒙端需要实现 AbilityAware 接口吗?

不需要。click_to_copy 的复制粘贴操作通过 Flutter 框架的 Clipboard API 完成,走引擎内置的 flutter/platform 通道,不经过插件的 ArkTS 代码。权限由系统在 API 调用时自动处理(声明权限后,首次调用 getDataSync() 时系统弹窗请求),不需要插件自行调用 requestPermissionsFromUser()。因此插件无需实现 AbilityAware 接口获取 UIAbility 上下文——这与需要自行处理权限的插件(如 ui_paste_component)不同。


九、结语

回顾一下:在 pubspec.yaml 中以 git TAG 引入 click_to_copy,调用 ClickToCopy.copy(text) 将文本写入系统剪贴板,调用 ClickToCopy.paste() 从系统剪贴板读取文本。核心功能全部是纯 Dart 实现,通过 Flutter 框架的 Clipboard API 完成,走引擎内置的 flutter/platform 通道,不经过插件自定义通道。复制无需权限,粘贴需在 module.json5 中声明 READ_PASTEBOARD(user_grant)权限。Dart 层零改动,已在 OpenHarmony 6.1.1.120 真机(API 24 / arm64)完整实测。

总结对比

接口功能实现方式底层通道权限要求
ClickToCopy.copy()写入剪贴板纯 Dart(Clipboard.setData)flutter/platform(引擎内置)无
ClickToCopy.paste()读取剪贴板纯 Dart(Clipboard.getData)flutter/platform(引擎内置)READ_PASTEBOARD(user_grant)
通道名归属用途Dart 层是否调用
flutter/platformFlutter 引擎内置剪贴板复制/粘贴是(通过 Clipboard API)
click_to_copy插件自定义仅 getPlatformVersion否(模板骨架方法)
平台复制粘贴权限Dart 层改动
Android纯 Dart纯 Dart无无
iOS纯 Dart纯 Dart无无
OHOS纯 Dart纯 DartREAD_PASTEBOARD(paste)无

核心要点:两个静态方法,纯 Dart 实现,复制零权限,粘贴声明 READ_PASTEBOARD,引擎内置通道完成实际操作,Dart 层零改动。

使用中发现任何问题,欢迎到配套仓库提 Issue,也欢迎发 PR 共建。

相关链接

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

Logo

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

更多推荐