Flutter custom_clipboard 库适配实践:适配HarmonyOS支持文本读写与剪贴内容监听,降低多端开发剪贴功能的适配成本
开发工具: 华为云码道
本文配套仓库: 上游 thorito/custom_clipboard;
鸿蒙适配后仓库:
custom_clipboard 是一个用于清空系统剪贴板的 Flutter 插件,同时提供读取剪贴板文本的便捷入口。由于鸿蒙系统的 ohos.permission.READ_PASTEBOARD 为受限权限,读取行为需要携带 ACL 的正式签名证书,清空与写入则不受此限制。本文以 custom_clipboard 0.0.6 为例,介绍源码准备、OHOS 工程配置、ArkTS 实现和 example 真机运行。
插件目前支持 ohos 平台,源码位于 GitHub 上游仓库。文中的代码以提交 20fd1dd621ca74b9035e4ad05cf35b75d9445cd9 为参考;OHOS 适配改动当前在 main 分支工作区,发布 TAG 为 0.0.6-ohos-1.0.0-beta.1。
一、插件简介与适配目标
custom_clipboard 把 Android/iOS 上清空剪贴板的操作封装为统一的 Dart API。在鸿蒙侧,清空剪贴板对应 @ohos.pasteboard 的 getSystemPasteboard().clearData(),直接移除剪贴板内容,而不是像通用方案那样写入空文本占位。
例如,在登录、支付或涉及敏感信息的页面退出后调用 clearClipboard(),可以避免下一次剪贴板粘贴泄露刚刚复制的密码、银行卡号或验证码。getClipboard() 则提供读取入口,在鸿蒙上受系统权限策略约束(详见 5.2 与 8.6)。
插件通过单条 MethodChannel 暴露 clearClipboard 命令;读取走 Flutter 引擎内置剪贴板通道,无需额外原生实现。
二、环境准备
环境搭建参考社区文档:Flutter OH 开发环境搭建,完成 Flutter OH SDK 安装、环境变量和 DevEco Studio 配置。
完成后,在宿主机终端执行以下命令,确认当前选中的是支持 OHOS 的 Flutter 工具链,并能发现目标设备:
flutter --version
flutter doctor -v
hdc list targets
工程使用的工具链和 SDK 配置如下:
| 项目 | 版本或配置 | 用途 |
|---|---|---|
| Flutter OHOS SDK | 3.44.9+ohos-0.0.1-canary1 | Flutter 编译与 OHOS 平台工具链 |
| Flutter 分支 | [user-branch] / CPF-Flutter fork | 社区适配版 Flutter 工具链 |
| Dart SDK | 3.12.2 | Dart 语言与包管理环境 |
| HarmonyOS 开发套件 | 26.0.0(API 26) | 开发套件版本及对应的 API 级别 |
compatibleSdkVersion | 5.0.0(12) | 当前工程声明的最低兼容版本 |
| 插件版本 | 0.0.6 | pubspec.yaml 中的包版本 |
| 原生语言 | ArkTS | HarmonyOS 插件实现 |
| 插件产物 | HAR | 被应用 entry 模块依赖 |
2.1 开发套件版本与工程中的 SDK 版本配置
26.0.0(API 26) 与 5.0.0(12) 涉及 HarmonyOS 开发套件版本(API 版本)及其底座 OpenHarmony 的版本号体系。在本文工程中,相关版本的含义与配置方式如下:
26.0.0(API 26)表示 DevEco Studio 使用的 HarmonyOS SDK 版本为26.0.0,对应 API 26。- 本仓库
example/ohos/build-profile.json5未显式覆盖compileSdkVersion与targetSdkVersion,由 DevEco Studio 默认使用 API 26 套件。 5.0.0(12)是本文工程中compatibleSdkVersion的属性值,声明最低兼容 API 12。
对应的 product 配置为:
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.0.0(12)",
"runtimeOS": "HarmonyOS"
}
这组配置使用 API 26 SDK 编译,最低兼容 API 12。真机验证设备为 OpenHarmony 6.1.1.120(API 24),满足安装门槛;但剪贴板读取能力还受 READ_PASTEBOARD 受限权限与签名证书 ACL 约束。
三、从源码仓库开始准备适配工程
3.1 将上游源码同步到 AtomGit
适配已有第三方插件时,先从 Pub 包信息或项目 README 确认上游地址、包名、版本和许可证。同步仓库时保留源码、许可证和提交历史,方便后续跟踪上游更新。
在 AtomGit 网页的新建仓库流程中,找到从已有仓库导入的入口,填写上游 Git 地址,选择自己有写权限的组织或个人空间,设置目标仓库名称后执行导入。完成后检查目标分支、pubspec.yaml、LICENSE 和提交历史是否完整,并记录适配起点的提交号。如果上游本身托管在 AtomGit,也可以通过 Fork 获得自己的工作仓库。
custom_clipboard 的上游仓库为 thorito/custom_clipboard。若你的团队有 AtomGit 镜像,可使用 https://atomgit.com/{your-org}/custom_clipboard.git 替换下方命令中的 GitHub 地址。需要提交修改时,使用自己有写权限的仓库或 Fork。
3.2 将代码拉取到宿主机
在安装了 Flutter OH 和 DevEco Studio 的开发电脑上打开终端,进入准备存放项目的目录,执行:
git clone https://github.com/thorito/custom_clipboard.git
cd custom_clipboard
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD
git clone 会创建 custom_clipboard/ 目录;cd 后的位置就是下文所说的插件仓库根目录,这里应能看到 pubspec.yaml(根目录管理 monorepo)、custom_clipboard/ 主包、custom_clipboard_android/、custom_clipboard_ios/、custom_clipboard_platform_interface/ 和 docs/。Git 仓库名是 custom_clipboard,Dart 主包名是 custom_clipboard。
需要使用与本文相同的代码版本时,在没有未提交修改的仓库中切换到以下提交:
git switch --detach 20fd1dd621ca74b9035e4ad05cf35b75d9445cd9
适配其他插件时,使用该插件对应版本的 tag 或 commit 作为分支起点。本仓库的 OHOS 改动当前在 main 分支工作区;发布时按 0.0.6-ohos-1.0.0-beta.1 打 TAG。

