引言

剪贴板是操作系统中最古老、也最常用的跨应用数据交换机制之一。无论是复制一段文字、一张图片,还是从一个应用粘贴内容到另一个应用,剪贴板都在背后默默工作。在 HarmonyOS NEXT 中,系统为开发者提供了 @ohos.pasteboard 模块,让我们能够以编程方式与系统剪贴板进行交互——读取内容、写入内容、监听变化、检测数据类型,甚至感知跨设备的剪贴板同步。

@ohos.pasteboard 属于 @kit.BasicServicesKit 基础服务套件,是系统核心 API 的重要组成部分。与 Android 的 ClipboardManager 或 iOS 的 UIPasteboard 类似,鸿蒙的剪贴板 API 提供了完整的剪贴板管理能力,但在设计上更具响应式特性——通过事件驱动的方式监听剪贴板变化,这在实时剪贴板感知场景中非常有用。

本文将从基础概念出发,逐步深入剪贴板的读写操作、数据类型检测、变化监听机制,以及权限模型和跨设备能力,并提供一个完整的"剪贴板实验室"Demo 页面,让你能够直观地体验和测试这些 API。

一、API 架构总览

1.1 核心设计:SystemPasteboard 单例

@ohos.pasteboard 的核心设计围绕 SystemPasteboard 接口展开。与许多系统服务 API 不同,剪贴板 API 不采用"创建实例"的模式,而是通过 getSystemPasteboard() 函数获取一个全局唯一的系统剪贴板对象:

import pasteboard from '@ohos.pasteboard';

// 获取系统剪贴板单例
const sp = pasteboard.getSystemPasteboard();

这个设计非常合理——系统中只有一个剪贴板,因此使用单例模式是最自然的表达。获取到 SystemPasteboard 对象后,所有的剪贴板操作都通过它的实例方法来完成。

1.2 API 功能分类

@ohos.pasteboard 的 API 可以按功能划分为以下六个类别:

功能类别 主要方法 说明
写入 setData(data) / setDataSync(data) 将数据写入剪贴板
读取 getData() / getDataSync() 从剪贴板读取数据
清空 clearData() / clearDataSync() 清空剪贴板内容
检测 hasData() / getChangeCount() / getMimeTypes() / hasDataType(mimeType) / detectPatterns(patterns) 检测剪贴板状态和数据类型
监听 on('update', callback) / off('update', callback) 监听剪贴板内容变化
源信息 getDataSource() / isRemoteData() 获取数据来源和跨设备状态

每个方法都提供了异步(Promise)和同步(Sync)两个版本,方便在不同场景下使用。在 UI 线程中,通常推荐使用异步版本以避免阻塞主线程;而在 aboutToAppear 等生命周期方法中进行初始检测时,同步版本更加简洁。

1.3 数据封装:PasteData 与 PasteDataRecord

剪贴板中的数据不是裸字符串或字节流,而是被封装在 PasteData 对象中。一个 PasteData 可以包含多条 PasteDataRecord,每条记录对应一个 MIME 类型的数据。

创建 PasteData 使用 pasteboard.createData(mimeType, value)

import pasteboard from '@ohos.pasteboard';
import util from '@ohos.util';

const encoder = new util.TextEncoder();
const bytes = encoder.encodeInto('Hello HarmonyOS');
const pasteData = pasteboard.createData('text/plain', bytes);

对于纯文本内容,使用 text/plain 作为 MIME 类型。除此之外,系统还支持 text/htmltext/uritext/want 等多种格式。

读取时,getData() 返回一个 PasteData 对象,通过 getRecordCount() 获取记录数量,通过 getRecord(index) 获取指定索引的记录,再通过记录的 convertToText() 方法将内容转换为字符串:

const pasteData = await sp.getData();
const count = pasteData.getRecordCount();
if (count > 0) {
  const record = pasteData.getRecord(0);
  const text = await record.convertToText();
  console.log('剪贴板内容:', text);
}

二、核心操作详解

2.1 写入剪贴板:setData

写入剪贴板是最基本的操作。setData 方法是异步的,返回 Promise,成功后数据就被放入了系统剪贴板:

