Flutter 三方库 clipboard_watcher 的鸿蒙化适配指南
CPF-Flutter 鸿蒙社区:https://atomgit.com/CPF-Flutter
本文配套仓库:https://atomgit.com/oh-flutter/clipboard_watcher
适配版本:clipboard_watcher 0.3.0;参考分支:feat/ohos_clipboard_watcher_0.3.0;本文核对提交:e8c32e033a53477db636e9a95ced5e67134a34ac。
clipboard_watcher 是一个用于监听系统剪贴板变化的 Flutter 插件。在翻译、验证码辅助录入、跨页面粘贴提示等场景中,业务不仅需要主动读写剪贴板,还希望在内容发生变化时立即收到通知。原插件已经为多个桌面端和移动端提供实现,但 Flutter 应用迁移到鸿蒙平台后,如果插件没有声明 ohos 平台入口,也没有对应的 ArkTS 原生实现,Dart 层调用 start 时就无法到达系统剪贴板。
本文以 clipboard_watcher 0.3.0 为例,从源码准备、Dart 接口分析、MethodChannel 协议、OHOS 原生实现、example 接入、自动化测试和常见问题排查几个环节,完整说明一次 Flutter 三方插件鸿蒙化适配。适配后的目标不是简单做到“能够编译”,而是在重复启动、停止监听、引擎销毁和异常重试等情况下仍保持稳定行为。

1. 插件简介与适配目标
Flutter 自带 Clipboard API,可以在业务主动发起操作时读取或写入剪贴板,但它没有提供一个跨平台的系统变化监听接口。clipboard_watcher 用观察者模式补上了这一能力:页面实现 ClipboardListener,向全局 clipboardWatcher 注册监听者,再通过 start 和 stop 控制原生订阅。系统剪贴板更新后,插件调用 onClipboardChanged,业务收到通知后决定是否刷新界面或执行后续逻辑。
剪贴板往往包含验证码、地址、账号或其他敏感信息,因此适配时要控制能力边界。本文实现只订阅 SystemPasteboard 的 update 事件,并把“发生变化”这一事实通知 Dart,不读取、不记录、也不上传剪贴板内容。应用如果确实需要读取文本,应在业务层单独评估系统限制与隐私要求,不能把读取动作暗藏在监听插件内部。
本次适配需要满足以下目标:
-
保持原有 Dart API 不变,现有 Flutter 业务不需要增加鸿蒙专用分支。
-
新增 ohos 插件声明、HAR 模块和 ArkTS 实现,让 MethodChannel 能够正确注册。
-
重复调用 start 或 stop 时保持幂等,避免一次剪贴板更新触发多个回调。
-
停止监听或 FlutterEngine 销毁后,不再向已经失效的 Dart 通道派发事件。
-
系统订阅或取消订阅失败时,把错误返回 Flutter,并允许后续再次清理或重试。
-
补齐 example、自动化测试和鸿蒙说明文档,形成可检查、可复现的适配交付。
插件的正常事件链路为:Flutter 页面注册监听者并调用 start,Dart 层通过 MethodChannel 发送 start,ArkTS 插件订阅系统剪贴板 update 事件;剪贴板发生变化时,ArkTS 调用 onClipboardChanged,Dart 层再把事件分发给仍然有效的 ClipboardListener。stop 的方向相反,它解除系统监听并使旧回调失效。
2. 环境准备
开始适配前,先确认终端使用的是支持 OHOS 的 Flutter 工具链,而不是系统中另一套普通 Flutter SDK。DevEco Studio、命令行构建工具和 Flutter 配置看到的 HarmonyOS SDK 目录也要一致,否则工程可能在插件代码参与编译之前就因为 SDK 版本解析失败而退出。
flutter --version
flutter doctor -v
hdc list targets
flutter devices

