开发工具: 华为云码道

本文配套仓库: oh-flutter/sensitive_clipboard

sensitive_clipboard 将敏感文本复制与读取封装为 Flutter 接口,OHOS 端通过剪贴板共享范围限制敏感内容的跨应用读取。本文以 sensitive_clipboard 1.2.0 为例,介绍源码准备、OHOS 工程配置、ArkTS 实现和 example 真机运行。

插件声明 Android、iOS 和 OHOS;敏感保护在不同平台的行为不同,OHOS 通过自定义标签与 INAPP 范围实现。配套仓库地址为 oh-flutter/sensitive_clipboard。


sensitive_clipboard 鸿蒙真机:敏感复制 sensitive_clipboard 鸿蒙真机:本应用读取 sensitive_clipboard 鸿蒙真机:普通复制

一、插件简介与适配目标

剪贴板复制涉及内容本身,也涉及哪些应用可以读取这些内容。OHOS 实现基于 pasteboard 写入纯文本,并在 hideContent 为 true 时设置敏感标签与应用内共享范围。

例如,应用可以将演示验证码或临时文本在本应用内复制读取;需要跨应用粘贴普通内容时,可以显式关闭敏感模式。

copy 通过插件 MethodChannel 写入,paste 使用 Flutter 标准 Clipboard 通道读取。复制返回的 bool 表示是否应用保护,不表示复制操作成功或失败的全部情况。


二、环境准备

环境搭建参考社区文档:Flutter OH 开发环境搭建,完成 Flutter OH SDK 安装、环境变量和 DevEco Studio 配置。

完成后,在宿主机终端执行以下命令,确认当前选中的是支持 OHOS 的 Flutter 工具链,并能发现目标设备:

flutter --version
flutter doctor -v
hdc list targets

工程使用的工具链和 SDK 配置如下:

项目版本或配置用途
Flutter OHOS SDK3.44.9+ohos-0.0.1-canary1Flutter 编译与 OHOS 平台工具链
Flutter 分支oh-3.44.9-devCPF-Flutter 对应开发分支
Dart SDK3.12.2Dart 语言与包管理环境
HarmonyOS 开发套件7.0.0(API 26)开发套件版本及对应的 API 级别
compileSdkVersion26.0.0编译时使用的 SDK API
targetSdkVersion26.0.0应用面向的行为版本
compatibleSdkVersion5.1.0(18)当前工程声明的最低兼容版本
插件版本1.2.0pubspec.yaml 中的包版本
原生语言ArkTSHarmonyOS 插件实现
插件产物HAR被应用 entry 模块依赖

2.1 开发套件版本与工程中的 SDK 版本配置

7.0.0(API 26) 和 26.0.0 涉及 HarmonyOS 开发套件版本(API 版本)及其底座 OpenHarmony 的版本号体系。在本文工程中,相关版本的含义与配置方式如下:

  • 7.0.0(API 26) 表示 HarmonyOS 开发套件版本为 7.0.0,对应 API 26。
  • 26.0.0 是本文 HarmonyOS 应用工程中 compileSdkVersion 和 targetSdkVersion 的属性值。
  • 5.1.0(18) 是本文工程中 compatibleSdkVersion 的属性值,声明最低兼容 API 18。

对应的 product 配置为:

{
  "name": "default",
  "compatibleSdkVersion": "5.1.0(18)",
  "compileSdkVersion": "26.0.0",
  "targetSdkVersion": "26.0.0",
  "runtimeOS": "HarmonyOS"
}

这组配置使用 API 26 SDK 编译,并以 API 26 为目标版本,最低兼容 API 18。敏感模式使用 INAPP,其他应用不能按普通跨应用剪贴板方式读取本次内容。自定义 tag 不等于 Android 的系统敏感标志,也不意味着鸿蒙系统一定隐藏所有界面预览。


三、从源码仓库开始准备适配工程

3.1 将上游源码同步到 AtomGit

适配已有第三方插件时,先从 Pub 包信息或项目 README 确认上游地址、包名、版本和许可证。同步仓库时保留源码、许可证和提交历史,方便后续跟踪上游更新。