private async writeToClipboard(text: string): Promise<void> {
  try {
    const encoder = new util.TextEncoder();
    const bytes = encoder.encodeInto(text);
    const pasteData = pasteboard.createData('text/plain', bytes);
    await this.sp.setData(pasteData);
    console.log('写入成功');
  } catch (err) {
    console.error('写入失败:', (err as Error).message);
  }
}

需要注意的是,TextEncoder.encodeInto() 返回的是 Uint8Array 类型,而 createData() 的第二个参数接收 ArrayBuffer。在 ArkTS 中,Uint8ArrayArrayBuffer 的子类型,因此可以直接传递,编译器会正确处理类型转换。

写入操作的权限要求比较宽松:当应用在前台运行时,setData() 不需要任何权限。这意味着任何应用都可以自由地向剪贴板写入内容。这个设计是合理的——写入数据是用户的主动行为(通过复制操作触发),不应该要求额外授权。

2.2 读取剪贴板:getData

读取剪贴板使用 getData() 方法,返回 Promise<PasteData>

private async readFromClipboard(): Promise<void> {
  try {
    const hasData = this.sp.hasDataSync();
    if (!hasData) {
      console.log('剪贴板为空');
      return;
    }

    const pasteData = await this.sp.getData();
    const count = pasteData.getRecordCount();
    if (count > 0) {
      const record = pasteData.getRecord(0);
      const text = await record.convertToText();
      console.log('读取到:', text);
    }
  } catch (err) {
    console.error('读取失败:', (err as Error).message);
  }
}

与写入不同,读取剪贴板需要 ohos.permission.READ_PASTEBOARD 权限(从 API 12 开始)。这是 HarmonyOS NEXT 在隐私保护方面的一个重要设计:用户可以自由地将内容复制到剪贴板(写入无需权限),但应用想要读取剪贴板内容时,需要用户明确授权。这种"不对称权限模型"有效防止了恶意应用在后台偷偷读取剪贴板中的敏感信息(如密码、验证码、银行卡号等)。

2.3 清空剪贴板:clearData

清空操作比较简单,clearDataSync() 可以立即清空剪贴板:

private clearClipboard(): void {
  try {
    this.sp.clearDataSync();
    console.log('剪贴板已清空');
  } catch (err) {
    console.error('清空失败:', (err as Error).message);
  }
}

与写入一样,清空操作在前台执行时不需要权限。

2.4 检测剪贴板状态

除了读写之外,SystemPasteboard 还提供了一系列检测方法,用于在不读取实际内容的情况下了解剪贴板状态:

hasData() / hasDataSync() — 检测剪贴板中是否有数据:

const hasData = this.sp.hasDataSync();
// 返回 boolean

getChangeCount() — 获取剪贴板的变更次数。每次向剪贴板写入新内容,这个计数器都会递增。这对于检测"自上次检查后剪贴板是否有变化"非常有用:

const count = this.sp.getChangeCount();
// 返回 number,表示累积变更次数

getDataSource() — 获取最后一次写入剪贴板的应用 bundle name:

const source = this.sp.getDataSource();
// 返回 string,如 "com.example.app"

getMimeTypes() — 获取当前剪贴板数据支持的所有 MIME 类型:

const types: string[] = await this.sp.getMimeTypes();
// 返回如 ["text/plain", "text/html"]

hasDataType(mimeType) — 检测剪贴板是否包含指定的 MIME 类型数据:

const hasHtml = await this.sp.hasDataType('text/html');

detectPatterns(patterns) — 检测剪贴板内容是否匹配特定模式(如 URL、邮箱、电话号码等):

const patterns = await this.sp.detectPatterns([pasteboard.Pattern.URL]);

isRemoteData() — 判断剪贴板数据是否来自分布式设备(例如从手机跨设备复制到平板):

const isRemote = this.sp.isRemoteData();

这些检测方法的一个共同特点是:它们都不需要 READ_PASTEBOARD 权限。这是因为检测方法只返回元数据(是否有数据、变更次数、MIME 类型列表等),而不返回剪贴板的实际内容。这个设计兼顾了隐私保护和开发体验。
在这里插入图片描述
在这里插入图片描述