| 项目 | 版本或配置 | 用途 |
|---|---|---|
| Flutter OHOS SDK | 3.44.9+ohos-0.0.1-canary1 | Flutter 编译与 OHOS 平台工具链 |
| Dart SDK | 3.12.2 | Dart 语言与包管理环境 |
| DevEco Studio | 26.0.0 Release | 鸿蒙工程同步、调试与设备运行 |
| HarmonyOS SDK | 7.0.0(API 26) | 当前批次要求的开发套件 |
| targetSdkVersion | 26.0.0 | example 面向的目标版本 |
| compatibleSdkVersion | 5.1.0(18) | example 声明的最低兼容版本 |
| 插件版本 | 0.3.0 | pubspec.yaml 中的包版本 |
| 原生语言与产物 | ArkTS / HAR | OHOS 插件实现及应用依赖形式 |
版本号需要分清含义。HarmonyOS SDK 7.0.0 对应 API 26;工程中的 targetSdkVersion 使用 26.0.0;compatibleSdkVersion 为 5.1.0(18),表示应用工程声明的最低兼容 API。最低版本满足安装条件,不代表所有系统行为都已验证,剪贴板监听仍要在目标设备上实际运行。

{
"name": "default",
"compatibleSdkVersion": "5.1.0(18)",
"runtimeOS": "HarmonyOS",
"targetSdkVersion": "26.0.0"
}
3. 从源码仓库准备适配工程
3.1 拉取适配仓库并固定代码版本
适配已有插件时,应先核对包名、版本、许可证和起始提交,并保留上游提交历史。下面直接从 AtomGit 拉取本文配套分支。使用固定提交复现,可以避免分支后续更新导致代码和文章不一致。
git clone -b feat/ohos_clipboard_watcher_0.3.0 \
https://atomgit.com/oh-flutter/clipboard_watcher.git
cd clipboard_watcher
git status --short --branch
git rev-parse HEAD