在 AtomGit 网页的新建仓库流程中,找到从已有仓库导入的入口,填写上游 Git 地址,选择自己有写权限的组织或个人空间,设置目标仓库名称后执行导入。完成后检查目标分支、pubspec.yaml、LICENSE 和提交历史是否完整,并记录适配起点的提交号。如果上游本身托管在 AtomGit,也可以通过 Fork 获得自己的工作仓库。

上游源码为 https://github.com/marcellocamara/sensitive_clipboard,本文基于 1.2.0。配套仓库为 sensitive_clipboard。需要提交修改时,使用自己有写权限的仓库或 Fork。

3.2 将代码拉取到宿主机

在安装了 Flutter OH 和 DevEco Studio 的开发电脑上打开终端,进入准备存放项目的目录,执行:

git clone https://atomgit.com/oh-flutter/sensitive_clipboard.git
cd sensitive_clipboard
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD

git clone 会创建 sensitive_clipboard/ 目录;cd 后的位置就是下文所说的插件仓库根目录,这里应能看到 pubspec.yaml、lib/ 和 example/。Git 仓库名和 Dart 包名均为 sensitive_clipboard。

需要使用与本文相同的代码版本时,先确认配套仓库已经包含下列本地参考提交,再在没有未提交修改的仓库中执行:

git switch --detach 29e93c333f9d73aed33ca2c81a73ccc625415308

适配其他插件时,使用该插件对应版本的 tag 或 commit 作为分支起点。

在这里插入图片描述

3.3 在仓库根目录创建适配分支

接着在 sensitive_clipboard/ 根目录创建分支。分支名统一使用 feat/ohos_库名称_版本号,库名称取 pubspec.yaml 的 name,版本号取此次适配的基线版本。本例为:

git switch -c feat/ohos_sensitive_clipboard_1.2.0
git branch --show-current

如果该分支已存在,使用 git switch feat/ohos_sensitive_clipboard_1.2.0 切换即可。

在这里插入图片描述

3.4 自动补全 OHOS 适配结构

分支创建后,仍在同一个插件根目录执行结构补全。以下命令适用于尚无 ohos/ 目录的既有 Flutter 平台插件:

flutter create --template=plugin --platforms=ohos --project-name sensitive_clipboard .
git status --short
git diff -- pubspec.yaml lib example
  • --template=plugin 指定插件模板。
  • --platforms=ohos 指定需要补全的平台。
  • --project-name sensitive_clipboard 使用 Dart 包名,避免当前目录重命名后生成错误的包名。
  • 最后的 . 表示在当前插件目录补全工程,不是另建一层 sensitive_clipboard/。

该命令生成 OHOS 平台脚手架,业务逻辑需要在 ArkTS 中实现。生成后通过 diff 检查 pubspec.yaml、lib/ 和 example/ 的变化,保留已有 API、其他平台注册项及依赖配置。不同 Flutter OH 版本生成的模板可能略有差异。

如果生成后 example/ohos/ 仍不存在,进入已有示例应用补全平台:

cd example
flutter create --platforms=ohos .
cd ..

本地适配工作区已经包含 ohos/ 和 example/ohos/,直接运行示例时可以跳过结构补全。新建插件则使用 flutter create --org com.nutpi --template=plugin --platforms=ohos sensitive_clipboard;已有插件使用上面的 . 在当前目录补全。

在这里插入图片描述

3.5 适配后的项目目录

适配后的关键目录如下:

sensitive_clipboard/
├── lib/
│   └── sensitive_clipboard.dart
├── ohos/
│   ├── src/
│   │   └── main/
│   │       ├── ets/
│   │       │   └── components/
│   │       │       └── plugin/
│   │       │           └── SensitiveClipboardPlugin.ets
│   │       └── module.json5
│   ├── index.ets
│   └── oh-package.json5
├── example/
│   ├── lib/
│   │   └── main.dart
│   └── ohos/
│       ├── entry/
│       │   └── src/
│       │       └── main/
│       │           └── module.json5
│       └── build-profile.json5
├── pubspec.yaml
├── README.OpenHarmony_CN.md
├── README.OpenHarmony.md
├── CHANGELOG.OpenHarmony.md
└── test/

项目根目录如下,其中包含 ohos/、example/ 及实际保留的说明文件;交付文档清单见第六节:

在这里插入图片描述