三、剪贴板变化监听:事件驱动机制

除了被动的轮询检测,@ohos.pasteboard 还支持通过事件监听的方式实时感知剪贴板变化。这是该 API 最具特色的功能之一。

3.1 on(‘update’) 监听

通过 on('update', callback) 注册一个监听器,当系统剪贴板内容发生变化时(无论是本应用写入的还是其他应用写入的),回调函数会被触发:

// 开启监听
this.sp.on('update', (data: pasteboard.PasteData) => {
  console.log('剪贴板已更新');
  // data 参数包含新的剪贴板内容
  this.refreshInfo();
});

这个机制在以下场景中非常有用:

  • 剪贴板管理器应用:实时显示剪贴板历史
  • 翻译工具:检测到新的复制文本后自动触发翻译
  • 密码管理器:感知用户复制密码后自动填充
  • 跨设备协同:感知远程设备复制的内容

3.2 off(‘update’) 取消监听

当不再需要监听时,使用 off('update') 取消监听器:

this.sp.off('update');

最佳实践是在组件销毁时取消监听,避免内存泄漏:

aboutToDisappear(): void {
  if (this.monitorOn) {
    this.sp.off('update');
    this.monitorOn = false;
  }
}

3.3 监听与轮询的对比

方式 优点 缺点
on(‘update’) 事件监听 实时响应,零延迟,资源开销小 仅 API 12+ 支持
hasDataSync() 轮询 兼容性好,实现简单 有延迟,浪费资源

在 HarmonyOS NEXT 开发中,建议优先使用事件监听方式,轮询仅作为兼容旧版本的兜底方案。

四、权限模型深度解析

@ohos.pasteboard 的权限模型是鸿蒙隐私保护理念的典型体现。不同操作对应不同的权限要求:

4.1 无需权限的操作

以下操作在应用前台运行时无需任何权限:

操作 方法 说明
写入剪贴板 setData() / setDataSync() 模拟用户复制操作
清空剪贴板 clearData() / clearDataSync() 清理自身写入的内容
查询数据来源 getDataSource() 返回 bundle name
检测是否有数据 hasData() / hasDataSync() 返回 boolean
获取变更次数 getChangeCount() 返回数字
获取 MIME 类型 getMimeTypes() 返回类型列表
检测数据类型 hasDataType(mimeType) 返回 boolean
模式检测 detectPatterns(patterns) 返回匹配模式
远端数据检测 isRemoteData() 返回 boolean

4.2 需要权限的操作

操作 权限 API 版本
读取剪贴板内容 ohos.permission.READ_PASTEBOARD API 12+

4.3 权限声明

module.json5 中添加权限声明:

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

READ_PASTEBOARD用户授权级别的权限(user_grant),这意味着应用不能静默获得该权限。当应用首次调用 getData() 时,系统会弹出授权对话框,用户可以选择允许或拒绝。

4.4 设计哲学

这个权限模型的设计逻辑非常清晰:

  • 写入不需要权限:写入是用户的主动行为(Ctrl+C / 复制按钮),应用只是代为执行,不需要额外授权。
  • 读取需要权限:读取可能在用户不知情的情况下发生,涉及隐私风险(如后台读取密码、验证码),因此需要用户明确授权。
  • 元数据检测不需要权限:检测方法只返回"有没有数据""是什么类型"等元信息,不泄露实际内容,实现了隐私保护与功能便利性的平衡。

这种"读需要授权、写不需要、检测不需要"的三层权限模型,比 Android 的"读写都需要后台权限"更加精细和人性化。

五、数据类型与 MIME 支持

5.1 支持的 MIME 类型

@ohos.pasteboard 支持以下标准 MIME 类型:

MIME 类型 说明 数据格式
text/plain 纯文本 UTF-8 字符串
text/html HTML 格式化文本 HTML 字符串
text/uri URI 地址 URI 字符串
text/want Want 对象 序列化的 Want 数据
pixelMap 图片像素数据 PixelMap 对象