图 1:在宿主机终端输入仓库拉取命令。
3.3 在仓库根目录确认分支与发布 TAG
custom_clipboard 的鸿蒙改动直接基于 main 分支维护,并在发布时通过 TAG 标记 OHOS 版本。在 custom_clipboard/ 根目录执行:
git branch --show-current
git tag 0.0.6-ohos-1.0.0-beta.1
git tag -l
如果习惯使用适配分支,也可以在主包目录创建 feat/ohos_custom_clipboard_0.0.6,完成后合并回 main 再打 TAG。本例按仓库现有工作方式直接以 main + TAG 发布。

图 2:在 custom_clipboard 仓库根目录确认分支与发布 TAG。
3.4 自动补全 OHOS 适配结构
本仓库的 custom_clipboard/ 主包下已经包含 ohos/ 目录和 example/ohos/ 宿主工程,因此可以跳过 flutter create --platforms=ohos 的补全步骤。如果是从零开始,在主包目录执行:
cd custom_clipboard
flutter create --template=plugin --platforms=ohos --project-name custom_clipboard .
git status --short
git diff -- pubspec.yaml lib example
--template=plugin指定插件模板。--platforms=ohos指定需要补全的平台。--project-name custom_clipboard使用 Dart 包名,避免把含下划线的目录名作为包名。- 最后的
.表示在当前插件目录补全工程,不是另建一层custom_clipboard/。
该命令生成 OHOS 平台脚手架,业务逻辑需要在 ArkTS 中实现。生成后通过 diff 检查 pubspec.yaml、lib/ 和 example/ 的变化,保留已有 API、其他平台注册项及依赖配置。不同 Flutter OH 版本生成的模板可能略有差异。
如果生成后 example/ohos/ 仍不存在,进入已有示例应用补全平台:
cd example
flutter create --platforms=ohos .
cd ..
配套仓库已经包含 custom_clipboard/ohos/ 和 custom_clipboard/example/ohos/,直接运行示例时可以跳过结构补全。新建插件则使用 flutter create --org com.example --template=plugin --platforms=ohos custom_clipboard;已有插件使用上面的 . 在当前目录补全。

图 3:在插件主包目录确认 ohos/ 与 example/ohos/ 已存在,无需重新生成脚手架。
3.5 适配后的项目目录
适配后的关键目录如下:
custom_clipboard/ # 仓库根目录(monorepo)
├── custom_clipboard/ # 插件主包
│ ├── lib/
│ │ └── custom_clipboard.dart # Dart 层 API
│ ├── ohos/
│ │ ├── index.ets # 插件导出
│ │ ├── BuildProfile.ets # HAR 版本号
│ │ ├── oh-package.json5 # HAR 包信息
│ │ └── src/main/
│ │ ├── ets/components/plugin/
│ │ │ └── CustomClipboardPlugin.ets
│ │ └── module.json5 # HAR 模块声明
│ ├── example/
│ │ ├── lib/main.dart # 示例页面
│ │ └── ohos/entry/ # 宿主 entry 工程
│ ├── README.OpenHarmony_CN.md
│ ├── CHANGELOG.OpenHarmony.md
│ ├── LICENSE
│ └── pubspec.yaml # 主包声明(含 ohos 平台)
├── custom_clipboard_android/ # Android 联邦实现
├── custom_clipboard_ios/ # iOS 联邦实现
├── custom_clipboard_platform_interface/ # 平台接口契约
├── docs/
│ └── ohos-test-evidence/ # 真机截图与 hilog
└── README.md
插件主包目录 custom_clipboard/custom_clipboard/ 如下,其中包含 ohos/、example/ohos/,以及 OpenHarmony 中英文说明和变更记录文件:

图 4:适配后的 custom_clipboard 项目根目录。
| 文件 | 主要职责 |
|---|---|
custom_clipboard/lib/custom_clipboard.dart | 为业务应用提供 getClipboard() 与 clearClipboard() 入口 |
custom_clipboard_platform_interface/lib/src/method_channel_custom_clipboard.dart | 声明 MethodChannel custom_clipboard 与 clearClipboard 契约 |
custom_clipboard_platform_interface/lib/custom_clipboard_platform_interface.dart | 定义平台接口基类,供 Android/iOS 联邦实现扩展 |
custom_clipboard/ohos/src/main/ets/components/plugin/CustomClipboardPlugin.ets | 注册 Flutter 通道并调用 @ohos.pasteboard 清空剪贴板 |
插件 custom_clipboard/ohos/src/main/module.json5 | 声明 HAR 模块(无特殊权限) |
示例 entry custom_clipboard/example/ohos/entry/src/main/module.json5 | 声明宿主应用 Ability、设备类型和权限场景 |
custom_clipboard/example/lib/main.dart | 展示复制、读取、清空与操作日志 |
四、Dart 接口与通道分析
OHOS 实现需要遵循 Dart 层已有的方法、参数和回调约定。先阅读 custom_clipboard/lib/custom_clipboard.dart、custom_clipboard_platform_interface/lib/custom_clipboard_platform_interface.dart 和 custom_clipboard_platform_interface/lib/src/method_channel_custom_clipboard.dart,再在 custom_clipboard/ohos/src/main/ets/components/plugin/CustomClipboardPlugin.ets 中实现对应的原生调用。适配其他已有平台的插件时,也应保留其公开接口和其他平台实现。
本例的对应关系如下:
| Dart 入口 | 通道协议 | OHOS 实现 | 应保持的行为 |
|---|---|---|---|
getClipboard() | Flutter 引擎内置 Clipboard.getData(Clipboard.kTextPlain) | 无自定义通道 | 返回 Future<String?>,受限权限下为 null |
clearClipboard() | custom_clipboard / clearClipboard | pasteboard.getSystemPasteboard().clearData() | 直接清空,成功返回 null,失败抛 PlatformException |
CustomClipboardPlatform.instance.clearClipboard() | custom_clipboard / clearClipboard | 同 clearClipboard 原生实现 | 联邦接口的默认实现命中同一原生插件 |
原生端需要保持通道名和方法名一致。getClipboard() 依赖引擎内置剪贴板通道,OHOS 侧无需额外实现;清空失败时通过 result.error 通知 Dart。
4.1 跨端架构与调用时序
Flutter 侧和 HarmonyOS 侧之间使用一条 MethodChannel:
MethodChannel('custom_clipboard'):负责接收clearClipboard命令;getClipboard()与写入剪贴板走 Flutter 引擎内置的Clipboard通道,无需自定义原生代码。
MethodChannel 返回 clearClipboard 操作的结果。由于清空操作只涉及一次调用,不存在持续事件流。
4.1.1 一次清空操作的时序
4.2 Dart API 与返回值
custom_clipboard 不提供复杂的状态模型,只有两个顶层异步方法:
/// Invoke the platform-specific method to get the clipboard data.
Future<String?> getClipboard() async {
final data = await Clipboard.getData(Clipboard.kTextPlain);
return data?.text;
}
/// Invoke the platform-specific method to clear the clipboard.
Future<void> clearClipboard() async {
if (Platform.isAndroid || Platform.isIOS) {
await _method.clearClipboard();
} else {
await Clipboard.setData(const ClipboardData(text: ''));
}
}
| 方法 | 返回值 | 在 OHOS 上的实际路径 | 说明 |
|---|---|---|---|
getClipboard() | Future<String?> | 引擎 Clipboard.getData | 受限权限下返回 null |
clearClipboard() | Future<void> | 引擎 Clipboard.setData('') | 写入空文本,剪贴板仍可能含空记录 |
CustomClipboardPlatform.instance.clearClipboard() | Future<void> | 原生插件 pasteboard.clearData() | 真正清空剪贴板 |
业务代码通常调用 clearClipboard(),与 Android/iOS 上层 API 保持一致;若需要直接调用 OHOS 原生的 clearData() 语义,可显式使用 CustomClipboardPlatform.instance.clearClipboard()。
4.3 公开 API 与平台接口
CustomClipboardPlatform 定义平台接口。业务通过 custom_clipboard.dart 的顶层函数调用,测试中也可以替换平台实现。
abstract class CustomClipboardPlatform extends PlatformInterface {
static CustomClipboardPlatform _instance = MethodChannelCustomClipboard();
static CustomClipboardPlatform get instance => _instance;
static set instance(CustomClipboardPlatform instance) { ... }
Future<void> clearClipboard();
}
class MethodChannelCustomClipboard extends CustomClipboardPlatform {
final methodChannel = const MethodChannel('custom_clipboard');
Future<void> clearClipboard() {
return methodChannel.invokeMethod<void>('clearClipboard');
}
}
custom_clipboard.dart 的 clearClipboard() 在 Platform.isAndroid || Platform.isIOS 为真时走 _method.clearClipboard(),否则回退到 Clipboard.setData('')。在 OHOS 上 Platform.isOhos 为真,因此库 API 默认走引擎通道;直接调用平台接口 CustomClipboardPlatform.instance.clearClipboard() 才会命中原生插件。
4.4 Dart 通道协议分析
4.4.1 通道名称必须两端完全一致
final methodChannel = const MethodChannel('custom_clipboard');
通道名称属于跨语言协议。Dart 和 ArkTS 任何一端拼写不一致,都会出现“方法未实现”。
4.4.2 clearClipboard 实现
Future<void> clearClipboard() {
return methodChannel.invokeMethod<void>('clearClipboard');
}
MethodChannelCustomClipboard 只发送 clearClipboard 命令,不接收持续事件。命令结果是一次性的 Future<void>,失败时抛出 MissingPluginException 或 PlatformException。
4.4.3 getClipboard 与错误处理
Future<String?> getClipboard() async {
final data = await Clipboard.getData(Clipboard.kTextPlain);
return data?.text;
}
getClipboard() 不经过 custom_clipboard 自定义通道,而是复用 Flutter 引擎内置的 Clipboard 服务。在 OHOS 上,引擎同样会通过 @ohos.pasteboard 读取系统剪贴板,但受 ohos.permission.READ_PASTEBOARD 受限权限约束。调试签名或未声明 ACL 时,Clipboard.getData 会返回 null,上层 data?.text 自然为 null,不会抛异常。
五、补全 OHOS 原生实现与工程配置
5.1 在 CustomClipboardPlugin.ets 中实现原生能力
业务仍调用 clearClipboard() 或 CustomClipboardPlatform.instance.clearClipboard(),Dart 通道仍发送同名命令。需要补全的是 CustomClipboardPlugin.ets 中的对应分支:调用 @ohos.pasteboard 的 clearData() 清空系统剪贴板,通过 result.success 或 result.error 回传操作结果。
CustomClipboardPlugin 实现 FlutterPlugin 和 MethodCallHandler。下面列出类中的主要成员和方法。
原生插件位于:
custom_clipboard/ohos/src/main/ets/components/plugin/CustomClipboardPlugin.ets
5.1.1 引入 Flutter 和 HarmonyOS 能力
import {
FlutterPlugin,
FlutterPluginBinding,
MethodCall,
MethodCallHandler,
MethodChannel,
MethodResult,
} from '@ohos/flutter_ohos';
import pasteboard from '@ohos.pasteboard';
import hilog from '@ohos.hilog';
const TAG = 'CustomClipboardPlugin';
其中:
FlutterPlugin负责接入 Flutter Engine 生命周期;MethodChannel接收 Dart 发来的clearClipboard命令;pasteboard提供getSystemPasteboard().clearData()清空剪贴板;hilog用于原生侧诊断日志。
由于 @ohos.pasteboard 的系统 API 不依赖 Ability/Context,本插件不需要实现 AbilityAware。
5.1.2 连接 Flutter Engine
private channel: MethodChannel | null = null;
onAttachedToEngine(binding: FlutterPluginBinding): void {
hilog.info(0x0000, TAG, '=== onAttachedToEngine ===');
this.channel = new MethodChannel(binding.getBinaryMessenger(), 'custom_clipboard');
this.channel.setMethodCallHandler(this);
}
引擎绑定后注册通道与 Handler。getUniqueClassName() 返回 "CustomClipboardPlugin",与 pubspec.yaml 中的 pluginClass 一致。
5.1.3 清空剪贴板
private async clearClipboard(result: MethodResult): Promise<void> {
try {
await pasteboard.getSystemPasteboard().clearData();
hilog.info(0x0000, TAG, 'clearClipboard success');
result.success(null);
} catch (e) {
hilog.error(0x0000, TAG, 'clearData failed: %{public}s', JSON.stringify(e));
result.error('-1', 'clearData failed: ' + JSON.stringify(e), null);
}
}
pasteboard.getSystemPasteboard().clearData() 直接移除系统剪贴板内容,语义与 Android ClipboardManager.clearPrimaryClip()、iOS UIPasteboard.general.string = "" 一致。操作成功返回 null,失败时将异常序列化后通过 result.error 回传。
5.1.4 处理 MethodChannel 命令
onMethodCall(call: MethodCall, result: MethodResult): void {
if (call.method == 'clearClipboard') {
this.clearClipboard(result);
} else {
result.notImplemented();
}
}
插件只处理 clearClipboard 方法,其余方法返回 notImplemented(),便于上层捕获“方法未实现”异常。
5.1.5 Engine 解绑时释放资源
onDetachedFromEngine(_binding: FlutterPluginBinding): void {
hilog.info(0x0000, TAG, '=== onDetachedFromEngine ===');
if (this.channel != null) {
this.channel.setMethodCallHandler(null);
this.channel = null;
}
}
Flutter Engine 销毁时清理通道 Handler,避免重复注册或内存泄漏。
5.2 声明插件和宿主权限
custom_clipboard 的清空与写入操作本身不需要特殊权限;读取剪贴板需要系统受限权限 ohos.permission.READ_PASTEBOARD,但该权限受签名 ACL 约束,普通调试签名无法直接声明。当前工程按如下方式处理:
5.2.1 插件 HAR 的权限
插件 HAR 模块不需要声明任何权限:
{
"module": {
"name": "custom_clipboard",
"type": "har",
"deviceTypes": ["default", "tablet"]
}
}
5.2.2 应用 entry 的权限
示例工程的 entry 声明了 INTERNET 和受限权限 READ_PASTEBOARD:
{
"module": {
"requestPermissions": [
{"name": "ohos.permission.INTERNET"},
{
"name": "ohos.permission.READ_PASTEBOARD",
"reason": "$string:read_pasteboard_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
权限原因资源在 custom_clipboard/example/ohos/entry/src/main/resources/base/element/string.json 中声明:
{
"name": "read_pasteboard_reason",
"value": "读取剪贴板内容,用于粘贴文本、验证码等信息"
}
ohos.permission.READ_PASTEBOARD 是 restricted 级别权限。调试签名下直接声明会导致安装失败(9568289 grant request permissions failed),因此调试阶段通常不声明该权限,getClipboard() 会返回 null;正式发布时若需静默读取,必须使用携带 ACL 的正式签名证书,并在 signing/ohos_acl_profile*.json 中显式放行该权限。
5.3 注册并导出插件
custom_clipboard/custom_clipboard/pubspec.yaml 通过以下配置声明 OHOS 插件类:
flutter:
plugin:
platforms:
ohos:
pluginClass: CustomClipboardPlugin
注意:根目录 pubspec.yaml 不直接声明平台,仅管理 monorepo;实际插件声明在 custom_clipboard/pubspec.yaml 中。
插件的 custom_clipboard/ohos/index.ets 需要导出实现:
import CustomClipboardPlugin from './src/main/ets/components/plugin/CustomClipboardPlugin';
export default CustomClipboardPlugin;
执行 flutter pub get 和构建后,Flutter 工具会为应用生成 GeneratedPluginRegistrant.ets。通常不应手工编辑该文件,因为下次构建可能覆盖它。
注册异常的排查步骤见第九节 MissingPluginException。
5.4 检查 example 的 OHOS 应用结构
本例的 custom_clipboard/example/ohos/build-profile.json5 在 products 中设置版本:
{
"app": {
"signingConfigs": [
{
"name": "default",
"type": "HarmonyOS",
"material": { ... }
}
],
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.0.0(12)",
"runtimeOS": "HarmonyOS"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": ["default"]
}
]
}
]
}
配置后,在 DevEco Studio 中执行一次 Sync Project。如果 entry 模块正常识别,Project 视图中会出现 entry,并能打开 entry 模块的签名配置。注意 signingConfig 中的 material 为本机签名材料,提交前不应随仓库一起上传。
六、补全交付文件并提交适配分支
6.1 除代码外还要补全哪些文件
代码适配完成后,还需要整理安装说明、接口文档、版本记录和开源信息。接收仓库有专用模板时,按其格式填写:
| 文件 | 应写清楚的内容 |
|---|---|
README.OpenSource | 上游名称、源码地址、适配版本或提交、版权及许可证信息;按仓库模板列出第三方依赖 |
README.md | 原项目说明、OHOS 支持入口、配套 Demo 和文档链接;保留上游信息 |
README.OpenHarmony_CN.md | 简介、AtomGit 安装方式、版本对应关系、环境约束、权限、接口表、示例、已验证范围和遗留问题 |
README.OpenHarmony.md | 与中文说明对应的英文文档 |
CHANGELOG.OpenHarmony.md | OHOS 新增能力、适配版本、兼容限制与测试范围 |
LICENSE / NOTICE | 保留上游许可证;NOTICE 按许可证和原项目要求保留或补充 |
example/README.md | 依赖方式、运行目录、签名、操作步骤与效果图;覆盖复制、读取、清空 |
pubspec.yaml、ohos/oh-package.json5 | 核对包名、版本、插件注册、仓库地址、许可证和依赖 |
.gitignore | 忽略构建缓存及本机签名材料,不漏提交必要源码和配置 |
README.OpenSource 记录库本身的来源与版本。本例的主包名为 custom_clipboard,版本为 0.0.6,采用 MIT 许可证;Flutter 和 HarmonyOS SDK 版本写入环境说明。
6.2 提交前检查
提交前先完成第八节的插件、example 和真机测试,再从根目录检查改动:
git branch --show-current
git diff --check
git status --short
git diff --stat
git diff
检查 diff 中的接口、平台注册和依赖变化,移除本机路径及签名信息,并使文档中的版本和分支与提交内容一致。
6.3 提交并推送到 AtomGit
文档和代码整理完成后,在根目录暂存并提交。文件名按项目实际情况调整:
git add custom_clipboard/ohos custom_clipboard/pubspec.yaml custom_clipboard/example .gitignore
git add custom_clipboard/README.md custom_clipboard/README.OpenSource custom_clipboard/README.OpenHarmony_CN.md
git add custom_clipboard/README.OpenHarmony.md custom_clipboard/CHANGELOG.OpenHarmony.md
git add docs/BLOG_OpenHarmony_Adaptation.md docs/ohos-test-evidence
git diff --cached --check
git diff --cached --stat
git diff --cached
git commit -m "feat: add OHOS support for custom_clipboard 0.0.6"
git remote -v
git branch --show-current
git tag 0.0.6-ohos-1.0.0-beta.1
git push -u origin main
git push origin 0.0.6-ohos-1.0.0-beta.1
DevEco 可能向 custom_clipboard/example/ohos/build-profile.json5 写入本机签名配置,需要在提交前从暂存内容中移除。推送时,origin 应指向自己的 AtomGit/GitHub 仓库,当前分支为 main,并同步推送 OHOS TAG 0.0.6-ohos-1.0.0-beta.1。
推送后在 AtomGit 发起合并请求,说明上游来源和版本、OHOS 实现范围、依赖及权限、测试环境、操作结果、已知限制,并附 Demo 运行图。目标分支和评审流程以接收仓库要求为准。
七、使用根目录 example 演示接入
仓库自带 custom_clipboard/example/,可以直接用来调试插件和体验复制、读取、清空流程。
7.1 本地适配时使用路径依赖
当前 custom_clipboard/example/pubspec.yaml 的依赖是:
dependencies:
flutter:
sdk: flutter
custom_clipboard:
path: ../
custom_clipboard_platform_interface: ^0.0.2
../ 相对于 custom_clipboard/example/pubspec.yaml 指向插件主包根目录,修改主包插件后可直接联调。custom_clipboard_platform_interface 由 example 直接依赖,便于在页面中调用 CustomClipboardPlatform.instance.clearClipboard() 走原生插件路径。
7.2 通过 AtomGit 引入插件
业务应用通过 AtomGit 引入时,将 custom_clipboard 的 path 配置替换为下面的 Git 依赖。这里固定到本文使用的 TAG:
dependencies:
flutter:
sdk: flutter
custom_clipboard:
git:
url: https://atomgit.com/{your-org}/custom_clipboard.git
ref: 0.0.6-ohos-1.0.0-beta.1
使用自己的适配版本时,先推送 TAG,再将 url 改为对应仓库,ref 改为 0.0.6-ohos-1.0.0-beta.1。正式发布后可固定到 TAG 或 commit。
从 custom_clipboard/example/ 目录执行:
cd custom_clipboard/example
flutter pub get
flutter pub deps
检查 custom_clipboard/example/pubspec.lock 中 custom_clipboard 的来源为 git,并核对 url、ref 和 resolved-ref。同时检查没有 dependency_overrides 或 pubspec_overrides.yaml 将其覆盖回本地依赖,确认应用使用的是 Git 依赖。
7.3 调用接口实现复制、读取与清空
下面的页面展示输入框、复制、读取、清空按钮与操作日志,可用于 custom_clipboard/example/lib/main.dart。完整 Demo 还包含受限权限提示与 SnackBar 反馈。
import 'package:custom_clipboard/custom_clipboard.dart';
import 'package:custom_clipboard_platform_interface/custom_clipboard_platform_interface.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static const String kDefaultClipboardText = 'Hello OpenHarmony from custom_clipboard';
final TextEditingController _inputController = TextEditingController();
final List<String> _logs = <String>[];
String? _clipboardData;
void _log(String message) => setState(() => _logs.insert(0, message));
Future<void> _copyInputToClipboard() async {
final String text = _inputController.text.trim().isEmpty
? kDefaultClipboardText
: _inputController.text.trim();
_log('复制内容: "$text"');
await Clipboard.setData(ClipboardData(text: text));
_log('已写入剪贴板');
}
Future<void> _getClipboard() async {
final data = await getClipboard();
setState(() => _clipboardData = data);
_log('读取剪贴板: "${data ?? 'null'}"');
}
Future<void> _clearClipboard() async {
_log('清空剪贴板(库API)...');
await clearClipboard();
_log('清空剪贴板(原生插件 clearData)...');
await CustomClipboardPlatform.instance.clearClipboard();
await _getClipboard();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('CustomClipboard Example')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text('Clipboard data: ${_clipboardData ?? ''}'),
TextField(controller: _inputController),
ElevatedButton(
onPressed: _copyInputToClipboard,
child: const Text('Copy Input to Clipboard'),
),
ElevatedButton(
onPressed: _getClipboard,
child: const Text('Get Clipboard'),
),
ElevatedButton(
onPressed: _clearClipboard,
child: const Text('Clear Clipboard'),
),
Expanded(
child: ListView.builder(
itemCount: _logs.length,
itemBuilder: (_, i) => Text(_logs[i]),
),
),
],
),
),
);
}
}
7.4 页面退出时资源处理
异步回调先检查 mounted,避免页面销毁后继续调用 setState。dispose 中释放 TextEditingController:
void dispose() {
_inputController.dispose();
super.dispose();
}
由于 custom_clipboard 没有持续订阅或事件流,退出页面时不需要取消订阅;原生插件在 onDetachedFromEngine 中会自动清理通道 Handler。
八、验证、构建与鸿蒙设备运行效果
8.1 分别验证插件与 example
从插件仓库根目录执行:
flutter pub get
flutter analyze
flutter test
cd custom_clipboard/example
flutter pub get
flutter analyze
flutter test
注意:本仓库当前未包含 custom_clipboard/test/ 目录,因此 flutter test 在插件主包可能提示无测试文件。example 目录同样未包含 widget 测试,验证重点放在真机运行与接口行为上。建议后续补充对 clearClipboard() 返回结果、getClipboard() 在受限权限下返回 null、以及 CustomClipboardPlatform.instance.clearClipboard() 命中原生通道的单元测试。
Dart 测试覆盖接口和页面逻辑,剪贴板读写及权限行为还需要在鸿蒙设备上验证。
8.2 确认设备连接
hdc list targets
flutter devices
设备首次连接电脑时,需要在手机端确认调试授权。列表为空时,检查 USB 连接、调试模式和电脑授权。
8.3 配置签名
真机安装的 HAP 通常需要有效签名。推荐使用 DevEco Studio 为 custom_clipboard/example/ohos/entry 模块配置自动签名:
- 用 DevEco Studio 打开
custom_clipboard/example/ohos,不是仓库根目录; - 等待工程 Sync 成功,确认 Project 视图中存在
entry模块; - 打开 File > Project Structure > Signing Configs;
- 为
defaultproduct 选择或生成签名; - 确认设备、应用包名、证书和 Profile 匹配;
- 若需要调试
getClipboard()读取能力,必须使用携带ohos.permission.READ_PASTEBOARDACL 的正式签名证书; - 再回到终端执行 Flutter 构建或运行。
签名材料保存在本机,公开仓库中只保留构建所需的通用配置。
8.4 运行示例
以下命令在 custom_clipboard/example/ 目录执行,将 <device-id> 替换为设备列表中的实际 ID:
flutter run -d <device-id>
也可以先构建 HAP:
flutter build hap --debug
典型产物位于:
custom_clipboard/example/build/ohos/hap/
目录中通常包含已签名和未签名 HAP。真机安装应选择与当前设备匹配的已签名产物。
8.5 在设备上测试复制、读取与清空
- 打开应用,确认初始状态 Clipboard data 为空;
- 在输入框输入文本(或留空使用默认文案),点击 Copy Input to Clipboard;
- 观察日志区出现“复制内容”和“已写入剪贴板”;
- 点击 Get Clipboard:
- 若使用普通调试签名或未声明 ACL,
getClipboard()返回null,界面显示受限权限提示; - 若使用含
READ_PASTEBOARDACL 的正式签名,应能读回刚才写入的文本;
- 若使用普通调试签名或未声明 ACL,
- 点击 Clear Clipboard:
- 库 API 路径走引擎
Clipboard.setData(''); - 原生插件路径走
CustomClipboardPlatform.instance.clearClipboard()→pasteboard.clearData();
- 库 API 路径走引擎
- 清空后再次点击 Get Clipboard,确认剪贴板状态为无内容或读取为
null; - 多次点击复制、读取、清空,确认无崩溃、无重复错误日志。
8.6 鸿蒙设备运行效果
完成适配后,Flutter 应用能够在鸿蒙设备上写入、读取(受限权限下返回 null)和清空剪贴板。下面是真机运行时的关键界面:
| Example 启动 | 复制后状态与日志 | 清空后状态 |
|---|---|---|
| 应用初始界面,Clipboard data 为空 | 写入剪贴板,日志显示复制内容与受限提示 | 清空后剪贴板无内容,日志显示清空成功 |
custom_clipboard 的读取能力受设备签名与 READ_PASTEBOARD ACL 限制。即使系统版本满足要求,不同签名场景下 getClipboard() 的表现也可能不同,需要在目标设备和签名条件下测试。
九、FAQ:适配过程与使用问题
9.1 Missing SDK components
典型错误如下:
Missing SDK components. SDK path: ...,
missing components: toolchains,ets,js,native,previewer.
这个错误发生在 Hvigor 同步阶段。通常需要检查构建工具使用的 SDK 路径、组件是否完整,以及 Hvigor 与 SDK 的版本是否匹配。
处理顺序:
- 在 DevEco Studio SDK Manager 中确认 API 26 组件已经下载完整;
- 检查 Flutter 和 DevEco Studio 使用的 SDK 路径是否一致;
- 避免误用
/Applications/DevEco-Studio.app/Contents/sdk之类的不完整目录; - 确认 SDK 根目录下存在
toolchains、ets、js、native、previewer; - 执行
flutter config --ohos-sdk <正确路径>; - 重新执行
flutter doctor -v和 DevEco Studio Sync。
因为 Sync 和 Compile 首先读取 Mac 本地 SDK。手机 API 版本只在部署、安装和运行时参与兼容判断。即使完全不连接手机,本地 SDK 不完整时也会得到相同错误。
当前工程的 compatibleSdkVersion 是 API 12,因此 API 24 在安装版本门槛上是满足的;但剪贴板读取能力还受签名证书 ACL 限制。
9.2 DevEco Studio 中看不到 entry 模块
插件的 custom_clipboard/ohos/ 目录是 HAR 模块,可安装应用的 entry 模块位于 custom_clipboard/example/ohos/entry。
请直接使用 DevEco Studio 打开:
custom_clipboard/example/ohos
如果仍看不到 entry,先解决 SDK Sync 错误,再检查 custom_clipboard/example/ohos/build-profile.json5 的 modules 是否包含 ./entry。同步失败时,Project Structure 无法正确解析模块,签名界面也可能不显示 entry。
9.3 无法手动签名
签名配置依附于可构建的应用模块和 product。只有 HAR 插件模块、工程 Sync 失败,或者打开了错误目录时,DevEco Studio 都可能无法提供 entry 签名入口。
建议先确认:
- 打开的是
custom_clipboard/example/ohos; - SDK 组件完整并且 Sync 成功;
entry的模块类型为entry;defaultproduct 和 target 已正确关联;- 当前账号、证书和调试设备状态有效。
9.4 能安装但清空无效
按以下顺序检查:
- UI 日志是否出现“清空剪贴板(库API)…”与“清空剪贴板(原生插件 clearData)…”;
- 插件
ohos/index.ets是否正确导出CustomClipboardPlugin; pubspec.yaml中pluginClass是否写为CustomClipboardPlugin;hilog中是否出现CustomClipboardPlugin: clearClipboard success;- 调用的是
clearClipboard()(走引擎 setData(‘’))还是CustomClipboardPlatform.instance.clearClipboard()(走原生 clearData()); - 是否使用了
Clipboard.setData('')后又被其他应用或输入法写回内容; - 目标设备型号和系统是否真正支持
@ohos.pasteboard.clearData()。
9.5 MissingPluginException
这通常表示 Dart 通道找不到已注册的原生插件。新增原生插件后需要重新构建应用。从 custom_clipboard/example/ 目录执行:
cd custom_clipboard/example
flutter clean
flutter pub get
flutter run -d <device-id>
如果仍然出现,检查自动生成的插件注册文件中是否包含 CustomClipboardPlugin,同时核对 custom_clipboard/pubspec.yaml、custom_clipboard/ohos/index.ets 和 custom_clipboard/ohos/oh-package.json5。
9.6 调用库 API 没走原生插件
这是预期行为:custom_clipboard/lib/custom_clipboard.dart 中的 clearClipboard() 对非 Android/iOS 平台回退到 Clipboard.setData(ClipboardData(text: '')),因此 OHOS 上调用 clearClipboard() 实际走引擎内置通道,不会命中 CustomClipboardPlugin.ets。
若需要调用原生插件的 pasteboard.clearData(),请使用:
await CustomClipboardPlatform.instance.clearClipboard();
如需让库 API 在 OHOS 上也走原生插件,可以在 custom_clipboard.dart 中增加 Platform.isOhos 分支,但本适配选择保持 Dart 层零改动。
9.7 编译成功但安装失败
常见原因包括:
- HAP 未签名或使用了错误的 Profile;
- 设备未加入调试设备列表;
- 包名与签名 Profile 不匹配;
- 安装包的
compatibleSdkVersion高于设备 API; - 手机上已经安装了使用不同证书签名的同包名应用;
- 在 module.json5 中声明了
ohos.permission.READ_PASTEBOARD但签名证书未携带对应 ACL(报错9568289 grant request permissions failed)。
根据安装错误码区分签名、版本、包名冲突和受限权限问题,再处理对应配置。
9.8 flutter create 不认识 ohos,或包名不合法
先执行 flutter --version,确认使用的是 OHOS 版工具链,环境配置回到第二节的社区链接核对。包名报错时,确认当前目录包含目标插件的 pubspec.yaml,并显式传入 --project-name custom_clipboard;仓库名 custom_clipboard 可直接作为 Dart 包名。生成后检查 diff,再补充 ArkTS 业务实现。
9.9 AtomGit 依赖提示找不到分支或无权限
先检查 URL 是否指向已同步的目标仓库,再确认 0.0.6-ohos-1.0.0-beta.1 TAG 已推送。TAG 尚未推送时,可以先使用第七节的提交号 20fd1dd621ca74b9035e4ad05cf35b75d9445cd9 作为 ref。私有仓库还需在本机配置 Git 认证。
9.10 改了本地 ArkTS,Demo 为什么没变化
先检查 custom_clipboard/example/pubspec.yaml:Git 依赖读取远程提交,不会自动读取本地插件改动。本地联调切回 path: ../;测试远程版本则先提交推送,再更新依赖并核对 pubspec.lock 的 resolved-ref。原生代码变动后停止应用并重新构建运行,不能只做 Dart 热重载。
9.11 getClipboard() 返回 null
这是 OHOS 受限权限的正常表现。ohos.permission.READ_PASTEBOARD 属于 restricted 权限,第三方应用需要使用携带 ACL 的正式签名证书声明方可读取;调试签名下声明该权限会导致安装失败,因此默认不声明,引擎返回 null。
排查顺序:
- 确认应用已声明
ohos.permission.READ_PASTEBOARD并配置了reason和usedScene; - 确认签名 Profile 的
acls.allowed-acls包含ohos.permission.READ_PASTEBOARD; - 确认 Profile 的
bundle-name与 entry 的 bundleName 一致; - 重新签名、重新安装后测试;
- 若仅验证清空能力,无需处理此问题;
clearClipboard()与写入不受READ_PASTEBOARD限制。
相关链接
更多推荐





所有评论(0)