鸿蒙跨设备剪贴板开发指难
一、概述
1.1 什么是跨设备剪贴板
跨设备剪贴板(Distributed Pasteboard)是HarmonyOS提供的一项核心分布式能力,允许用户在不同设备间无缝地复制和粘贴内容。当用户拥有多台HarmonyOS设备时,可以在设备A上复制一段文本或图片,然后在设备B上直接粘贴,系统会自动完成数据的跨设备同步。
1.2 本地剪贴板 vs 跨设备剪贴板
| 特性 | 本地剪贴板 | 跨设备剪贴板 |
|---|---|---|
| 作用范围 | 仅限当前设备内 | 登录同一账号的多台设备间 |
| 数据同步 | 无需同步 | 系统自动同步 |
| 网络要求 | 无 | 需要Wi-Fi和蓝牙 |
| 使用场景 | 应用内或应用间复制粘贴 | 跨设备内容共享 |
| 数据有效期 | 持续有效(直到被覆盖) | 2分钟自动过期 |
1.3 核心特性
- 自动同步:系统剪贴板服务自动处理数据同步,开发者无需感知同步过程
- 多格式支持:支持纯文本、图片(PNG、JPEG)、富文本、HTML等多种数据类型
- 透明体验:用户操作与本地剪贴板完全一致,无需额外学习成本
- 安全可靠:基于华为账号认证,确保数据传输安全
- 高效传输:通过Wi-Fi、蓝牙或局域网通道智能选择最优传输方式
1.4 典型应用场景
| 场景 | 描述 | 示例 |
|---|---|---|
| 浏览器 | 跨设备复制链接或网页内容 | 在手机上复制链接,粘贴到平板浏览器 |
| 备忘录 | 跨设备同步笔记内容 | 在PC上复制备忘录,粘贴到手机备忘录 |
| 邮件 | 跨设备编辑邮件内容 | 在平板复制邮件正文,粘贴到手机邮件应用 |
| 文档编辑 | 跨设备传输文本和图片 | 在手机复制图片,粘贴到平板文档中 |
| 社交应用 | 跨设备分享聊天内容 | 在PC复制消息,粘贴到手机微信 |