文件主要职责
lib/sensitive_clipboard.dart提供业务公开 API
ohos/src/main/ets/components/plugin/SensitiveClipboardPlugin.ets注册通道并实现 OHOS 原生能力
ohos/src/main/module.json5声明 HAR 模块和权限
example/lib/main.dart演示接口调用与结果显示

四、Dart 接口与通道分析

OHOS 实现需要遵循 Dart 层已有的方法、参数和回调约定。先阅读 lib/sensitive_clipboard.dart,再在 ohos/src/main/ets/components/plugin/SensitiveClipboardPlugin.ets 中实现对应的原生调用。适配其他已有平台的插件时,也应保留其公开接口和其他平台实现。

本例的对应关系如下:

Dart 入口或模型通道协议OHOS 实现应保持的行为
copy(text, hideContent: true)sensitive_clipboard / copytag + INAPP成功返回 true
copy(text, hideContent: false)同一 copy 方法空 tag + LOCALDEVICE成功返回 false
paste()Flutter 标准 Clipboard.getDataFlutter 引擎剪贴板实现纯文本或空字符串

原生端需要保持方法名、参数键和返回类型一致,不能只保留方法名称而改变业务语义。

4.1 跨端架构与调用时序

复制走 MethodChannel(sensitive_clipboard),读取走 Clipboard.getData 对应的 Flutter 引擎通道。插件 ArkTS 只处理 copy,没有 paste 分支,也没有持续事件流。

Flutter 页面

Dart 对外 API

MethodChannel sensitive_clipboard

ArkTS SensitiveClipboardPlugin

pasteboard 与 Flutter Clipboard

方法结果

4.1.1 一次完整复制文本的时序
pasteboard 与 Flutter Clipboard SensitiveClipboardPlugin Dart API Flutter App pasteboard 与 Flutter Clipboard SensitiveClipboardPlugin Dart API Flutter App SensitiveClipboard.copy(text) invokeMethod 复制文本 是否应用敏感保护 result.success 或 result.error 是否应用敏感保护

4.2 结果模型:保护标记与文本

返回值实际含义
copy 返回 true写入成功并应用 INAPP 保护
copy 返回 false正常复制但未应用敏感保护
copy 抛异常原生写入失败
paste 返回空字符串没有可用的纯文本数据

text 参数可为空;OHOS 写入时将 null 转为空字符串,hideContent 默认由 Dart 公开接口设为 true。

4.3 公开 API 与平台接口

SensitiveClipboard 提供两个静态方法,不需要创建实例:

import 'dart:async';

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

class SensitiveClipboard {
  SensitiveClipboard._();

  static const MethodChannel _channel = MethodChannel('sensitive_clipboard');

  /// Copies a string to the clipboard.
  ///
  /// Returns `true` when the current platform applied its sensitive-content
  /// protection. On Android this requires API 33 or later. On OpenHarmony,
  /// sensitive content is tagged and restricted to the current application.
  static Future<bool> copy(String? text, {bool hideContent = true}) async {
    if (defaultTargetPlatform == TargetPlatform.android ||
        defaultTargetPlatform == TargetPlatform.ohos) {
      final copyResult = await _channel.invokeMethod<bool>('copy', {
        'text': text,
        'hideContent': hideContent,
      });
      return copyResult ?? false;
    } else {
      await Clipboard.setData(ClipboardData(text: text ?? ''));
      return false;
    }
  }

  /// Pastes a string previously copied to the clipboard
  static Future<String> paste() async {
    final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
    return data?.text ?? '';
  }
}

4.4 Dart 通道协议分析

4.4.1 通道名称必须两端完全一致
const methodChannel = MethodChannel('sensitive_clipboard');

通道名称属于跨语言协议。Dart 和 ArkTS 任何一端拼写不一致,都会出现“方法未实现”或“调用找不到插件”等问题。

4.4.2 复制时分发到 OHOS
static Future<bool> copy(String? text, {bool hideContent = true}) async {
  if (defaultTargetPlatform == TargetPlatform.android ||
      defaultTargetPlatform == TargetPlatform.ohos) {
    final copyResult = await _channel.invokeMethod<bool>('copy', {
      'text': text,
      'hideContent': hideContent,
    });
    return copyResult ?? false;
  } else {
    await Clipboard.setData(ClipboardData(text: text ?? ''));
    return false;
  }
}

TargetPlatform.ohos 与 Android 进入插件通道,其他平台使用 Clipboard.setData 并返回 false;false 表示未应用敏感保护,不表示复制失败。