3.2 识别插件中需要补齐的层次
Flutter 平台插件通常包含三层:lib 目录提供业务可见的 Dart API;ohos 目录负责 FlutterEngine、MethodChannel 与系统能力之间的桥接;example 是可安装应用,用来验证插件在真实运行环境中的行为。只增加一个 ArkTS 文件而没有平台声明、模块导出和示例工程,仍不能算完整适配。
clipboard_watcher/
├── lib/
│ ├── clipboard_watcher.dart
│ └── src/
│ ├── clipboard_listener.dart
│ └── clipboard_watcher.dart
├── ohos/
│ ├── index.ets
│ ├── oh-package.json5
│ ├── build-profile.json5
│ ├── hvigorfile.ts
│ ├── src/main/
│ │ ├── ets/ClipboardWatcherPlugin.ets
│ │ └── module.json5
│ └── test/clipboard_watcher_lifecycle.test.cjs
├── example/
│ ├── lib/main.dart
│ ├── ohos/
│ └── test/widget_test.dart
├── test/clipboard_watcher_test.dart
├── README.OpenHarmony_CN.md
└── pubspec.yaml
3.3 在 pubspec.yaml 中声明 ohos 平台
Flutter 工具通过 pubspec.yaml 判断插件支持哪些平台。ohos 下的 pluginClass 必须与 ArkTS 默认导出的类一致。缺少声明时,Dart 依赖可以正常下载,但原生插件不会进入自动注册流程,运行时容易出现 MissingPluginException。
flutter:
plugin:
platforms:
ohos:
pluginClass: ClipboardWatcherPlugin
插件包名仍是 clipboard_watcher,版本仍是 0.3.0。适配平台不应随意改变 Dart 包名或公开类名,否则现有应用迁移时还要修改 import 和业务代码,增加了与上游继续同步的成本。
3.4 检查 OHOS 模块类型与入口
ohos/src/main/module.json5 将模块类型声明为 har。HAR 本身不是可安装应用,它会被 example/ohos/entry 依赖并打入最终应用。ohos/index.ets 则导出插件实现,供注册流程加载。
export { default } from './src/main/ets/ClipboardWatcherPlugin';
{
"module": {
"name": "clipboard_watcher",
"type": "har",
"deviceTypes": ["default", "tablet", "2in1"]
}
}
4. Dart 接口与通道协议分析
4.1 公开监听接口
原插件对外暴露 ClipboardListener 和全局单例 clipboardWatcher。监听者只需要实现 onClipboardChanged,不需要了解 OHOS 的 pasteboard 类型。保持这一层不变,是适配对业务无侵入的关键。
abstract mixin class ClipboardListener {
void onClipboardChanged() {}
}
ClipboardWatcher 内部使用 ObserverList 保存监听者,并在构造时为 MethodChannel 安装回调处理器。listeners getter 会复制当前列表,避免分发过程中直接遍历可变集合;正式回调前还会确认监听者没有被移除。
void addListener(ClipboardListener listener) {
_listeners.add(listener);
}
void removeListener(ClipboardListener listener) {
_listeners.remove(listener);
}
4.2 方法通道名称必须完全一致
Dart 与 ArkTS 之间没有自动推断机制,两端需要同时使用 clipboard_watcher 作为通道名称。任何大小写、下划线或拼写差异都会导致调用落不到插件。适配时可以先把协议整理成表格,再逐项核对方法名、方向和参数。
| 方向 | 方法 | 参数 | 含义 |
|---|---|---|---|
| Dart → OHOS | start | null | 开始订阅系统剪贴板变化 |
| Dart → OHOS | stop | null | 停止订阅并释放本插件回调 |
| OHOS → Dart | onClipboardChanged | null | 仅通知内容发生变化,不携带剪贴板数据 |
final MethodChannel _channel =
const MethodChannel('clipboard_watcher');
Future<void> start() async {
await _channel.invokeMethod('start');
}
Future<void> stop() async {
await _channel.invokeMethod('stop');
}
4.3 原生事件如何回到业务监听者
ArkTS 调用 onClipboardChanged 后,Dart 的 _methodCallHandler 会遍历监听者并触发同名回调。这里没有传递字符串或二进制数据,因此插件不负责内容去重。系统是否对相同文本触发 update,应以实际设备行为为准;业务如果要根据文本值去重,应在主动读取内容后自行处理。
Future<void> _methodCallHandler(MethodCall call) async {
for (final ClipboardListener listener in listeners) {
if (!_listeners.contains(listener)) {
return;
}
if (call.method == 'onClipboardChanged') {
listener.onClipboardChanged();
} else {
throw UnimplementedError();
}
}
}
5. 补全 OHOS 原生实现与工程配置
5.1 引入 Flutter 与系统剪贴板能力
ClipboardWatcherPlugin 同时实现 FlutterPlugin 和 MethodCallHandler。前者负责跟随 FlutterEngine 完成注册和释放,后者负责处理 Dart 发来的 start、stop 命令。系统剪贴板能力来自 @kit.BasicServicesKit 中的 pasteboard。
import {
FlutterPlugin,
FlutterPluginBinding,
MethodCall,
MethodCallHandler,
MethodChannel,
MethodResult,
} from '@ohos/flutter_ohos';
import { pasteboard } from '@kit.BasicServicesKit';
类中保存 MethodChannel、SystemPasteboard、监听状态、订阅代次和回调引用。回调引用必须保存下来,因为取消订阅时需要把同一个函数传给 off;临时新建一个函数无法匹配之前注册的回调。
private channel: MethodChannel | null = null;
private board: pasteboard.SystemPasteboard | null = null;
private listening: boolean = false;
private generation: number = 0;
private onUpdate: (() => void) | null = null;
5.2 连接 FlutterEngine
插件接入引擎时创建通道并设置方法处理器。getUniqueClassName 返回的名称与插件类一致,通道名称则与 Dart 层完全相同。
getUniqueClassName(): string {
return 'ClipboardWatcherPlugin';
}
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.channel = new MethodChannel(
binding.getBinaryMessenger(),
'clipboard_watcher',
);
this.channel.setMethodCallHandler(this);
}
5.3 订阅 SystemPasteboard 更新事件
start 首先检查 listening,已经监听时直接返回,这样 Dart 重复调用 start 也只会向系统注册一次。若上一次取消订阅失败而 board 仍然存在,则先再次尝试 stop,清理成功后才建立新订阅。
订阅成功前不能提前把 listening 设置为 true。board.on 抛出异常时,状态保持为未监听,错误由上层捕获并返回 Flutter;下一次 start 仍有机会重试。只有系统注册完成后,才保存回调、SystemPasteboard 和监听状态。
private start(): void {
if (this.listening) {
return;
}
if (this.board !== null) {
this.stop();
}
const board = pasteboard.getSystemPasteboard();
const generation = ++this.generation;
const callback = (): void => {
if (this.listening && generation === this.generation) {
this.channel?.invokeMethod('onClipboardChanged', null);
}
};
board.on('update', callback);
this.onUpdate = callback;
this.board = board;
this.listening = true;
}
generation 用于隔离异步残留事件。系统 update 已进入队列后,业务可能紧接着调用 stop,再重新调用 start。如果旧回调晚于新订阅执行,仅检查 listening 会把它误认为新一轮事件。每次启停都推进代次,旧回调携带的 generation 与当前值不同,因此不会再通知 Dart。
5.4 停止监听并保留失败重试能力
stop 先把 listening 设为 false,并推进 generation。这样即使系统注销失败,原回调也不会继续向 Dart 发送消息。off 成功后再清空 board 与 onUpdate;如果 off 抛错,这两个引用仍然保留,后续 start 或 stop 可以再次尝试清理,而不是丢失唯一可注销的回调。
private stop(): void {
this.listening = false;
this.generation++;
if (this.board !== null && this.onUpdate !== null) {
this.board.off('update', this.onUpdate);
this.board = null;
this.onUpdate = null;
}
}
5.5 处理 Flutter 命令与错误
onMethodCall 只识别 start 和 stop。未知方法返回 notImplemented,方便 Flutter 明确区分“插件没有实现”与普通业务异常。系统调用抛出的错误统一转换为 clipboard_watcher_error,Dart 侧会收到 PlatformException,页面可以展示错误或提示用户重试。
onMethodCall(call: MethodCall, result: MethodResult): void {
try {
switch (call.method) {
case 'start':
this.start();
result.success(null);
break;
case 'stop':
this.stop();
result.success(null);
break;
default:
result.notImplemented();
}
} catch (error) {
result.error(
'clipboard_watcher_error',
(error as Error).message,
null,
);
}
}
5.6 引擎解绑时释放资源
FlutterEngine 销毁后,原生回调不能继续访问旧 MethodChannel。解绑时先移除 MethodCallHandler 并将 channel 置空,再停止逻辑监听和系统订阅。即使系统注销抛错,listening 与 generation 也已经阻止旧事件投递给失效引擎。
onDetachedFromEngine(binding: FlutterPluginBinding): void {
this.channel?.setMethodCallHandler(null);
this.channel = null;
this.listening = false;
try {
this.stop();
} catch (_) {
// 不再向已解绑的 FlutterEngine 投递事件。
}
}
5.7 权限与隐私边界
当前实现只调用 getSystemPasteboard、on 和 off,没有调用获取剪贴板内容的接口,也没有在 module.json5 中为此新增读取权限。事件回传参数固定为 null。这个结论只适用于插件当前实现;如果业务收到变化通知后主动读取内容,仍需按照目标系统版本的访问策略自行处理。
6. 补全交付文件并提交适配分支
6.1 代码之外需要交付什么
可验收的三方库适配不应只有 ohos/src/main/ets 下的一份实现文件。README.OpenHarmony_CN.md 需要说明安装方式、支持边界、验证环境和已知限制;英文说明可放在 README.OpenHarmony.md;CHANGELOG 记录新增 OHOS 支持;LICENSE 与上游历史应完整保留;example 要能够演示 start、stop 和变化回调;测试则覆盖 Dart 协议、页面交互和原生生命周期。
| 文件或目录 | 检查重点 |
|---|---|
| pubspec.yaml | 包版本不变,新增 ohos / ClipboardWatcherPlugin 平台声明 |
| ohos/ | HAR 模块、入口导出、ArkTS 实现和生命周期测试完整 |
| example/ | 存在 OHOS entry,页面能够启动、停止并显示变化次数 |
| test/ | 验证通道命令、事件分发、移除监听者和错误透传 |
| README.OpenHarmony_CN.md | 安装、环境、能力边界、验证记录与限制可复现 |
| LICENSE 与提交历史 | 保留原许可证与上游来源信息 |
6.2 提交前检查
提交前先查看实际差异,排除构建产物、本机配置、缓存文件以及与本次适配无关的修改。git diff --check 可以发现行尾空格等基础问题,git diff --stat 和 git diff 用于确认交付范围。不要直接使用包含大量未审查文件的全量暂存。
git status --short
git diff --check
git diff --stat
git diff
git log --oneline -5
推送适配分支后,在 AtomGit 的合并请求中写清楚上游版本、OHOS 实现范围、系统能力、测试环境、测试结果和已知限制。当前分支命名为 feat/ohos_clipboard_watcher_0.3.0,同一个库应先查重,避免重复提交适配奖励。
git push -u origin feat/ohos_clipboard_watcher_0.3.0
7. 使用 example 演示插件接入
7.1 本地开发使用路径依赖
仓库自带 example,适配过程中使用 path: ../ 指向插件根目录。这样修改 Dart 或 ArkTS 后可以直接重新构建联调,不需要先把每次变更推送到远端。
dependencies:
flutter:
sdk: flutter
clipboard_watcher:
path: ../
7.2 业务工程通过 AtomGit 引入
业务工程验证远端适配版本时,把 path 依赖改为 AtomGit Git 依赖。试用阶段可以固定分支,正式接入建议锁定验收提交或标签,以免分支推进造成构建结果变化。
dependencies:
clipboard_watcher:
git:
url: https://atomgit.com/oh-flutter/clipboard_watcher.git
ref: feat/ohos_clipboard_watcher_0.3.0
执行 flutter pub get 后,检查 pubspec.lock 中 clipboard_watcher 的 url、ref 与 resolved-ref。若工程存在 dependency_overrides 或 pubspec_overrides.yaml,也要确认它没有把 Git 依赖覆盖回其他本地目录。
7.3 页面实现 ClipboardListener
示例页面使用 Switch 控制监听状态,Changes 显示收到的变化次数,Copy sample 按钮向系统剪贴板写入一段不同的示例文本。页面只统计事件,不在回调中读取内容,因此可以直接观察监听、停止和重新启动是否符合预期。
class _MyAppState extends State<MyApp>
with ClipboardListener {
int _changes = 0;
bool _listening = false;
String? _error;
void initState() {
clipboardWatcher.addListener(this);
super.initState();
}
Future<void> _setListening(bool value) async {
try {
if (value) {
await clipboardWatcher.start();
} else {
await clipboardWatcher.stop();
}
if (mounted) {
setState(() {
_listening = value;
_error = null;
});
}
} on PlatformException catch (error) {
if (mounted) {
setState(() => _error = error.message ?? error.code);
}
}
}
void onClipboardChanged() {
if (mounted) {
setState(() => _changes++);
}
}
void dispose() {
clipboardWatcher.removeListener(this);
clipboardWatcher.stop().catchError((Object _) {});
super.dispose();
}
}
异步调用完成后必须检查 mounted,避免页面销毁后继续 setState。dispose 中移除 Dart 监听者,并尝试停止原生订阅。如果多个页面同时使用插件,更适合由应用级服务统一持有一个系统订阅,再把变化事件分发给各页面,避免某个页面退出时误停其他页面仍在使用的监听。
8. 验证、构建与鸿蒙设备运行效果
8.1 分层执行自动化测试
插件根目录的 Dart 测试验证 start、stop 命令、监听者移除和 PlatformException;example 的 Widget 测试验证开关是否依次调用 start 与 stop;ArkTS 生命周期测试直接加载实际 ClipboardWatcherPlugin.ets,只模拟 Flutter 与系统 API 的边界,验证幂等、解绑、失败重试和过期回调隔离。
flutter pub get
flutter test
cd example
flutter pub get
flutter test
cd ..
export NODE_PATH="<HarmonyOS-SDK>/ets/build-tools/ets-loader/node_modules"
node --test ohos/test/clipboard_watcher_lifecycle.test.cjs
如果 Node 提示找不到 TypeScript,可把 NODE_PATH 指向当前 HarmonyOS SDK 的 ets-loader/node_modules 后重试。该设置只负责让测试入口解析编译依赖,不会改变插件实现;最终应以 6 项 ArkTS 生命周期测试全部通过为准。
本文在 Flutter 3.44.9+ohos-0.0.1-canary1 环境重新执行后,共有 10 项自动化测试通过:3 个 Dart 测试、1 个 Widget 测试和 6 个 ArkTS 生命周期测试。6 个原生测试分别覆盖重复启停只注册一次、引擎解绑、订阅失败后重试、取消失败后抑制事件并再次清理、未知方法返回未实现,以及旧订阅的排队回调不能进入新一轮监听。