二、工作原理
2.1 架构设计
graph TB
subgraph 设备A[设备A - 源端]
A1[应用层]
A2[Pasteboard API]
A3[系统剪贴板服务]
end
subgraph 传输层[分布式传输通道]
T1[Wi-Fi]
T2[蓝牙]
T3[局域网]
end
subgraph 设备B[设备B - 目标端]
B1[系统剪贴板服务]
B2[Pasteboard API]
B3[应用层]
end
A1 -->|setData| A2
A2 --> A3
A3 -->|同步数据| T1
A3 -->|同步数据| T2
A3 -->|同步数据| T3
T1 --> B1
T2 --> B1
T3 --> B1
B1 --> B2
B2 -->|getData| B3
style A1 fill:#e3f2fd
style A3 fill:#bbdefb
style B1 fill:#bbdefb
style B3 fill:#e3f2fd
style T1 fill:#fff3e0
style T2 fill:#fff3e0
style T3 fill:#fff3e0
2.2 数据同步流程
sequenceDiagram
participant 用户A as 用户(设备A)
participant 应用A as 应用A
participant 服务A as 剪贴板服务A
participant 网络 as 分布式传输通道
participant 服务B as 剪贴板服务B
participant 应用B as 应用B
participant 用户B as 用户(设备B)
用户A->>应用A: 复制文本/图片
应用A->>应用A: 调用setData()
应用A->>服务A: 写入剪贴板数据
Note over 服务A: 检测到数据写入
服务A->>服务A: 判断是否需要跨设备同步
alt 满足跨设备同步条件
服务A->>服务A: 序列化数据
服务A->>网络: 通过Wi-Fi/蓝牙/局域网发送
Note over 网络: 数据传输中...
网络->>服务B: 接收同步数据
服务B->>服务B: 反序列化数据
服务B->>服务B: 写入本地剪贴板
Note over 服务B: 数据同步完成
Note over 服务B: 有效期2分钟
用户B->>应用B: 执行粘贴操作
应用B->>应用B: 调用getData()
应用B->>服务B: 读取剪贴板数据
服务B->>应用B: 返回数据
应用B->>用户B: 显示粘贴内容
else 不满足同步条件
服务A->>服务A: 仅本地存储
end
2.3 同步条件
跨设备剪贴板自动同步需满足以下条件:
- 账号认证:双端设备登录同一华为账号
- 网络连接:
- Wi-Fi:已打开
- 蓝牙:已打开
- 建议同一局域网(提升传输速度)
- 设备状态:
- 设备解锁
- 屏幕亮屏
- 数据有效期:复制后2分钟内有效
三、前置条件
3.1 系统要求
- 系统版本:HarmonyOS NEXT Developer Preview0 及以上
- 设备类型:手机、平板、PC(2in1设备)
3.2 账号与网络要求
| 要求项 | 说明 | 重要性 |
|---|---|---|
| 华为账号 | 双端设备需登录同一账号 | 必须 |
| Wi-Fi | 需要打开Wi-Fi开关 | 必须 |
| 蓝牙 | 需要打开蓝牙开关 | 必须 |
| 局域网 | 建议接入同一局域网 | 推荐 |
| 设备解锁 | 传输过程中设备需解锁 | 必须 |
| 屏幕亮屏 | 传输过程中屏幕需亮屏 | 必须 |
3.3 权限配置
module.json5 权限申请
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.READ_PASTEBOARD",
"reason": "$string:read_pasteboard_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
}
]
}
}
权限说明
- 权限名称:
ohos.permission.READ_PASTEBOARD - 权限级别:system_basic(系统基础权限)
- 授权方式:user_grant(用户授权)
- 使用场景:应用在后台访问剪贴板时需要申请此权限
- 不需要申请的场景:应用在前台运行且处于焦点状态时
3.4 约束与限制
| 约束项 | 说明 |
|---|---|
| 数据有效期 | 复制后2分钟内有效,超时自动失效 |
| 数据大小 | 建议单次数据不超过20MB |
| 支持格式 | 文本、图片(PNG/JPEG)、HTML、URI等 |
| 不支持格式 | 不支持自定义二进制数据的跨设备同步 |
| 网络依赖 | 无网络时无法跨设备同步 |
| 设备数量 | 同一账号下所有在线设备均可同步 |
四、核心API详解
4.1 API概览
| API | 功能 | 类型 |
|---|---|---|
getSystemPasteboard() | 获取系统剪贴板对象 | 静态方法 |
createData() | 创建剪贴板数据对象 | 静态方法 |
setData() | 写入剪贴板数据 | 实例方法 |
getData() | 读取剪贴板数据 | 实例方法 |
getRecordCount() | 获取数据条目数量 | 实例方法 |
getPrimaryMimeType() | 获取首个数据的MIME类型 | 实例方法 |
getPrimaryText() | 获取纯文本内容 | 实例方法 |
getPrimaryImage() | 获取图片数据 | 实例方法 |
hasData() | 检查是否有数据 | 实例方法 |
clear() | 清空剪贴板 | 实例方法 |
4.2 getSystemPasteboard - 获取剪贴板对象
获取系统剪贴板的单例对象。
import { pasteboard } from '@kit.BasicServicesKit';
// 获取系统剪贴板对象
let systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard();
返回值:
SystemPasteboard:系统剪贴板实例
说明:
- 系统剪贴板是单例模式,多次调用返回同一实例
- 该对象可在应用生命周期内重复使用
4.3 createData - 创建剪贴板数据
创建指定MIME类型的剪贴板数据对象。
import { pasteboard } from '@kit.BasicServicesKit';
// 创建纯文本数据
let textData: pasteboard.PasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
'这是要复制的文本内容'
);
// 创建HTML数据
let htmlData: pasteboard.PasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_HTML,
'<h1>这是HTML标题</h1><p>这是段落</p>'
);
// 创建URI数据
let uriData: pasteboard.PasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_URI,
'https://www.example.com'
);
参数:
mimeType: string:数据的MIME类型value: ValueType:数据内容
返回值:
PasteData:剪贴板数据对象
支持的MIME类型常量:
| 常量 | 值 | 描述 |
|---|---|---|
MIMETYPE_TEXT_PLAIN | 'text/plain' | 纯文本 |
MIMETYPE_TEXT_HTML | 'text/html' | HTML格式文本 |
MIMETYPE_TEXT_URI | 'text/uri' | URI链接 |
MIMETYPE_TEXT_WANT | 'text/want' | Want对象 |
MIMETYPE_PIXELMAP | 'pixelMap' | 图片PixelMap |
4.4 setData - 写入剪贴板
将数据写入系统剪贴板。
import { pasteboard } from '@kit.BasicServicesKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 方式1:使用Promise
async function copyText(text: string) {
try {
let pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text);
let systemPasteboard = pasteboard.getSystemPasteboard();
await systemPasteboard.setData(pasteData);
console.info('文本复制成功');
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`复制失败: ${err.message}`);
}
}
// 方式2:使用callback
function copyTextCallback(text: string) {
let pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text);
let systemPasteboard = pasteboard.getSystemPasteboard();
systemPasteboard.setData(pasteData, (err: BusinessError) => {
if (err) {
console.error(`复制失败: ${err.message}`);
} else {
console.info('文本复制成功');
}
});
}
参数:
data: PasteData:要写入的剪贴板数据
返回值:
Promise<void>:Promise形式- 无返回值(callback形式)
错误码:
12900003:剪贴板服务不可用12900004:数据格式不支持
4.5 getData - 读取剪贴板
从系统剪贴板读取数据。
import { pasteboard } from '@kit.BasicServicesKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 方式1:使用Promise
async function pasteText(): Promise<string> {
try {
let systemPasteboard = pasteboard.getSystemPasteboard();
let pasteData: pasteboard.PasteData = await systemPasteboard.getData();
// 获取纯文本内容
let text: string = pasteData.getPrimaryText();
console.info(`粘贴的文本: ${text}`);
return text;
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`粘贴失败: ${err.message}`);
return '';
}
}
// 方式2:使用callback
function pasteTextCallback() {
let systemPasteboard = pasteboard.getSystemPasteboard();
systemPasteboard.getData((err: BusinessError, pasteData: pasteboard.PasteData) => {
if (err) {
console.error(`粘贴失败: ${err.message}`);
return;
}
let text: string = pasteData.getPrimaryText();
console.info(`粘贴的文本: ${text}`);
});
}
返回值:
Promise<PasteData>:Promise形式- 通过callback参数返回(callback形式)
错误码:
12900003:剪贴板服务不可用
4.6 PasteData 类方法详解
getRecordCount - 获取数据条目数
let systemPasteboard = pasteboard.getSystemPasteboard();
let pasteData = await systemPasteboard.getData();
let count: number = pasteData.getRecordCount();
console.info(`剪贴板中有 ${count} 条数据`);
getPrimaryMimeType - 获取首个数据类型
let mimeType: string = pasteData.getPrimaryMimeType();
console.info(`数据类型: ${mimeType}`);
// 根据类型处理数据
if (mimeType === pasteboard.MIMETYPE_TEXT_PLAIN) {
console.info('剪贴板包含纯文本');
} else if (mimeType === pasteboard.MIMETYPE_TEXT_HTML) {
console.info('剪贴板包含HTML');
} else if (mimeType === pasteboard.MIMETYPE_PIXELMAP) {
console.info('剪贴板包含图片');
}
getPrimaryText - 获取纯文本
let text: string = pasteData.getPrimaryText();
if (text) {
console.info(`获取到文本: ${text}`);
} else {
console.warn('剪贴板中没有文本数据');
}
getPrimaryImage - 获取图片
import { image } from '@kit.ImageKit';
let pixelMap: image.PixelMap | null = pasteData.getPrimaryImage();
if (pixelMap) {
console.info('获取到图片数据');
// 使用pixelMap显示图片
} else {
console.warn('剪贴板中没有图片数据');
}
getPrimaryHtml - 获取HTML
let html: string = pasteData.getPrimaryHtml();
if (html) {
console.info(`获取到HTML: ${html}`);
}
hasData - 检查是否有数据
let systemPasteboard = pasteboard.getSystemPasteboard();
let hasData: boolean = systemPasteboard.hasData();
if (hasData) {
console.info('剪贴板有数据');
} else {
console.info('剪贴板为空');
}
clear - 清空剪贴板
let systemPasteboard = pasteboard.getSystemPasteboard();
systemPasteboard.clear();
console.info('剪贴板已清空');
4.7 监听剪贴板变化
监听剪贴板内容变化事件。
import { pasteboard } from '@kit.BasicServicesKit';
let systemPasteboard = pasteboard.getSystemPasteboard();
// 定义监听回调
function onPasteboardChanged() {
console.info('剪贴板内容已变化');
// 读取新的剪贴板内容
systemPasteboard.getData().then((pasteData: pasteboard.PasteData) => {
let text = pasteData.getPrimaryText();
console.info(`新的剪贴板内容: ${text}`);
});
}
// 注册监听
systemPasteboard.on('update', onPasteboardChanged);
// 取消监听
// systemPasteboard.off('update', onPasteboardChanged);
事件类型:
'update':剪贴板内容更新事件
使用场景:
- 实时同步显示剪贴板内容
- 检测跨设备剪贴板同步
- 实现剪贴板历史记录功能
五、完整开发示例
5.1 文本跨设备复制粘贴
场景说明
实现一个简单的笔记应用,支持在不同设备间复制粘贴文本内容。
完整代码实现
import { pasteboard } from '@kit.BasicServicesKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { promptAction } from '@kit.ArkUI';
const TAG = '[ClipboardDemo]';
const DOMAIN = 0xFF00;
@Entry
@Component
struct TextClipboardPage {
@State inputText: string = '';
@State pastedText: string = '';
@State clipboardStatus: string = '就绪';
private systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard();
aboutToAppear() {
// 监听剪贴板变化
this.systemPasteboard.on('update', () => {
hilog.info(DOMAIN, TAG, '剪贴板内容已更新');
this.clipboardStatus = '剪贴板已更新';
});
}
aboutToDisappear() {
// 取消监听
this.systemPasteboard.off('update');
}
/**
* 复制文本到剪贴板
*/
async copyText() {
if (!this.inputText) {
this.showToast('请输入要复制的文本');
return;
}
try {
hilog.info(DOMAIN, TAG, `开始复制文本: ${this.inputText}`);
// 1. 创建剪贴板数据
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
this.inputText
);
// 2. 写入剪贴板
await this.systemPasteboard.setData(pasteData);
hilog.info(DOMAIN, TAG, '文本复制成功');
this.clipboardStatus = '复制成功';
this.showToast('文本已复制,可在其他设备粘贴');
} catch (error) {
let err: BusinessError = error as BusinessError;
hilog.error(DOMAIN, TAG, `复制失败: ${err.message}`);
this.clipboardStatus = `复制失败: ${err.message}`;
this.showToast('复制失败');
}
}
/**
* 从剪贴板粘贴文本
*/
async pasteText() {
try {
hilog.info(DOMAIN, TAG, '开始粘贴文本');
// 1. 检查剪贴板是否有数据
let hasData = this.systemPasteboard.hasData();
if (!hasData) {
this.showToast('剪贴板为空');
return;
}
// 2. 读取剪贴板数据
let pasteData: pasteboard.PasteData = await this.systemPasteboard.getData();
// 3. 检查数据类型
let mimeType = pasteData.getPrimaryMimeType();
hilog.info(DOMAIN, TAG, `剪贴板数据类型: ${mimeType}`);
if (mimeType !== pasteboard.MIMETYPE_TEXT_PLAIN) {
this.showToast('剪贴板中不是纯文本数据');
return;
}
// 4. 获取文本内容
let text = pasteData.getPrimaryText();
this.pastedText = text;
hilog.info(DOMAIN, TAG, `粘贴成功: ${text}`);
this.clipboardStatus = '粘贴成功';
this.showToast('粘贴成功');
} catch (error) {
let err: BusinessError = error as BusinessError;
hilog.error(DOMAIN, TAG, `粘贴失败: ${err.message}`);
this.clipboardStatus = `粘贴失败: ${err.message}`;
this.showToast('粘贴失败');
}
}
/**
* 清空剪贴板
*/
clearClipboard() {
this.systemPasteboard.clear();
this.clipboardStatus = '剪贴板已清空';
this.showToast('剪贴板已清空');
hilog.info(DOMAIN, TAG, '剪贴板已清空');
}
/**
* 显示Toast提示
*/
private showToast(message: string) {
promptAction.showToast({
message: message,
duration: 2000
});
}
build() {
Column() {
// 标题栏
Text('跨设备剪贴板示例')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 10 })
Text(`状态: ${this.clipboardStatus}`)
.fontSize(14)
.fontColor('#999999')
.margin({ bottom: 20 })
// 复制区域
Column() {
Text('复制文本')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 10 })
TextArea({ placeholder: '输入要复制的文本' })
.width('100%')
.height(100)
.fontSize(16)
.onChange((value: string) => {
this.inputText = value;
})
Button('复制到剪贴板')
.width('100%')
.margin({ top: 10 })
.onClick(() => {
this.copyText();
})
}
.width('90%')
.padding(20)
.backgroundColor('#f5f5f5')
.borderRadius(12)
.margin({ bottom: 20 })
// 粘贴区域
Column() {
Text('粘贴文本')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 10 })
Text(this.pastedText || '点击按钮从剪贴板粘贴')
.width('100%')
.height(100)
.padding(10)
.backgroundColor(Color.White)
.borderRadius(8)
.fontSize(16)
Button('从剪贴板粘贴')
.width('100%')
.margin({ top: 10 })
.onClick(() => {
this.pasteText();
})
}
.width('90%')
.padding(20)
.backgroundColor('#f5f5f5')
.borderRadius(12)
.margin({ bottom: 20 })
// 清空按钮
Button('清空剪贴板')
.width('90%')
.backgroundColor('#F44336')
.onClick(() => {
this.clearClipboard();
})
// 提示信息
Text('提示:在另一台设备登录同一账号,即可跨设备粘贴')
.fontSize(12)
.fontColor('#666666')
.margin({ top: 20 })
.textAlign(TextAlign.Center)
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
}
}
5.2 图片跨设备复制粘贴
场景说明
实现图片在不同设备间的复制和粘贴功能。
完整代码实现
import { pasteboard } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { promptAction } from '@kit.ArkUI';
const TAG = '[ImageClipboard]';
const DOMAIN = 0xFF00;
@Entry
@Component
struct ImageClipboardPage {
@State imageUri: string = ''; // 要复制的图片URI
@State pastedImagePixelMap: image.PixelMap | null = null; // 粘贴的图片
@State statusMessage: string = '就绪';
private systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard();
/**
* 复制图片到剪贴板
*/
async copyImage() {
if (!this.imageUri) {
this.showToast('请先选择图片');
return;
}
try {
hilog.info(DOMAIN, TAG, `开始复制图片: ${this.imageUri}`);
// 1. 加载图片为PixelMap
let pixelMap = await this.loadImageAsPixelMap(this.imageUri);
// 2. 创建图片类型的剪贴板数据
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_PIXELMAP,
pixelMap
);
// 3. 写入剪贴板
await this.systemPasteboard.setData(pasteData);
hilog.info(DOMAIN, TAG, '图片复制成功');
this.statusMessage = '图片复制成功';
this.showToast('图片已复制到剪贴板');
} catch (error) {
let err: BusinessError = error as BusinessError;
hilog.error(DOMAIN, TAG, `图片复制失败: ${err.message}`);
this.statusMessage = `复制失败: ${err.message}`;
this.showToast('图片复制失败');
}
}
/**
* 从剪贴板粘贴图片
*/
async pasteImage() {
try {
hilog.info(DOMAIN, TAG, '开始粘贴图片');
// 1. 检查剪贴板是否有数据
if (!this.systemPasteboard.hasData()) {
this.showToast('剪贴板为空');
return;
}
// 2. 读取剪贴板数据
let pasteData: pasteboard.PasteData = await this.systemPasteboard.getData();
// 3. 检查数据类型
let mimeType = pasteData.getPrimaryMimeType();
hilog.info(DOMAIN, TAG, `剪贴板数据类型: ${mimeType}`);
if (mimeType !== pasteboard.MIMETYPE_PIXELMAP) {
this.showToast('剪贴板中不是图片数据');
return;
}
// 4. 获取图片PixelMap
let pixelMap = pasteData.getPrimaryImage();
if (pixelMap) {
this.pastedImagePixelMap = pixelMap;
hilog.info(DOMAIN, TAG, '图片粘贴成功');
this.statusMessage = '图片粘贴成功';
this.showToast('图片粘贴成功');
} else {
this.showToast('无法获取图片数据');
}
} catch (error) {
let err: BusinessError = error as BusinessError;
hilog.error(DOMAIN, TAG, `图片粘贴失败: ${err.message}`);
this.statusMessage = `粘贴失败: ${err.message}`;
this.showToast('图片粘贴失败');
}
}
/**
* 加载图片为PixelMap
*/
private async loadImageAsPixelMap(uri: string): Promise<image.PixelMap> {
// 创建ImageSource
let imageSource = image.createImageSource(uri);
// 解码为PixelMap
let pixelMap = await imageSource.createPixelMap();
return pixelMap;
}
/**
* 选择图片(示例方法)
*/
selectImage() {
// 实际项目中应使用picker选择图片
// 这里使用示例图片路径
this.imageUri = 'file://data/storage/el2/base/haps/entry/files/sample.jpg';
this.statusMessage = '图片已选择';
this.showToast('图片已选择');
}
private showToast(message: string) {
promptAction.showToast({
message: message,
duration: 2000
});
}
build() {
Column() {
Text('图片剪贴板示例')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 10 })
Text(`状态: ${this.statusMessage}`)
.fontSize(14)
.fontColor('#999999')
.margin({ bottom: 20 })
// 复制区域
Column() {
Text('复制图片')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 10 })
if (this.imageUri) {
Image(this.imageUri)
.width('100%')
.height(200)
.objectFit(ImageFit.Contain)
.borderRadius(8)
} else {
Column() {
Text('未选择图片')
.fontSize(16)
.fontColor('#999999')
}
.width('100%')
.height(200)
.justifyContent(FlexAlign.Center)
.backgroundColor('#f0f0f0')
.borderRadius(8)
}
Row() {
Button('选择图片')
.layoutWeight(1)
.margin({ right: 5 })
.onClick(() => {
this.selectImage();
})
Button('复制图片')
.layoutWeight(1)
.margin({ left: 5 })
.onClick(() => {
this.copyImage();
})
}
.width('100%')
.margin({ top: 10 })
}
.width('90%')
.padding(20)
.backgroundColor('#f5f5f5')
.borderRadius(12)
.margin({ bottom: 20 })
// 粘贴区域
Column() {
Text('粘贴图片')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 10 })
if (this.pastedImagePixelMap) {
Image(this.pastedImagePixelMap)
.width('100%')
.height(200)
.objectFit(ImageFit.Contain)
.borderRadius(8)
} else {
Column() {
Text('点击按钮从剪贴板粘贴图片')
.fontSize(16)
.fontColor('#999999')
}
.width('100%')
.height(200)
.justifyContent(FlexAlign.Center)
.backgroundColor('#f0f0f0')
.borderRadius(8)
}
Button('从剪贴板粘贴')
.width('100%')
.margin({ top: 10 })
.onClick(() => {
this.pasteImage();
})
}
.width('90%')
.padding(20)
.backgroundColor('#f5f5f5')
.borderRadius(12)
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
}
}
5.3 富文本(HTML)跨设备复制粘贴
场景说明
支持富文本格式的跨设备复制粘贴,适用于文档编辑器、邮件应用等。
完整代码实现
import { pasteboard } from '@kit.BasicServicesKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { webview } from '@kit.ArkWeb';
const TAG = '[RichTextClipboard]';
const DOMAIN = 0xFF00;
@Entry
@Component
struct RichTextClipboardPage {
@State htmlContent: string = '';
@State pastedHtmlContent: string = '';
@State plainText: string = '';
private systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard();
private webViewController: webview.WebviewController = new webview.WebviewController();
/**
* 复制富文本到剪贴板
* 同时复制HTML和纯文本格式
*/
async copyRichText() {
if (!this.htmlContent && !this.plainText) {
console.warn('没有内容可复制');
return;
}
try {
hilog.info(DOMAIN, TAG, '开始复制富文本');
// 1. 创建剪贴板数据(HTML格式)
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_HTML,
this.htmlContent
);
// 2. 添加纯文本作为备选格式
if (this.plainText) {
let plainTextRecord = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
this.plainText
);
pasteData.addRecord(plainTextRecord);
}
// 3. 写入剪贴板
await this.systemPasteboard.setData(pasteData);
hilog.info(DOMAIN, TAG, '富文本复制成功');
console.info('富文本已复制到剪贴板');
} catch (error) {
let err: BusinessError = error as BusinessError;
hilog.error(DOMAIN, TAG, `富文本复制失败: ${err.message}`);
}
}
/**
* 从剪贴板粘贴富文本
*/
async pasteRichText() {
try {
hilog.info(DOMAIN, TAG, '开始粘贴富文本');
if (!this.systemPasteboard.hasData()) {
console.warn('剪贴板为空');
return;
}
// 1. 读取剪贴板数据
let pasteData: pasteboard.PasteData = await this.systemPasteboard.getData();
// 2. 检查数据类型
let mimeType = pasteData.getPrimaryMimeType();
hilog.info(DOMAIN, TAG, `剪贴板数据类型: ${mimeType}`);
// 3. 优先获取HTML格式
if (mimeType === pasteboard.MIMETYPE_TEXT_HTML) {
let html = pasteData.getPrimaryHtml();
this.pastedHtmlContent = html;
hilog.info(DOMAIN, TAG, `粘贴HTML内容: ${html}`);
}
// 4. 如果没有HTML,获取纯文本
else if (mimeType === pasteboard.MIMETYPE_TEXT_PLAIN) {
let text = pasteData.getPrimaryText();
this.pastedHtmlContent = `<p>${text}</p>`;
hilog.info(DOMAIN, TAG, `粘贴纯文本: ${text}`);
}
console.info('富文本粘贴成功');
} catch (error) {
let err: BusinessError = error as BusinessError;
hilog.error(DOMAIN, TAG, `富文本粘贴失败: ${err.message}`);
}
}
build() {
Column() {
Text('富文本剪贴板示例')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 20 })
// 输入区域
Column() {
Text('输入富文本')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 10 })
TextArea({ placeholder: '输入HTML内容,如:<h1>标题</h1><p>段落</p>' })
.width('100%')
.height(120)
.fontSize(14)
.onChange((value: string) => {
this.htmlContent = value;
})
TextArea({ placeholder: '输入纯文本(备选)' })
.width('100%')
.height(80)
.fontSize(14)
.margin({ top: 10 })
.onChange((value: string) => {
this.plainText = value;
})
Button('复制到剪贴板')
.width('100%')
.margin({ top: 10 })
.onClick(() => {
this.copyRichText();
})
}
.width('90%')
.padding(20)
.backgroundColor('#f5f5f5')
.borderRadius(12)
.margin({ bottom: 20 })
// 粘贴区域
Column() {
Text('粘贴富文本')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 10 })
// 使用Web组件渲染HTML
Web({ src: '', controller: this.webViewController })
.width('100%')
.height(200)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.onControllerAttached(() => {
if (this.pastedHtmlContent) {
this.webViewController.loadData(this.pastedHtmlContent);
}
})
Button('从剪贴板粘贴')
.width('100%')
.margin({ top: 10 })
.onClick(() => {
this.pasteRichText();
// 刷新Web组件显示
if (this.pastedHtmlContent) {
this.webViewController.loadData(this.pastedHtmlContent);
}
})
}
.width('90%')
.padding(20)
.backgroundColor('#f5f5f5')
.borderRadius(12)
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
}
}
六、高级特性
6.1 多格式数据同时复制
支持同时复制多种格式的数据,接收端可根据需要选择合适的格式。
import { pasteboard } from '@kit.BasicServicesKit';
async function copyMultiFormatData() {
try {
// 1. 创建HTML格式的主数据
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_HTML,
'<h1>这是标题</h1><p>这是段落</p>'
);
// 2. 添加纯文本格式(作为备选)
let plainTextData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
'这是标题\n这是段落'
);
pasteData.addRecord(plainTextData);
// 3. 添加URI格式
let uriData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_URI,
'https://example.com/article'
);
pasteData.addRecord(uriData);
// 4. 写入剪贴板
let systemPasteboard = pasteboard.getSystemPasteboard();
await systemPasteboard.setData(pasteData);
console.info('多格式数据复制成功');
} catch (error) {
console.error(`复制失败: ${error}`);
}
}
// 粘贴时遍历所有格式
async function pasteMultiFormatData() {
let systemPasteboard = pasteboard.getSystemPasteboard();
let pasteData = await systemPasteboard.getData();
let recordCount = pasteData.getRecordCount();
console.info(`剪贴板包含 ${recordCount} 条记录`);
// 遍历所有记录
for (let i = 0; i < recordCount; i++) {
let record = pasteData.getRecord(i);
let mimeType = record.mimeType;
switch (mimeType) {
case pasteboard.MIMETYPE_TEXT_HTML:
console.info(`HTML: ${record.htmlText}`);
break;
case pasteboard.MIMETYPE_TEXT_PLAIN:
console.info(`纯文本: ${record.plainText}`);
break;
case pasteboard.MIMETYPE_TEXT_URI:
console.info(`URI: ${record.uri}`);
break;
}
}
}
6.2 剪贴板数据延迟加载
对于大数据(如高分辨率图片),可以使用延迟加载机制提升性能。
import { pasteboard } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';
/**
* 延迟加载图片数据
*/
class ImageDataProvider implements pasteboard.PasteDataProvider {
private imageUri: string;
constructor(imageUri: string) {
this.imageUri = imageUri;
}
/**
* 当需要数据时才调用此方法加载
*/
async getData(type: string): Promise<pasteboard.PasteDataRecord> {
console.info(`延迟加载数据,类型: ${type}`);
// 加载图片
let imageSource = image.createImageSource(this.imageUri);
let pixelMap = await imageSource.createPixelMap();
// 创建记录
let record = pasteboard.createData(pasteboard.MIMETYPE_PIXELMAP, pixelMap);
return record;
}
}
// 使用延迟加载
async function copyImageWithDelay(imageUri: string) {
let systemPasteboard = pasteboard.getSystemPasteboard();
// 创建数据提供者
let provider = new ImageDataProvider(imageUri);
// 创建带延迟加载的剪贴板数据
let pasteData = new pasteboard.PasteData(provider);
// 写入剪贴板(此时数据未真正加载)
await systemPasteboard.setData(pasteData);
console.info('图片引用已复制(延迟加载)');
}
6.3 剪贴板数据安全
敏感数据处理
import { pasteboard } from '@kit.BasicServicesKit';
/**
* 复制敏感数据(如密码)
* 设置敏感标志,防止被第三方应用读取
*/
async function copySensitiveData(password: string) {
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
password
);
// 设置为敏感数据(仅限本应用读取)
pasteData.setShareOption(pasteboard.ShareOption.LocalDevice);
let systemPasteboard = pasteboard.getSystemPasteboard();
await systemPasteboard.setData(pasteData);
console.info('敏感数据已复制(本地设备限制)');
}
ShareOption 选项:
| 选项 | 值 | 说明 |
|---|---|---|
InApp | 0 | 仅限本应用内粘贴 |
LocalDevice | 1 | 仅限本地设备粘贴 |
CrossDevice | 2 | 允许跨设备粘贴(默认) |
检查数据来源
async function checkPasteDataSource() {
let systemPasteboard = pasteboard.getSystemPasteboard();
let pasteData = await systemPasteboard.getData();
// 获取数据来源信息
let sourceInfo = pasteData.getDataSource();
console.info(`数据来源: ${sourceInfo}`);
// 根据来源决定是否信任
if (sourceInfo === 'trusted_app') {
// 处理数据
} else {
console.warn('数据来源不可信');
}
}
6.4 剪贴板历史记录
实现剪贴板历史记录功能,记录最近的复制内容。
import { pasteboard } from '@kit.BasicServicesKit';
interface ClipboardHistoryItem {
content: string;
mimeType: string;
timestamp: number;
}
@Component
struct ClipboardHistory {
@State historyList: ClipboardHistoryItem[] = [];
private maxHistoryCount: number = 20;
private systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard();
aboutToAppear() {
// 监听剪贴板变化
this.systemPasteboard.on('update', () => {
this.recordClipboardChange();
});
// 加载历史记录
this.loadHistory();
}
/**
* 记录剪贴板变化
*/
private async recordClipboardChange() {
try {
let pasteData = await this.systemPasteboard.getData();
let mimeType = pasteData.getPrimaryMimeType();
let content = '';
if (mimeType === pasteboard.MIMETYPE_TEXT_PLAIN) {
content = pasteData.getPrimaryText();
} else if (mimeType === pasteboard.MIMETYPE_TEXT_HTML) {
content = pasteData.getPrimaryHtml();
}
// 添加到历史记录
if (content) {
let item: ClipboardHistoryItem = {
content: content,
mimeType: mimeType,
timestamp: Date.now()
};
this.historyList.unshift(item);
// 限制历史记录数量
if (this.historyList.length > this.maxHistoryCount) {
this.historyList.pop();
}
// 保存到持久化存储
this.saveHistory();
}
} catch (error) {
console.error(`记录剪贴板历史失败: ${error}`);
}
}
/**
* 从历史记录恢复到剪贴板
*/
private async restoreFromHistory(item: ClipboardHistoryItem) {
try {
let pasteData = pasteboard.createData(item.mimeType, item.content);
await this.systemPasteboard.setData(pasteData);
console.info('已恢复历史记录到剪贴板');
} catch (error) {
console.error(`恢复失败: ${error}`);
}
}
private saveHistory() {
// 保存到preferences或database
}
private loadHistory() {
// 从preferences或database加载
}
build() {
Column() {
Text('剪贴板历史')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 10 })
List() {
ForEach(this.historyList, (item: ClipboardHistoryItem, index: number) => {
ListItem() {
Row() {
Column() {
Text(item.content)
.fontSize(14)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(new Date(item.timestamp).toLocaleString())
.fontSize(12)
.fontColor('#999999')
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
Button('恢复')
.fontSize(12)
.onClick(() => {
this.restoreFromHistory(item);
})
}
.width('100%')
.padding(12)
}
})
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
七、常见问题与解决方案
7.1 跨设备同步失败
问题:复制后在另一台设备无法粘贴。
可能原因及解决方案:
-
未登录同一账号
// 检查账号登录状态 // 确保两台设备登录同一华为账号 -
Wi-Fi或蓝牙未开启
// 提示用户检查网络设置 promptAction.showDialog({ title: '提示', message: '跨设备剪贴板需要开启Wi-Fi和蓝牙', buttons: [ { text: '确定', color: '#4CAF50' } ] }); -
数据已过期(超过2分钟)
// 添加时间戳检查 let pasteData = pasteboard.createData( pasteboard.MIMETYPE_TEXT_PLAIN, content ); // 添加自定义时间戳属性 pasteData.setProperty({ timestamp: Date.now().toString() }); // 粘贴时检查 let timestamp = parseInt(pasteData.getProperty('timestamp')); let elapsed = Date.now() - timestamp; if (elapsed > 2 * 60 * 1000) { console.warn('剪贴板数据已过期'); } -
设备锁屏或息屏
// 提示用户保持设备解锁和亮屏 console.info('请保持两台设备解锁且屏幕亮起');
7.2 权限被拒绝
问题:应用无法读取剪贴板,报权限错误。
解决方案:
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
async function requestPasteboardPermission() {
try {
let atManager = abilityAccessCtrl.createAtManager();
let permission: Permissions = 'ohos.permission.READ_PASTEBOARD';
// 检查权限状态
let grantStatus = await atManager.checkAccessToken(
context.applicationInfo.accessTokenId,
permission
);
if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
console.info('已有剪贴板权限');
return true;
}
// 请求权限
let result = await atManager.requestPermissionsFromUser(
context,
[permission]
);
if (result.authResults[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
console.info('权限授予成功');
return true;
} else {
console.warn('权限被拒绝');
return false;
}
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`权限请求失败: ${err.message}`);
return false;
}
}
7.3 图片复制失败
问题:大图片复制失败或粘贴时图片丢失。
解决方案:
-
压缩大图片
import { image } from '@kit.ImageKit'; async function compressImage(pixelMap: image.PixelMap): Promise<image.PixelMap> { // 获取原始尺寸 let imageInfo = await pixelMap.getImageInfo(); let width = imageInfo.size.width; let height = imageInfo.size.height; // 如果图片太大,进行压缩 let maxSize = 2000; if (width > maxSize || height > maxSize) { let scale = Math.min(maxSize / width, maxSize / height); let newWidth = Math.floor(width * scale); let newHeight = Math.floor(height * scale); // 缩放图片 await pixelMap.scale(newWidth / width, newHeight / height); } return pixelMap; } // 复制时压缩 async function copyLargeImage(originalPixelMap: image.PixelMap) { let compressedPixelMap = await compressImage(originalPixelMap); let pasteData = pasteboard.createData( pasteboard.MIMETYPE_PIXELMAP, compressedPixelMap ); let systemPasteboard = pasteboard.getSystemPasteboard(); await systemPasteboard.setData(pasteData); } -
使用URI代替PixelMap
// 对于本地图片,复制URI而非完整数据 async function copyImageUri(imageUri: string) { let pasteData = pasteboard.createData( pasteboard.MIMETYPE_TEXT_URI, imageUri ); let systemPasteboard = pasteboard.getSystemPasteboard(); await systemPasteboard.setData(pasteData); } // 粘贴时加载图片 async function pasteImageUri() { let systemPasteboard = pasteboard.getSystemPasteboard(); let pasteData = await systemPasteboard.getData(); if (pasteData.getPrimaryMimeType() === pasteboard.MIMETYPE_TEXT_URI) { let imageUri = pasteData.getPrimaryText(); // 从URI加载图片 let imageSource = image.createImageSource(imageUri); let pixelMap = await imageSource.createPixelMap(); return pixelMap; } }
7.4 HTML格式丢失
问题:复制富文本后粘贴时格式丢失。
解决方案:
/**
* 同时复制HTML和纯文本格式
* 确保在不支持HTML的应用中也能粘贴
*/
async function copyRichTextWithFallback(html: string, plainText: string) {
// 1. 创建HTML格式数据
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_HTML,
html
);
// 2. 添加纯文本作为备选
let plainTextRecord = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
plainText
);
pasteData.addRecord(plainTextRecord);
// 3. 写入剪贴板
let systemPasteboard = pasteboard.getSystemPasteboard();
await systemPasteboard.setData(pasteData);
}
// 粘贴时优先使用HTML,回退到纯文本
async function pasteRichTextWithFallback(): Promise<{ html: string, text: string }> {
let systemPasteboard = pasteboard.getSystemPasteboard();
let pasteData = await systemPasteboard.getData();
let html = '';
let text = '';
// 尝试获取HTML
try {
html = pasteData.getPrimaryHtml();
} catch (error) {
console.warn('无HTML格式');
}
// 回退到纯文本
if (!html) {
text = pasteData.getPrimaryText();
}
return { html, text };
}
7.5 剪贴板监听不生效
问题:注册了剪贴板变化监听,但回调未触发。
解决方案:
import { pasteboard } from '@kit.BasicServicesKit';
@Component
struct ClipboardMonitor {
private systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard();
private changeCallback = () => {
console.info('剪贴板内容已变化');
};
aboutToAppear() {
// ✅ 正确:使用箭头函数或bind保持this上下文
this.systemPasteboard.on('update', this.changeCallback);
}
aboutToDisappear() {
// ⚠️ 重要:必须取消监听,避免内存泄漏
this.systemPasteboard.off('update', this.changeCallback);
}
build() {
Column() {
Text('监听剪贴板变化')
}
}
}
// ❌ 错误示例:匿名函数无法取消监听
// systemPasteboard.on('update', () => {
// console.info('变化');
// });
// 无法取消:systemPasteboard.off('update', ???)
八、性能优化建议
8.1 数据大小控制
// ❌ 低效:复制大量数据
async function copyLargeData() {
let largeText = 'x'.repeat(10 * 1024 * 1024); // 10MB文本
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
largeText
);
await systemPasteboard.setData(pasteData);
}
// ✅ 高效:控制数据大小
async function copyOptimizedData() {
let text = 'some text';
// 检查数据大小
let size = new TextEncoder().encode(text).length;
let maxSize = 1 * 1024 * 1024; // 1MB限制
if (size > maxSize) {
// 截断或压缩数据
text = text.substring(0, maxSize);
console.warn('数据已截断以提升性能');
}
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
text
);
await systemPasteboard.setData(pasteData);
}
8.2 异步操作优化
// ❌ 低效:阻塞UI线程
function copyTextSync(text: string) {
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
text
);
// 同步等待
systemPasteboard.setData(pasteData).then(() => {
// 长时间阻塞
});
}
// ✅ 高效:使用异步非阻塞
async function copyTextAsync(text: string) {
try {
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
text
);
// 异步执行,不阻塞UI
await systemPasteboard.setData(pasteData);
// 操作完成后更新UI
} catch (error) {
console.error(`复制失败: ${error}`);
}
}
8.3 监听器管理
@Component
struct OptimizedClipboardComponent {
private systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard();
private updateTimer: number = 0;
// 使用防抖避免频繁处理
private handleClipboardChange = () => {
clearTimeout(this.updateTimer);
this.updateTimer = setTimeout(() => {
this.processClipboardData();
}, 300); // 300ms防抖
};
private async processClipboardData() {
try {
let pasteData = await this.systemPasteboard.getData();
// 处理数据
} catch (error) {
console.error(`处理失败: ${error}`);
}
}
aboutToAppear() {
this.systemPasteboard.on('update', this.handleClipboardChange);
}
aboutToDisappear() {
// 清理定时器
clearTimeout(this.updateTimer);
// 取消监听
this.systemPasteboard.off('update', this.handleClipboardChange);
}
build() {
Column() {}
}
}
8.4 内存管理
import { image } from '@kit.ImageKit';
async function handleImageClipboard() {
let systemPasteboard = pasteboard.getSystemPasteboard();
let pasteData = await systemPasteboard.getData();
let pixelMap = pasteData.getPrimaryImage();
if (pixelMap) {
// 使用图片
// ...
// ✅ 重要:使用完毕后释放PixelMap
await pixelMap.release();
console.info('PixelMap已释放');
}
}
// 批量处理时的内存管理
async function processMulipleImages(imageUris: string[]) {
for (let uri of imageUris) {
let imageSource = image.createImageSource(uri);
let pixelMap = await imageSource.createPixelMap();
// 处理图片
await processImage(pixelMap);
// 立即释放
await pixelMap.release();
// 给垃圾回收器一些时间
await new Promise(resolve => setTimeout(resolve, 10));
}
}
九、测试与调试
9.1 日志输出
import { hilog } from '@kit.PerformanceAnalysisKit';
import { pasteboard } from '@kit.BasicServicesKit';
const TAG = '[ClipboardTest]';
const DOMAIN = 0xFF00;
async function testClipboardWithLogs() {
let systemPasteboard = pasteboard.getSystemPasteboard();
// 复制阶段日志
hilog.info(DOMAIN, TAG, '=== 开始复制测试 ===');
let text = 'Test clipboard content';
hilog.info(DOMAIN, TAG, `复制内容: ${text}`);
hilog.info(DOMAIN, TAG, `内容大小: ${new TextEncoder().encode(text).length} 字节`);
let pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text);
let startTime = Date.now();
await systemPasteboard.setData(pasteData);
let copyTime = Date.now() - startTime;
hilog.info(DOMAIN, TAG, `复制耗时: ${copyTime}ms`);
hilog.info(DOMAIN, TAG, '=== 复制完成 ===');
// 粘贴阶段日志
hilog.info(DOMAIN, TAG, '=== 开始粘贴测试 ===');
let hasData = systemPasteboard.hasData();
hilog.info(DOMAIN, TAG, `剪贴板是否有数据: ${hasData}`);
startTime = Date.now();
let retrievedData = await systemPasteboard.getData();
let pasteTime = Date.now() - startTime;
hilog.info(DOMAIN, TAG, `粘贴耗时: ${pasteTime}ms`);
let mimeType = retrievedData.getPrimaryMimeType();
hilog.info(DOMAIN, TAG, `数据类型: ${mimeType}`);
let retrievedText = retrievedData.getPrimaryText();
hilog.info(DOMAIN, TAG, `粘贴内容: ${retrievedText}`);
hilog.info(DOMAIN, TAG, `内容匹配: ${text === retrievedText}`);
hilog.info(DOMAIN, TAG, '=== 粘贴完成 ===');
}
9.2 单元测试示例
import { describe, it, expect } from '@ohos/hypium';
import { pasteboard } from '@kit.BasicServicesKit';
export default function ClipboardTest() {
describe('剪贴板功能测试', () => {
it('测试文本复制粘贴', async () => {
let systemPasteboard = pasteboard.getSystemPasteboard();
let testText = '测试文本内容';
// 复制
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
testText
);
await systemPasteboard.setData(pasteData);
// 粘贴
let retrievedData = await systemPasteboard.getData();
let retrievedText = retrievedData.getPrimaryText();
// 断言
expect(retrievedText).assertEqual(testText);
});
it('测试空剪贴板', async () => {
let systemPasteboard = pasteboard.getSystemPasteboard();
systemPasteboard.clear();
let hasData = systemPasteboard.hasData();
expect(hasData).assertEqual(false);
});
it('测试多格式数据', async () => {
let systemPasteboard = pasteboard.getSystemPasteboard();
// 创建多格式数据
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_HTML,
'<p>HTML</p>'
);
let plainText = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
'Plain Text'
);
pasteData.addRecord(plainText);
await systemPasteboard.setData(pasteData);
// 验证
let retrievedData = await systemPasteboard.getData();
let recordCount = retrievedData.getRecordCount();
expect(recordCount).assertEqual(2);
});
});
}
9.3 测试清单
基础功能测试
- 文本复制粘贴测试
- 图片复制粘贴测试
- HTML富文本复制粘贴测试
- URI复制粘贴测试
- 清空剪贴板测试
- 检查剪贴板是否有数据
跨设备测试
- 同一账号双设备同步测试
- Wi-Fi环境同步测试
- 蓝牙环境同步测试
- 局域网同步测试
- 2分钟超时测试
- 设备锁屏场景测试
权限测试
- 前台访问剪贴板测试
- 后台访问剪贴板(需权限)测试
- 权限请求流程测试
- 权限拒绝处理测试
性能测试
- 大文本复制性能测试(>1MB)
- 大图片复制性能测试(>5MB)
- 多格式数据复制性能测试
- 频繁操作性能测试
- 内存占用测试
异常场景测试
- 网络断开时跨设备同步测试
- 数据格式错误处理测试
- 超大数据处理测试
- 快速连续复制粘贴测试
十、最佳实践总结
10.1 开发建议
-
合理使用权限
- 前台使用剪贴板无需申请权限
- 后台访问时才申请READ_PASTEBOARD权限
- 向用户说明权限用途
-
数据格式选择
- 优先使用标准MIME类型
- 提供多格式备选方案
- 大数据使用URI引用
-
性能优化
- 控制单次数据大小(建议<20MB)
- 使用异步API避免阻塞UI
- 及时释放PixelMap等大对象
-
用户体验
- 提供明确的复制成功反馈
- 粘贴前检查数据有效性
- 优雅处理跨设备同步失败
-
安全性
- 敏感数据设置ShareOption限制
- 验证粘贴数据的来源和格式
- 避免执行未验证的代码或脚本
10.2 常见使用场景最佳实践
文本编辑器
// 支持纯文本和富文本
async function copyFromEditor(plainText: string, html: string) {
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_HTML,
html
);
// 添加纯文本备选
if (plainText !== html) {
let plainTextData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
plainText
);
pasteData.addRecord(plainTextData);
}
let systemPasteboard = pasteboard.getSystemPasteboard();
await systemPasteboard.setData(pasteData);
}
图片编辑器
// 同时提供图片和URI
async function copyImageWithUri(pixelMap: image.PixelMap, uri: string) {
// 主数据:图片PixelMap
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_PIXELMAP,
pixelMap
);
// 备选:图片URI
let uriData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_URI,
uri
);
pasteData.addRecord(uriData);
let systemPasteboard = pasteboard.getSystemPasteboard();
await systemPasteboard.setData(pasteData);
}
浏览器
// 复制链接和标题
async function copyLink(url: string, title: string) {
// 主数据:URI
let pasteData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_URI,
url
);
// 备选1:HTML格式(带标题)
let html = `<a href="${url}">${title}</a>`;
let htmlData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_HTML,
html
);
pasteData.addRecord(htmlData);
// 备选2:纯文本(URL + 标题)
let plainText = `${title}\n${url}`;
let textData = pasteboard.createData(
pasteboard.MIMETYPE_TEXT_PLAIN,
plainText
);
pasteData.addRecord(textData);
let systemPasteboard = pasteboard.getSystemPasteboard();
await systemPasteboard.setData(pasteData);
}
十一、总结
跨设备剪贴板是HarmonyOS提供的强大分布式能力,通过系统级的自动同步机制,实现了设备间无缝的内容共享。
核心要点回顾:
-
使用条件
- 同一华为账号
- Wi-Fi + 蓝牙开启
- 设备解锁且亮屏
- 2分钟有效期
-
核心API
getSystemPasteboard():获取剪贴板实例createData():创建剪贴板数据setData():写入剪贴板getData():读取剪贴板
-
支持格式
- 纯文本(MIMETYPE_TEXT_PLAIN)
- HTML(MIMETYPE_TEXT_HTML)
- URI(MIMETYPE_TEXT_URI)
- 图片(MIMETYPE_PIXELMAP)
-
最佳实践
- 提供多格式备选方案
- 控制数据大小
- 使用异步API
- 及时释放资源
- 处理异常情况
-
安全性
- 申请必要权限
- 设置ShareOption
- 验证数据来源
- 保护敏感信息