4.4.3 通过标准剪贴板读取
static Future<String> paste() async {
  final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
  return data?.text ?? '';
}

读取不会调用 SensitiveClipboardPlugin 的 copy 通道。系统读取授权由宿主配置与 Flutter 引擎剪贴板实现共同决定。


五、补全 OHOS 原生实现与工程配置

5.1 在 SensitiveClipboardPlugin.ets 中实现原生能力

业务层沿用已有 API,原生侧在 SensitiveClipboardPlugin 中接入 pasteboard 与 Flutter Clipboard,通过 Flutter 通道回传结果。

下面列出类中的主要成员和方法,完整文件还包含 getUniqueClassName() 等插件接口;各段均为核心摘录,需要结合完整类使用。

原生插件位于:

ohos/src/main/ets/components/plugin/SensitiveClipboardPlugin.ets
5.1.1 引入 Flutter 和 HarmonyOS 能力
import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from '@ohos/flutter_ohos';
import { BusinessError, pasteboard } from '@kit.BasicServicesKit';

FlutterPlugin 负责接入 Flutter Engine 生命周期,MethodChannel 接收 Dart 命令;系统能力由 pasteboard 与 Flutter Clipboard 提供。错误和事件处理以对应方法实现为准。

5.1.2 连接 Flutter Engine
private channel: MethodChannel | null = null;


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

Engine 连接只注册 copy 方法通道。本插件没有 EventSink、系统事件监听和 Ability 引用。

5.1.3 构造纯文本剪贴板数据
const text = String(call.argument('text') ?? '');
const hideContent = Boolean(call.argument('hideContent') ?? false);
const data = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text);
const property = data.getProperty();

这是 onMethodCall 内部的准备片段;完整异常处理见 5.1.5。createData 使用 MIMETYPE_TEXT_PLAIN,后续修改 PasteDataProperty。

5.1.4 设置敏感范围并写入
property.tag = hideContent ? SENSITIVE_TAG : '';
      property.shareOption = hideContent
        ? pasteboard.ShareOption.INAPP
        : pasteboard.ShareOption.LOCALDEVICE;
      data.setProperty(property);

      pasteboard.getSystemPasteboard().setDataSync(data);
      result.success(hideContent);

hideContent 为 true 时设置 sensitive_clipboard:sensitive 与 INAPP;为 false 时设置 LOCALDEVICE,只在本机共享,不跨设备同步。setDataSync 成功后才返回保护标记。

5.1.5 处理 MethodChannel 命令
onMethodCall(call: MethodCall, result: MethodResult): void {
  if (call.method !== 'copy') {
    result.notImplemented();
    return;
  }

  const text = String(call.argument('text') ?? '');
  const hideContent = Boolean(call.argument('hideContent') ?? false);

  try {
    const data = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text);
    const property = data.getProperty();
    property.tag = hideContent ? SENSITIVE_TAG : '';
    property.shareOption = hideContent
      ? pasteboard.ShareOption.INAPP
      : pasteboard.ShareOption.LOCALDEVICE;
    data.setProperty(property);

    pasteboard.getSystemPasteboard().setDataSync(data);
    result.success(hideContent);
  } catch (error) {
    const businessError = error as BusinessError;
    result.error(
      String(businessError.code ?? 'PASTEBOARD_ERROR'),
      businessError.message ?? 'Failed to write data to the OpenHarmony pasteboard.',
      null,
    );
  }
}

只处理 copy,未知方法返回 notImplemented。写入异常保留原生错误码,缺少错误码时使用 PASTEBOARD_ERROR,Dart 收到 PlatformException。读取失败属于 Flutter 标准剪贴板路径。

5.1.6 Engine 解绑时释放资源
onDetachedFromEngine(_binding: FlutterPluginBinding): void {
  this.channel?.setMethodCallHandler(null);
  this.channel = null;
}

Engine 解绑释放 MethodChannel Handler。这里不会清空系统剪贴板内容,也没有自动过期或定时清除功能。

5.2 声明插件和宿主权限

复制文本不需要额外权限;paste 读取需要宿主声明 READ_PASTEBOARD。示例同时保留 INTERNET。

5.2.1 插件 HAR 的权限

插件 ohos/src/main/module.json5 的模块配置如下:

{
  "module": {
    "name": "sensitive_clipboard",
    "type": "har",
    "deviceTypes": [
      "default",
      "tablet"
    ]
  }
}
5.2.2 应用 entry 的权限

最终安装的是宿主应用。以下片段来自 example/ohos/entry/src/main/module.json5,合并时保留原有 Ability 等配置:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        "name": "ohos.permission.READ_PASTEBOARD"
      }
    ]
  }
}

READ_PASTEBOARD 属于读取路径要求,清单声明不等于所有读取场景都会自动放行,实际授权行为以系统弹窗为准。

5.3 注册并导出插件

pubspec.yaml 通过以下配置声明 OHOS 插件类:

flutter:
  plugin:
    platforms:
      ohos:
        pluginClass: SensitiveClipboardPlugin

插件的 ohos/index.ets 需要导出实现:

import SensitiveClipboardPlugin from './src/main/ets/components/plugin/SensitiveClipboardPlugin';
export default SensitiveClipboardPlugin;

执行 flutter pub get 和构建后,Flutter 工具会为应用生成插件注册代码。通常不应手工编辑 GeneratedPluginRegistrant.ets,因为下次构建可能覆盖它。

注册异常的排查步骤见第九节 MissingPluginException。

5.4 检查 example 的 OHOS 应用结构

本例的 example/ohos/build-profile.json5 应在 products 中设置版本。下面是需核对的配置片段,请合并到现有工程;其中 signingConfig: "default" 需要与本机配置的签名名称一致,签名材料保留在本地。

{
  "app": {
    "products": [
      {
        "name": "default",
        "signingConfig": "default",
        "compatibleSdkVersion": "5.1.0(18)",
        "runtimeOS": "HarmonyOS",
        "compileSdkVersion": "26.0.0",
        "targetSdkVersion": "26.0.0"
      }
    ]
  },
  "modules": [
    {
      "name": "entry",
      "srcPath": "./entry",
      "targets": [
        {
          "name": "default",
          "applyToProducts": [
            "default"
          ]
        }
      ]
    }
  ]
}

配置后,在 DevEco Studio 中执行一次 Sync Project。如果 entry 模块正常识别,Project 视图中会出现 entry,并能打开 entry 模块的签名配置。


六、补全交付文件并提交适配分支

6.1 除代码外还要补全哪些文件

代码适配完成后,还需要整理安装说明、接口文档、版本记录和开源信息。接收仓库有专用模板时,按其格式填写:

文件应写清楚的内容
README.OpenSource上游名称、源码地址、适配版本或提交、版权及许可证信息;按仓库模板列出第三方依赖
README.md原项目说明、OHOS 支持入口、配套 Demo 和文档链接;保留上游信息
README.OpenHarmony_CN.md简介、AtomGit 安装方式、版本对应关系、环境约束、权限、接口表、示例、已验证范围和遗留问题
README.OpenHarmony.md与中文说明对应的英文文档
CHANGELOG.OpenHarmony.mdOHOS 新增能力、适配版本、兼容限制与测试范围
LICENSE / NOTICE保留上游许可证;NOTICE 按许可证和原项目要求保留或补充
example/README.md依赖方式、运行目录、签名、操作步骤与效果图;覆盖敏感复制、普通复制、本应用读取与跨应用边界
pubspec.yaml、ohos/oh-package.json5核对包名、版本、插件注册、仓库地址、许可证和依赖
.gitignore忽略构建缓存及本机签名材料,不漏提交必要源码和配置

README.OpenSource 记录库本身的来源与版本。本例的包名为 sensitive_clipboard,版本为 1.2.0,采用 MIT 许可证;Flutter 和 HarmonyOS SDK 版本写入环境说明。

现有说明文件以目录树为准。README.OpenSource 等缺失交付文件按接收仓库要求补全;安装与反馈链接统一使用 AtomGit 地址。

6.2 提交前检查

提交前先完成第八节的插件、example 和真机测试,再从根目录检查改动:

git branch --show-current
git diff --check
git status --short
git diff --stat
git diff

检查 diff 中的接口、平台注册和依赖变化,移除本机路径及签名信息,并使文档中的版本和分支与提交内容一致。

6.3 提交并推送到 AtomGit