8.2 静态检查
示例工程最初锁定 mostly_reasonable_lints 0.1.1,而该版本不包含 analysis_options.yaml,导致静态检查出现 include_file_not_found。将 example 的锁定版本升级到 pubspec.yaml 允许范围内的 0.1.2 后,从仓库根目录重新执行 flutter analyze,结果为 No issues found,说明 Dart 与 example 代码没有遗留静态分析问题。
8.3 构建 HAP
完成静态检查和测试后,在 example 目录构建鸿蒙应用。构建前确认 Flutter 与 DevEco Studio 使用同一套完整 SDK,API 26 所需组件已经安装,example/ohos 的工程同步可以完成。
cd example
flutter clean
flutter pub get
flutter build hap --debug
本次在 Flutter 3.44.9+ohos-0.0.1-canary1、HarmonyOS SDK 7.0.0(API 26)环境执行 flutter build hap --debug,Hvigor assembleHap 任务完成并生成 build/ohos/hap/entry-default-signed.hap。该结果与静态检查及三层自动化测试共同构成可复现的构建证据。
8.4 Pura 90 虚拟设备运行与手工验证
自动化测试之外,还需要在设备环境验证系统剪贴板事件链路。本次使用 DevEco Studio 的 Pura 90 虚拟设备,系统版本为 HarmonyOS 6.1.0(API 23);example 采用 API 26 工具链完成编译后,可正常安装并启动。先通过 hdc 确认虚拟设备在线,再安装上一节生成的 HAP。
hdc list targets
cd example
hdc install -r build/ohos/hap/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b dev.leanflutter.clipboard_watcher_example
-
打开应用,确认初始页面显示 Changes: 0,Listening 开关关闭。
-
打开 Listening,点击 Copy sample 一次,确认 Changes 只增加 1。
-
再次调用 start 或重复切换页面后复制,确认同一次更新不会产生多个回调。
-
关闭 Listening,再次复制示例文本,确认 Changes 不再增加。
-
重新打开 Listening 后复制,确认事件能够恢复,计数继续累加。
-
让应用进入后台再返回,观察是否有重复监听、停止后残留事件或异常退出。