5.2 多类型数据共存

一个 PasteData 可以同时包含多种类型的数据。例如,从网页复制的内容可能同时包含 text/plain(纯文本)和 text/html(富文本)两份数据,粘贴时应用可以根据自身能力选择最合适的类型。

使用 getMimeTypes() 可以获取当前剪贴板中所有可用的数据类型:

const types: string[] = await this.sp.getMimeTypes();
// 可能返回: ["text/plain", "text/html"]

使用 hasDataType(mimeType) 可以检测是否包含特定类型:

const hasHtml = await this.sp.hasDataType('text/html');
if (hasHtml) {
  // 可以按 HTML 格式处理
}

5.3 创建多类型数据

// 创建纯文本记录
const textBytes = new util.TextEncoder().encodeInto('普通文本');
const textRecord = pasteboard.createRecord('text/plain', textBytes);

// 创建 HTML 记录
const htmlBytes = new util.TextEncoder().encodeInto('<b>粗体文本</b>');
const htmlRecord = pasteboard.createRecord('text/html', htmlBytes);

// 创建包含两种类型的 PasteData
const pasteData = pasteboard.createData('text/plain', textBytes);
// 通过 addRecord 添加更多类型
pasteData.addRecord(htmlRecord);

await this.sp.setData(pasteData);

这样,支持 HTML 的应用可以获取富文本,而只支持纯文本的应用也能正常粘贴,实现最大兼容性。

六、跨设备剪贴板

HarmonyOS 的分布式能力也延伸到了剪贴板。当用户在手机上复制内容后,可以在平板上直接粘贴,这就是分布式剪贴板。

6.1 检测数据来源

使用 isRemoteData() 可以判断当前剪贴板中的数据是来自本设备还是远端设备:

const isRemote = this.sp.isRemoteData();
if (isRemote) {
  console.log('数据来自其他设备');
} else {
  console.log('数据来自本设备');
}

6.2 远程剪贴板更新监听

除了本地剪贴板变化监听外,还有专门针对远程更新的 onRemoteUpdate() 方法:

this.sp.onRemoteUpdate((data: pasteboard.PasteData) => {
  console.log('收到远程设备剪贴板更新');
});

这为跨设备协同场景提供了精细的事件驱动机制。

6.3 应用共享选项(已废弃)

ShareOption 枚举曾经用于控制剪贴板数据的跨设备共享行为:

  • INAPP:仅在应用内共享
  • LOCALDEVICE:仅在本地设备共享
  • CROSSDEVICE:允许跨设备共享(已废弃)

随着 HarmonyOS NEXT 的演进,跨设备剪贴板的同步策略已由系统统一管理,应用不再需要手动设置共享选项。

七、实战 Demo:剪贴板实验室

下面我们来构建一个完整的剪贴板实验室页面,通过实际操作来体验所有 API。

7.1 页面布局设计

页面从上到下分为六个功能区:

  1. 写入剪贴板TextArea 输入文本 + 写入按钮
  2. 读取剪贴板:显示读取到的内容 + 读取按钮
  3. 剪贴板状态:展示 hasDatachangeCountdataSourceisRemoteDatamimeTypes
  4. 实时监听Toggle 开关控制 on('update') / off('update')
  5. 清空剪贴板:清空按钮
  6. 操作日志:记录所有操作的时间线

7.2 状态管理

页面使用 @State 装饰器管理以下状态变量:

@State inputText: string = '';           // 输入框文本
@State clipboardText: string = '';       // 读取到的剪贴板内容
@State hasContent: boolean = false;      // 剪贴板是否有数据
@State changeCount: number = 0;          // 变更次数
@State dataSource: string = '';          // 数据来源 bundle name
@State mimeTypes: string = '';           // MIME 类型列表
@State isRemote: boolean = false;        // 是否为远程数据
@State monitorOn: boolean = false;       // 监听开关状态
@State events: PasteEvent[] = [];        // 操作日志数组

7.3 核心方法实现

写入操作——使用 TextEncoder 将字符串编码为 Uint8Array,再通过 createData 封装为 PasteData,最后调用 setData 写入系统剪贴板:

private async writeToClipboard(): Promise<void> {
  if (this.inputText.trim() === '') {
    this.addLog('请输入文本内容');
    return;
  }
  try {
    const encoder: util.TextEncoder = new util.TextEncoder();
    const bytes: Uint8Array = encoder.encodeInto(this.inputText);
    const pasteData: pasteboard.PasteData =
      pasteboard.createData('text/plain', bytes);
    await this.sp.setData(pasteData);
    this.addLog('写入成功');
    this.refreshInfo();
  } catch (e) {
    this.addLog('写入失败: ' + (e as Error).message);
  }
}

读取操作——先检测是否有数据,再通过 getData 获取 PasteData,从第一条记录中提取文本:

private async readFromClipboard(): Promise<void> {
  try {
    const hasData = this.sp.hasDataSync();
    if (!hasData) {
      this.clipboardText = '(剪贴板为空)';
      this.addLog('剪贴板为空');
      return;
    }
    const pasteData = await this.sp.getData();
    const count = pasteData.getRecordCount();
    if (count > 0) {
      const record = pasteData.getRecord(0);
      const text = await record.convertToText();
      this.clipboardText = text;
      this.addLog('读取成功');
    }
    this.refreshInfo();
  } catch (e) {
    this.addLog('读取失败: ' + (e as Error).message);
    this.addLog('需要 ohos.permission.READ_PASTEBOARD 权限');
  }
}

状态刷新——批量获取剪贴板的各种状态信息:

private async refreshInfo(): Promise<void> {
  try {
    this.hasContent = this.sp.hasDataSync();
    this.changeCount = this.sp.getChangeCount();
    this.dataSource = this.sp.getDataSource();
    this.isRemote = this.sp.isRemoteData();
    const types: string[] = await this.sp.getMimeTypes();
    this.mimeTypes = types.join(', ');
  } catch (e) {
    // 静默处理
  }
}

监听切换——通过 Toggle 组件的开关状态来控制监听的注册和注销:

private toggleMonitor(): void {
  if (this.monitorOn) {
    this.sp.off('update');
    this.monitorOn = false;
    this.addLog('监听已关闭');
  } else {
    this.sp.on('update', () => {
      this.addLog('剪贴板已更新');
      this.refreshInfo();
    });
    this.monitorOn = true;
    this.addLog('监听已开启');
  }
}

操作日志——带时间戳的日志记录,方便追踪操作顺序:

private addLog(msg: string): void {
  const now = new Date();
  const ts = now.getHours().toString().padStart(2, '0') + ':' +
    now.getMinutes().toString().padStart(2, '0') + ':' +
    now.getSeconds().toString().padStart(2, '0');
  const entry: PasteEvent = { time: ts, msg: msg };
  const newEvents: PasteEvent[] = [entry];
  this.events = newEvents.concat(this.events).slice(0, 30);
}

7.4 权限说明

Demo 页面底部展示了权限声明模板和关键注意事项:

  1. setData / clearData / getDataSource:前台运行时无需权限
  2. getData:需要 ohos.permission.READ_PASTEBOARD 权限
  3. 检测类方法hasData / getChangeCount / getMimeTypes / hasDataType 无需权限
  4. 模拟器中可正常读写剪贴板,但跨设备剪贴板同步需真机测试

7.5 如何在模拟器中测试

由于模拟器中的剪贴板是独立的,你可以在 Demo 页面中进行以下操作来验证 API 行为:

  1. 写入测试:在 TextArea 输入任意文本,点击"写入剪贴板",观察操作日志中的"写入成功"记录
  2. 读取测试:点击"读取剪贴板",查看下方是否显示刚才写入的文本内容
  3. 状态检测:观察"剪贴板状态"区域中的各项指标变化(hasData 变为"是",changeCount 递增)
  4. 监听测试:打开"实时监听"开关,然后切换到其他应用复制一些内容,再切回 Demo 页面,观察日志中是否有"剪贴板已更新"的记录
  5. 清空测试:点击"清空剪贴板",再读取验证是否显示"剪贴板为空"