文档和代码整理完成后,在 Git 仓库根目录暂存并提交。以下命令以第 6.1 节文档已经补全为前提,文件名按项目实际情况调整:

git add lib ohos pubspec.yaml example test
git add README.md README.OpenSource README.OpenHarmony_CN.md
git add README.OpenHarmony.md CHANGELOG.OpenHarmony.md
git diff --cached --check
git diff --cached --stat
git diff --cached
git commit -m "feat: add OHOS support for sensitive_clipboard 1.2.0"
git remote -v
git branch --show-current
git push -u origin feat/ohos_sensitive_clipboard_1.2.0

DevEco 可能向 example/ohos/build-profile.json5 写入本机签名配置,需要在提交前从暂存内容中移除。推送时,origin 应指向自己的 AtomGit 仓库,当前分支为 feat/ohos_sensitive_clipboard_1.2.0。

推送后在 AtomGit 发起合并请求,说明上游来源和版本、OHOS 实现范围、依赖及权限、测试环境、操作结果、已知限制,并附 Demo 运行图。目标分支和评审流程以接收仓库要求为准。


七、使用根目录 example 演示接入

插件包自带 example/,可以直接用来调试插件和体验敏感文本复制与读取。

7.1 本地适配时使用路径依赖

当前 example/pubspec.yaml 的依赖是:

dependencies:
  flutter:
    sdk: flutter
  sensitive_clipboard:
    path: ../

../ 相对于 example/pubspec.yaml 指向插件根目录,修改根目录插件后可直接联调。

7.2 通过 AtomGit 引入插件

业务应用通过 AtomGit 引入时,将 sensitive_clipboard 的 path 配置替换为下面的 Git 依赖。仓库同步后,可固定到本文的本地参考提交:

dependencies:
  flutter:
    sdk: flutter
  sensitive_clipboard:
    git:
      url: https://atomgit.com/oh-flutter/sensitive_clipboard.git
      ref: 29e93c333f9d73aed33ca2c81a73ccc625415308

使用自己的适配版本时,先推送分支,再将 url 改为对应仓库,ref 改为 feat/ohos_sensitive_clipboard_1.2.0。正式发布后可固定到 tag 或 commit。

从插件根目录执行:

cd example
flutter pub get
flutter pub deps

检查 example/pubspec.lock 中 sensitive_clipboard 的来源为 git,并核对 url、ref 和 resolved-ref。同时检查没有 dependency_overrides 或 pubspec_overrides.yaml 将其覆盖回本地依赖,确认应用使用的是 Git 依赖。

7.3 调用接口实现敏感文本复制与读取

下面的页面可用于插件包内的 example/lib/main.dart,是便于讲解的最小页面;仓库完整 Demo 的入口和布局可能不同,第八节截图与验收步骤以仓库完整 Demo 为准。

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:sensitive_clipboard/sensitive_clipboard.dart';


void main() {
  runApp(const MaterialApp(home: DemoPage()));
}

class DemoPage extends StatefulWidget {
  const DemoPage({super.key});
  
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
  String _status = '尚未操作';
  bool _busy = false;


  void _show(String value) {
    if (mounted) setState(() => _status = value);
  }

  Future<void> _run(Future<String> Function() action) async {
    if (_busy) return;
    setState(() => _busy = true);
    try {
      _show(await action());
    } catch (error) {
      _show('调用失败:$error');
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('敏感剪贴板')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Text(_status),
          const SizedBox(height: 16),
            FilledButton(onPressed: _busy ? null : () => _run(() async {
              final protected = await SensitiveClipboard.copy('演示文本 123456');
              return '复制完成,敏感保护:$protected';
            }), child: const Text('敏感复制')),
            FilledButton(onPressed: _busy ? null : () => _run(() async {
              return '读取结果:${await SensitiveClipboard.paste()}';
            }), child: const Text('读取')),
            FilledButton(onPressed: _busy ? null : () => _run(() async {
              final protected = await SensitiveClipboard.copy('普通演示文本', hideContent: false);
              return '复制完成,敏感保护:$protected';
            }), child: const Text('普通复制')),

        ],
      ),
    );
  }
}

7.4 页面退出时释放页面控制器

异步回调先检查 mounted。最小页面没有原生订阅;仓库完整 Demo 的 TextEditingController 需要在 dispose 中释放。页面退出和 Engine 解绑均不等于清除剪贴板。