在 Pura 90 虚拟设备中,应用页面能够正常显示 Clipboard Watcher、Listening、Changes 和 Copy sample。打开 Listening 后点击 Copy sample,Changes 从 0 增加到 1,说明 Flutter 页面、MethodChannel、ArkTS 插件和系统剪贴板事件之间的基础链路已经打通;重复启停、失败重试和旧回调隔离则由前述 10 项自动化测试继续覆盖。
9. FAQ:适配过程与使用问题
9.1 构建提示找不到对应 SDK 或缺少 SDK components
这类错误通常发生在 Hvigor 同步或插件编译之前。本次问题的根因是终端中的 DEVECO_SDK_HOME 指向了错误层级;DevEco Studio 实际 SDK 位于应用 Contents/sdk。修正 DEVECO_HOME、DEVECO_SDK_HOME、HOS_SDK_HOME 和 JAVA_HOME 后,新登录 Shell 可以直接完成 HAP 构建。不同安装位置只需要替换 DEVECO_HOME,其余目录按相对关系配置即可。
export DEVECO_HOME="/Applications/DevEco-Studio.app/Contents"
export DEVECO_SDK_HOME="$DEVECO_HOME/sdk"
export HOS_SDK_HOME="$DEVECO_SDK_HOME"
export JAVA_HOME="$DEVECO_HOME/jbr/Contents/Home"
export PATH="$DEVECO_HOME/tools/ohpm/bin:$DEVECO_HOME/tools/hvigor/bin:$DEVECO_HOME/sdk/default/openharmony/toolchains:$JAVA_HOME/bin:$PATH"
flutter doctor -v
cd example
flutter build hap --debug
9.2 出现 MissingPluginException
先检查 pubspec.yaml 是否声明 ohos 和 ClipboardWatcherPlugin,ohos/index.ets 是否导出同一个类,Dart 与 ArkTS 的通道名称是否都是 clipboard_watcher。新增或修改原生代码后必须停止应用并重新构建,热重载只更新 Dart 代码,不能完成原生插件注册。
cd example
flutter clean
flutter pub get
flutter run -d <device-id>
9.3 start 返回成功但没有变化回调
确认页面已经 addListener,并且 Listening 的状态是在 await clipboardWatcher.start 成功后才更新。随后观察 SystemPasteboard 是否真的发生变化。连续写入同一内容时,不同系统版本的事件行为可能不同,示例按钮使用不同文本更容易验证。若第一次订阅曾经失败,还要检查原生实现是否在 board.on 成功前错误地留下 listening 或 callback 状态。
9.4 一次复制收到多个回调
重点检查 ArkTS start 是否具备 listening 幂等保护,以及业务层是否创建了多个服务实例或重复 addListener。stop 必须向 off 传入最初注册的同一个 callback。不要仅在页面做防抖掩盖重复订阅,因为原生监听和资源泄漏仍然存在。
9.5 stop 后偶尔仍收到事件
系统事件可能在 stop 之前已经进入异步队列,单纯调用 off 不一定撤回排队任务。本文实现会先把 listening 置为 false,并更新 generation;回调真正发送给 Dart 前再次核对状态和代次,从而屏蔽旧事件。
9.6 flutter analyze 报 mostly_reasonable_lints 找不到
本例的原因是 example/pubspec.lock 锁定了 mostly_reasonable_lints 0.1.1,而该版本不包含被 analysis_options.yaml 引用的规则文件。进入 example 目录执行 flutter pub upgrade mostly_reasonable_lints,将其升级到 pubspec.yaml 允许的 0.1.2,再回到仓库根目录执行 flutter analyze;本次复验结果为 No issues found。
9.7 修改本地 ArkTS 后,example 没有变化
查看 example/pubspec.yaml 实际使用的是 path 还是 git。Git 依赖只读取远端提交,不会自动加载本地工作区的 ArkTS 修改;本地联调应使用 path: ../。原生代码变化后需要停止应用并重新构建,不能只执行 Dart 热重载。
9.8 AtomGit 依赖提示找不到分支
先确认 URL 指向 clipboard_watcher 配套仓库,再检查 feat/ohos_clipboard_watcher_0.3.0 是否已经推送。正式交付可改用明确的提交号或标签。如果使用私有 Fork,还需要保证本机 Git 凭据拥有读取权限。
9.9 多个页面都要监听,应该怎样管理
不要让每个页面分别控制同一个全局系统订阅。建议由应用级 ClipboardService 持有 clipboardWatcher:第一个业务订阅者加入时调用 start,最后一个订阅者移除时调用 stop,各页面只订阅服务分发的状态。这样可以避免页面 A 退出时停止页面 B 仍在使用的监听,也方便统一处理错误和前后台策略。
9.10 插件是否会读取用户剪贴板内容
当前 OHOS 实现只监听 update,并以 null 参数通知 Dart,不调用读取内容接口。示例中的 Copy sample 是业务主动写入一段测试文本,用来触发系统事件,与插件在后台读取数据是两回事。若业务收到通知后再调用 Clipboard.getData,相关数据处理责任属于业务应用。
相关链接
clipboard_watcher 的鸿蒙化适配代码并不庞大,但它集中体现了 Flutter 平台插件的几个关键问题:Dart API 如何保持兼容,通道协议如何对齐,系统事件如何安全地转换为 Flutter 回调,启停和引擎解绑如何释放资源,以及失败和异步残留事件如何处理。把源码、测试、example、文档和设备运行证据一起闭环,适配成果才具备进入业务项目的基础。
更多推荐



所有评论(0)