八、最佳实践与注意事项

8.1 隐私保护

剪贴板中可能包含敏感信息(密码、验证码、银行卡号、身份证号等),因此:

  • 最小权限原则:只在真正需要读取剪贴板时声明 READ_PASTEBOARD 权限
  • 用户知情:在 UI 中明确告知用户应用正在读取剪贴板
  • 不要后台读取:避免在后台或用户不可见的情况下读取剪贴板
  • 及时清理敏感数据:如果应用将密码、验证码写入剪贴板,建议在使用后清空

8.2 性能优化

  • 优先使用事件监听on('update') 比轮询 hasData() 更高效
  • 使用同步方法进行快速检测hasDataSync() / getChangeCount() 比异步版本更快,适合在 UI 初始化时使用
  • 及时注销监听:在组件销毁时调用 off('update'),避免内存泄漏

8.3 错误处理

写操作可能因为系统剪贴板被其他进程占用而失败,读操作可能因为权限被拒绝而失败。建议对所有剪贴板操作进行 try-catch 包裹,并提供友好的用户提示:

try {
  await this.sp.getData();
} catch (err) {
  const error = err as BusinessError;
  if (error.code === 201) {
    // 权限被拒绝
    this.showPermissionTip();
  } else {
    // 其他错误
    this.addLog('读取失败: ' + error.message);
  }
}

8.4 跨设备协同

  • 跨设备剪贴板同步需要设备登录同一华为账号,并开启蓝牙和 Wi-Fi
  • isRemoteData() 可以帮助界面区分数据来源,可能需要在 UI 上做出区分(如标注"来自手机")
  • 跨设备同步有一定延迟,不要假设远程数据能立即到达

8.5 ArkTS 严格模式注意事项

在编写剪贴板相关代码时,需要注意 ArkTS 严格模式的类型要求:

// 错误:inline 类型
const bytes = encoder.encodeInto(text); // 隐式 any

// 正确:显式类型声明
const bytes: Uint8Array = encoder.encodeInto(text);
const pasteData: pasteboard.PasteData = pasteboard.createData('text/plain', bytes);

对于 ForEach 中的回调参数和数组操作,也需要显式的类型标注:

// 错误:untyped object literal
this.events = [{ time: ts, msg: msg }].concat(...)

// 正确:先声明类型再使用
const entry: PasteEvent = { time: ts, msg: msg };
const newEvents: PasteEvent[] = [entry];
this.events = newEvents.concat(this.events).slice(0, 30);

九、总结

@ohos.pasteboard 是 HarmonyOS NEXT 中一个看似简单但内涵丰富的 API。它提供了一套完整的剪贴板管理方案,涵盖了读写、检测、监听和跨设备四大能力。通过本文的深入讲解和实战 Demo,你应该已经掌握了以下内容:

  1. 架构理解SystemPasteboard 单例模式的设计理念和 API 组织结构
  2. 核心操作setData 写入、getData 读取、clearData 清空的完整流程
  3. 事件监听on('update') / off('update') 实现实时剪贴板感知
  4. 权限模型:非对称权限设计(写无需授权,读需要授权)的隐私保护理念
  5. 类型检测getMimeTypes / hasDataType / detectPatterns 等元数据检测方法
  6. 跨设备能力isRemoteData / onRemoteUpdate 的分布式协同支持
  7. 实战经验:ArkTS 严格模式下的类型处理、错误处理、生命周期管理

剪贴板 API 虽然不像振动器、传感器那样"引人注目",但它在日常开发中的应用频率极高。无论是文本编辑器、翻译工具、密码管理器,还是任何需要处理用户复制粘贴行为的应用,都需要深入理解和使用剪贴板 API。

在实际开发中,建议遵循以下原则:

  • 前台读写 + 事件监听的技术组合是最佳实践
  • 异步操作为主,同步方法用于快速检测
  • 权限申请要克制,只在真正需要时申请 READ_PASTEBOARD
  • 隐私保护要到位,不要滥用剪贴板读取能力
Logo

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

更多推荐