八、验证、构建与鸿蒙设备运行效果

8.1 分别验证插件与 example

从插件仓库根目录执行:

flutter pub get
flutter analyze
flutter test
cd example
flutter pub get
flutter analyze
flutter test

现有测试覆盖 OHOS/Android copy 参数、其他平台回退和标准 Clipboard 读取;example 另有中文页面与复制读取集成测试。敏感模式的跨应用读取边界仍需真机验证。Widget 测试应匹配实际保留的 Demo 页面;如果替换成第七节最小页面,也要相应调整原来的 UI 断言。

Dart 测试覆盖接口和页面逻辑,敏感复制、普通复制、本应用读取与跨应用边界还需要在鸿蒙设备上验证。

8.2 确认设备连接

hdc list targets
flutter devices

设备首次连接电脑时,需要在手机端确认调试授权。列表为空时,检查 USB 连接、调试模式和电脑授权。

8.3 配置签名

真机安装的 HAP 通常需要有效签名。推荐使用 DevEco Studio 为 entry 模块配置自动签名:

  1. 用 DevEco Studio 打开 example/ohos,不是仓库根目录;
  2. 等待工程 Sync 成功,确认 Project 视图中存在 entry 模块;
  3. 打开 File > Project Structure > Signing Configs;
  4. 为 default product 选择或生成签名;
  5. 确认设备、应用包名、证书和 Profile 匹配;
  6. 再回到终端执行 Flutter 构建或运行。

签名材料保存在本机,公开仓库中只保留构建所需的通用配置。

8.4 运行示例

以下命令在 example/ 目录执行,将 <device-id> 替换为设备列表中的实际 ID:

flutter run -d <device-id>

也可以先构建 HAP:

flutter build hap --debug

典型产物位于:

example/ohos/entry/build/default/outputs/default/

目录中通常包含已签名和未签名 HAP。真机安装应选择与当前设备匹配的已签名产物。

flutter build hap --release
hdc -t <device-id> install -r build/ohos/hap/entry-default-signed.hap
hdc -t <device-id> shell aa start -a EntryAbility -b marcello.dev.sensitive_clipboard_example

8.5 在设备上测试敏感文本复制与读取

  1. 运行完整 Demo,保留演示文本“验证码 123456”,不要使用真实凭据。
  2. 开启“启用敏感内容保护”,点击“复制”,确认保护结果。
  3. 点击“读取”,核对本应用可以读取演示文本。
  4. 切换到另一应用验证敏感内容未按普通模式暴露,再返回 Demo。
  5. 关闭保护开关重新复制,检查普通模式返回值与本机跨应用粘贴。
  6. 测试空文本与读取授权路径;普通复制返回 false 表示未应用敏感保护,不是失败。

页面初始提示来自 Demo 默认值,调用结果或事件到达后才反映系统状态。

8.6 鸿蒙设备运行效果

OHOS 实现提供敏感文本复制与读取。以下为仓库完整 Demo 的三张真机运行截图,按实际状态记录。

sensitive_clipboard 鸿蒙真机:敏感复制 sensitive_clipboard 鸿蒙真机:本应用读取 sensitive_clipboard 鸿蒙真机:普通复制

敏感复制本应用读取普通复制
INAPP 保护已应用读取演示文本LOCALDEVICE 普通模式

敏感模式使用 INAPP,其他应用不能按普通跨应用剪贴板方式读取本次内容。自定义 tag 不等于 Android 的系统敏感标志,也不意味着鸿蒙系统一定隐藏所有界面预览。


九、FAQ:适配过程与使用问题

9.1 Missing SDK components

典型错误如下:

Missing SDK components. SDK path: ...,
missing components: toolchains,ets,js,native,previewer.

这个错误发生在 Hvigor 同步阶段。通常需要检查构建工具使用的 SDK 路径、组件是否完整,以及 Hvigor 与 SDK 的版本是否匹配。

处理顺序:

  1. 在 DevEco Studio SDK Manager 中确认 API 26 组件已经下载完整;
  2. 检查 Flutter 和 DevEco Studio 使用的 SDK 路径是否一致;
  3. 避免误用 /Applications/DevEco-Studio.app/Contents/sdk 之类的不完整目录;
  4. 确认 SDK 根目录下存在 toolchains、ets、js、native、previewer;
  5. 执行 flutter config --ohos-sdk <正确路径>;
  6. 重新执行 flutter doctor -v 和 DevEco Studio Sync。
为什么连接 API 24 手机仍然会报这个错误?

因为 Sync 和 Compile 首先读取 Mac 本地 SDK。手机 API 版本只在部署、安装和运行时参与兼容判断。即使完全不连接手机,本地 SDK 不完整时也会得到相同错误。

当前工程的 compatibleSdkVersion 是 API 18,因此 API 24 在安装版本门槛上是满足的;但功能是否可用还取决于目标系统能力、权限和运行环境,不能仅凭最低版本判断。

9.2 DevEco Studio 中看不到 entry 模块

插件的 ohos/ 目录是 HAR 模块,可安装应用的 entry 模块位于 example/ohos/entry。

请直接使用 DevEco Studio 打开:

sensitive_clipboard/example/ohos

如果仍看不到 entry,先解决 SDK Sync 错误,再检查 example/ohos/build-profile.json5 的 modules 是否包含 ./entry。同步失败时,Project Structure 无法正确解析模块,签名界面也可能不显示 entry。

9.3 无法手动签名

签名配置依附于可构建的应用模块和 product。只有 HAR 插件模块、工程 Sync 失败,或者打开了错误目录时,DevEco Studio 都可能无法提供 entry 签名入口。

建议先确认:

  • 打开的是 example/ohos;
  • SDK 组件完整并且 Sync 成功;
  • entry 的模块类型为 entry;
  • default product 和 target 已正确关联;
  • 当前账号、证书和调试设备状态有效。

9.4 复制返回 false 是否失败

不是。hideContent: false 时写入成功返回 false,表示没有启用敏感保护;写入失败通过异常返回。按“复制结果”和“保护是否启用”两个维度显示状态。

9.5 MissingPluginException

这通常表示 Dart 通道找不到已注册的原生插件。新增原生插件后需要重新构建应用。从仓库根目录执行:

cd example
flutter clean
flutter pub get
flutter run -d <device-id>

如果仍然出现,检查自动生成的插件注册文件中是否包含 SensitiveClipboardPlugin,同时核对 pubspec.yaml、ohos/index.ets 和 oh-package.json5。

9.6 多次读取为什么没有订阅事件

本插件没有事件流,paste 是一次标准 Clipboard 查询。页面重复读取来自业务调用,不存在可取消的原生剪贴板监听。

9.7 编译成功但安装失败

常见原因包括:

  • HAP 未签名或使用了错误的 Profile;
  • 设备未加入调试设备列表;
  • 包名与签名 Profile 不匹配;
  • 安装包的 compatibleSdkVersion 高于设备 API;
  • 手机上已经安装了使用不同证书签名的同包名应用。

根据安装错误码区分签名、版本和包名冲突,再处理对应配置。

9.8 flutter create 不认识 ohos,或包名不合法

先执行 flutter --version,确认使用的是 OHOS 版工具链,环境配置回到第二节的社区链接核对。包名报错时,确认当前目录包含目标插件的 pubspec.yaml,并显式传入 --project-name sensitive_clipboard;本例仓库名和 Dart 包名均为 sensitive_clipboard。生成后检查 diff,再补充 ArkTS 业务实现。

9.9 AtomGit 依赖提示找不到分支或无权限

先检查 URL 是否指向已同步的目标仓库,再确认 feat/ohos_sensitive_clipboard_1.2.0 已推送。仓库未创建、适配分支未推送或提交未同步时,应先完成同步;不能直接使用仅存在本地的提交号。私有仓库还需在本机配置 Git 认证。

9.10 改了本地 ArkTS,Demo 为什么没变化

先检查 example/pubspec.yaml:Git 依赖读取远程提交,不会自动读取本地插件改动。本地联调切回 path: ../;测试远程版本则先提交推送,再更新依赖并核对 pubspec.lock 的 resolved-ref。原生代码变动后停止应用并重新构建运行,不能只做 Dart 热重载。

9.11 敏感内容为什么粘贴不到其他应用

OHOS 敏感模式明确设置 INAPP,限制当前应用之外的读取。需要复制可跨应用的普通内容时,业务显式设置 hideContent: false。


相关链接

Logo

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

更多推荐