跨設備剪貼板數據:實現應用間內容共享
概述
在 HarmonyOS 生態系統中,跨設備剪貼板功能讓用户能夠在一台設備上覆制內容,然後在同一賬號下的其他設備上粘貼使用。這項技術打破了設備邊界,為開發者提供了全新的內容共享體驗。
官方參考資料:
- HarmonyOS API 參考
- 剪貼板服務指南
- 跨設備剪貼板
重要提示:本文所有示例基於 HarmonyOS Next API 10+和 DevEco Studio 4.0+,請確保開發環境正確配置。
開發環境準備
必備條件
在開始編碼前,請確保滿足以下條件:
- 賬號體系:所有設備登錄同一華為賬號
權限配置
跨設備剪貼板需要以下權限:
// 在module.json5文件中添加權限
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
}
]
}
}
基礎剪貼板操作
單設備剪貼板
首先了解基本的剪貼板操作,這是跨設備功能的基礎:
import { pasteboard } from "@kit.ArkData";
// 創建PasteData對象
let pasteData = pasteboard.createData("text/plain");
// 設置文本內容
pasteData.addText("Hello HarmonyOS!");
// 寫入剪貼板
pasteboard
.setSystemPasteData(pasteData)
.then(() => {
console.info("Data written to clipboard successfully");
})
.catch((err: BusinessError) => {
console.error(`Failed to write data: ${err.code}, ${err.message}`);
});
讀取剪貼板數據
// 從剪貼板讀取數據
pasteboard
.getSystemPasteData()
.then((data: pasteboard.PasteData) => {
if (data) {
// 檢查並獲取文本內容
if (data.hasType(pasteboard.MIMETYPE_TEXT_PLAIN)) {
let text = data.getPrimaryText();
console.info(`Retrieved text: ${text}`);
}
}
})
.catch((err: BusinessError) => {
console.error(`Failed to read data: ${err.code}, ${err.message}`);
});
跨設備剪貼板實現
核心 API 概覽
跨設備剪貼板主要依賴以下 API:
|
API 名稱 |
功能描述 |
適用場景 |
|
|
創建剪貼板數據對象 |
初始化數據 |
|
|
添加文本內容 |
文本數據複製 |
|
|
添加 HTML 內容 |
富文本複製 |
|
|
添加圖片數據 |
圖像複製 |
|
|
設置系統剪貼板 |
寫入操作 |
|
|
獲取系統剪貼板 |
讀取操作 |
完整的跨設備複製實現
import { pasteboard } from "@kit.ArkData";
import { BusinessError } from "@kit.BasicServicesKit";
class CrossDeviceClipboard {
/**
* 複製文本到跨設備剪貼板
* @param text 要複製的文本內容
*/
async copyTextToCrossDevice(text: string): Promise<void> {
try {
// 1. 創建剪貼板數據
let pasteData = pasteboard.createData("text/plain");
// 2. 添加文本內容
pasteData.addText(text);
// 3. 設置屬性標籤(可選,用於標識應用)
pasteData.addProperty("sourceApp", "com.example.myapp");
// 4. 寫入系統剪貼板(自動同步到其他設備)
await pasteboard.setSystemPasteData(pasteData);
console.info("Text copied to cross-device clipboard");
} catch (err) {
let error = err as BusinessError;
console.error(`Copy failed: ${error.code}, ${error.message}`);
throw error;
}
}
/**
* 從跨設備剪貼板粘貼文本
*/
async pasteTextFromCrossDevice(): Promise<string | null> {
try {
// 1. 獲取剪貼板數據
let pasteData = await pasteboard.getSystemPasteData();
if (!pasteData) {
console.info("Clipboard is empty");
return null;
}
// 2. 檢查數據類型
if (pasteData.hasType(pasteboard.MIMETYPE_TEXT_PLAIN)) {
// 3. 獲取文本內容
let text = pasteData.getPrimaryText();
console.info(`Pasted text: ${text}`);
return text;
} else {
console.info("Clipboard does not contain text data");
return null;
}
} catch (err) {
let error = err as BusinessError;
console.error(`Paste failed: ${error.code}, ${error.message}`);
throw error;
}
}
}
富文本內容處理
除了純文本,跨設備剪貼板還支持富文本內容:
class RichTextClipboard {
/**
* 複製HTML內容到跨設備剪貼板
*/
async copyHtmlContent(): Promise<void> {
let htmlContent = `
<h1>HarmonyOS開發指南</h1>
<p>這是<strong>加粗文本</strong>和<em>斜體文本</em></p>
<ul>
<li>列表項1</li>
<li>列表項2</li>
</ul>
`;
let pasteData = pasteboard.createData("text/html");
pasteData.addHtml(htmlContent);
// 同時添加純文本版本作為備選
pasteData.addText(
"HarmonyOS開發指南 - 這是加粗文本和斜體文本 - 列表項1 - 列表項2"
);
await pasteboard.setSystemPasteData(pasteData);
console.info("HTML content copied to cross-device clipboard");
}
/**
* 粘貼HTML內容
*/
async pasteHtmlContent(): Promise<{ html?: string; text?: string }> {
let pasteData = await pasteboard.getSystemPasteData();
if (!pasteData) {
return {};
}
let result: { html?: string; text?: string } = {};
// 優先獲取HTML內容
if (pasteData.hasType(pasteboard.MIMETYPE_TEXT_HTML)) {
result.html = pasteData.getPrimaryHtml();
}
// 備選純文本
if (pasteData.hasType(pasteboard.MIMETYPE_TEXT_PLAIN)) {
result.text = pasteData.getPrimaryText();
}
return result;
}
}
高級功能實現
自定義數據類型
對於應用特定的數據結構,可以使用自定義 MIME 類型:
// 定義自定義MIME類型
const MIMETYPE_APP_DATA = "application/vnd.example.appdata+json";
class CustomDataClipboard {
/**
* 複製自定義數據到剪貼板
*/
async copyCustomData(userData: object): Promise<void> {
try {
let pasteData = pasteboard.createData(MIMETYPE_APP_DATA);
// 將對象轉換為JSON字符串
let jsonData = JSON.stringify(userData);
pasteData.addText(jsonData);
// 添加屬性標記數據來源
pasteData.addProperty("dataType", "userProfile");
pasteData.addProperty("timestamp", Date.now().toString());
await pasteboard.setSystemPasteData(pasteData);
console.info("Custom data copied to clipboard");
} catch (err) {
let error = err as BusinessError;
console.error(`Custom data copy failed: ${error.code}, ${error.message}`);
}
}
/**
* 粘貼並解析自定義數據
*/
async pasteCustomData(): Promise<object | null> {
try {
let pasteData = await pasteboard.getSystemPasteData();
if (!pasteData || !pasteData.hasType(MIMETYPE_APP_DATA)) {
return null;
}
let jsonString = pasteData.getPrimaryText();
let userData = JSON.parse(jsonString);
// 驗證數據來源(可選)
let dataType = pasteData.getProperty("dataType");
if (dataType === "userProfile") {
console.info("Valid custom data received");
return userData;
}
return null;
} catch (err) {
let error = err as BusinessError;
console.error(
`Custom data paste failed: ${error.code}, ${error.message}`
);
return null;
}
}
}
剪貼板變化監聽
實時監聽剪貼板內容變化:
class ClipboardMonitor {
private listener: pasteboard.SystemPasteboardChangedListener | null = null;
/**
* 開始監聽剪貼板變化
*/
startMonitoring(): void {
this.listener = (pasteData: pasteboard.PasteData) => {
console.info("Clipboard content changed");
if (pasteData) {
// 檢查新內容類型
if (pasteData.hasType(pasteboard.MIMETYPE_TEXT_PLAIN)) {
let text = pasteData.getPrimaryText();
console.info(`New text content: ${text}`);
}
if (pasteData.hasType(pasteboard.MIMETYPE_TEXT_HTML)) {
console.info("New HTML content available");
}
// 檢查數據來源設備
let sourceDevice = pasteData.getProperty("sourceDevice");
if (sourceDevice) {
console.info(`Data from device: ${sourceDevice}`);
}
}
};
// 註冊監聽器
pasteboard.on("systemPasteboardChanged", this.listener);
console.info("Clipboard monitoring started");
}
/**
* 停止監聽
*/
stopMonitoring(): void {
if (this.listener) {
pasteboard.off("systemPasteboardChanged", this.listener);
this.listener = null;
console.info("Clipboard monitoring stopped");
}
}
}
實戰案例:跨設備筆記共享
讓我們構建一個完整的跨設備筆記共享應用:
import { pasteboard } from "@kit.ArkData";
import { BusinessError } from "@kit.BasicServicesKit";
import { UIAbility, AbilityConstant, Want } from "@kit.AbilityKit";
class CrossDeviceNoteApp {
private readonly MIMETYPE_NOTE = "application/vnd.notepad.note+json";
/**
* 複製筆記到其他設備
*/
async copyNoteToDevices(note: Note): Promise<void> {
try {
let pasteData = pasteboard.createData(this.MIMETYPE_NOTE);
// 構建筆記數據
let noteData = {
title: note.title,
content: note.content,
createdAt: note.createdAt,
category: note.category,
};
pasteData.addText(JSON.stringify(noteData));
// 添加元數據
pasteData.addProperty("dataType", "crossDeviceNote");
pasteData.addProperty("version", "1.0");
pasteData.addProperty("sourceApp", "NotePadApp");
await pasteboard.setSystemPasteData(pasteData);
console.info(`Note "${note.title}" copied to cross-device clipboard`);
} catch (err) {
let error = err as BusinessError;
console.error(`Note copy failed: ${error.code}, ${error.message}`);
throw new Error("Failed to copy note to other devices");
}
}
/**
* 從剪貼板導入筆記
*/
async importNoteFromClipboard(): Promise<Note | null> {
try {
let pasteData = await pasteboard.getSystemPasteData();
if (!pasteData || !pasteData.hasType(this.MIMETYPE_NOTE)) {
return null;
}
// 驗證數據格式
let dataType = pasteData.getProperty("dataType");
if (dataType !== "crossDeviceNote") {
return null;
}
let jsonString = pasteData.getPrimaryText();
let noteData = JSON.parse(jsonString);
// 創建筆記對象
let note: Note = {
title: noteData.title || "Imported Note",
content: noteData.content || "",
createdAt: noteData.createdAt || Date.now(),
category: noteData.category || "General",
sourceDevice: pasteData.getProperty("sourceDevice") || "Unknown",
};
console.info(`Note imported from device: ${note.sourceDevice}`);
return note;
} catch (err) {
let error = err as BusinessError;
console.error(`Note import failed: ${error.code}, ${error.message}`);
return null;
}
}
/**
* 清空跨設備剪貼板
*/
async clearCrossDeviceClipboard(): Promise<void> {
try {
await pasteboard.clearSystemPasteData();
console.info("Cross-device clipboard cleared");
} catch (err) {
let error = err as BusinessError;
console.error(`Clear clipboard failed: ${error.code}, ${error.message}`);
}
}
}
// 筆記數據類型定義
interface Note {
title: string;
content: string;
createdAt: number;
category: string;
sourceDevice?: string;
}
性能優化與最佳實踐
數據大小限制
跨設備剪貼板有數據大小限制,需要合理管理:
class ClipboardOptimizer {
private readonly MAX_TEXT_SIZE = 1024 * 1024; // 1MB
/**
* 優化大文本數據的複製
*/
async copyLargeTextOptimized(text: string): Promise<void> {
if (text.length > this.MAX_TEXT_SIZE) {
console.warn("Text exceeds size limit, truncating...");
text = text.substring(0, this.MAX_TEXT_SIZE);
}
let pasteData = pasteboard.createData("text/plain");
pasteData.addText(text);
await pasteboard.setSystemPasteData(pasteData);
}
/**
* 分塊處理大內容
*/
async copyLargeContentInChunks(
largeContent: string,
chunkSize: number = 50000
): Promise<void> {
let chunks: string[] = [];
for (let i = 0; i < largeContent.length; i += chunkSize) {
chunks.push(largeContent.substring(i, i + chunkSize));
}
// 只複製第一個塊,其他塊通過其他方式傳輸
if (chunks.length > 0) {
let pasteData = pasteboard.createData("text/plain");
pasteData.addText(chunks[0]);
pasteData.addProperty("totalChunks", chunks.length.toString());
pasteData.addProperty("contentId", this.generateContentId());
await pasteboard.setSystemPasteData(pasteData);
}
}
private generateContentId(): string {
return `content_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
注意事項與常見問題
重要安全提示
⚠️ 安全警告:
- 剪貼板可能包含敏感信息,確保應用有明確的隱私政策
- 不要自動讀取剪貼板內容,必須用户主動觸發
- 定期清理不再需要的剪貼板數據
版本兼容性
|
HarmonyOS 版本 |
跨設備剪貼板支持 |
注意事項 |
|
API 10+ |
✅ 完整支持 |
本文示例基於此版本 |
|
API 9 |
⚠️ 部分支持 |
缺少某些高級特性 |
|
API 8 及以下 |
❌ 不支持 |
需要升級目標版本 |
常見錯誤處理
class ClipboardErrorHandler {
static handleCommonErrors(error: BusinessError): string {
const ERROR_CODES = {
201: "Permission denied - Check DISTRIBUTED_DATASYNC permission",
202: "Parameter error - Verify input parameters",
203: "Operation timeout - Network connectivity issue",
204: "Service unavailable - Cross-device service not running",
};
let errorCode = error.code.toString();
return (
ERROR_CODES[errorCode] || `Unknown error: ${error.code}, ${error.message}`
);
}
// 使用示例
static async safeClipboardOperation(
operation: () => Promise<void>
): Promise<boolean> {
try {
await operation();
return true;
} catch (err) {
let error = err as BusinessError;
let errorMessage = this.handleCommonErrors(error);
console.error(`Clipboard operation failed: ${errorMessage}`);
return false;
}
}
}
調試技巧
// 剪貼板調試工具
class ClipboardDebugger {
/**
* 調試剪貼板內容
* @param verbose 是否顯示詳細信息
*/
static async debugClipboard(verbose: boolean = false): Promise<void> {
try {
console.info('=== Clipboard Debug Info ===');
// 獲取剪貼板數據
let pasteData = await pasteboard.getSystemPasteData();
if (!pasteData) {
console.info('Clipboard is currently empty');
return;
}
// 檢查數據類型
console.info('Available data types:');
const MIME_TYPES = [
{type: pasteboard.MIMETYPE_TEXT_PLAIN, label: 'Plain Text'},
{type: pasteboard.MIMETYPE_TEXT_HTML, label: 'HTML'},
{type: pasteboard.MIMETYPE_IMAGE_PIXELMAP, label: 'Image'}
];
for (const {type, label} of MIME_TYPES) {
if (pasteData.hasType(type)) {
console.info(`- ${label}`);
// 顯示內容預覽
if (verbose) {
if (type === pasteboard.MIMETYPE_TEXT_PLAIN) {
let text = pasteData.getPrimaryText();
// 限制輸出長度
let preview = text.length > 100 ? text.substring(0, 100) + '...' : text;
console.info(` Content preview: ${preview}`);
} else if (type === pasteboard.MIMETYPE_TEXT_HTML) {
let html = pasteData.getPrimaryHtml();
let preview = html.length > 100 ? html.substring(0, 100) + '...' : html;
console.info(` HTML preview: ${preview}`);
}
}
}
}
// 顯示屬性信息
if (verbose) {
console.info('\nClipboard properties:');
// 獲取所有屬性鍵
const properties = ['sourceApp', 'dataType', 'timestamp', 'sourceDevice', 'version'];
for (const prop of properties) {
let value = pasteData.getProperty(prop);
if (value) {
console.info(`- ${prop}: ${value}`);
}
}
}
console.info('=== Debug Complete ===');
} catch (err) {
let error = err as BusinessError;
console.error(`Debug failed: ${error.code}, ${error.message}`);
}
}
/**
* 監控剪貼板變化並記錄日誌
*/
static enableMonitoringLog(): void {
console.info('Enabling clipboard monitoring logs');
const listener = (pasteData: pasteboard.PasteData) => {
console.log('CLIPBOARD EVENT:', new Date().toISOString());
if (pasteData) {
// 檢查主要內容類型
if (pasteData.hasType(pasteboard.MIMETYPE_TEXT_PLAIN)) {
let text = pasteData.getPrimaryText();
console.log(' New text content (length):', text?.length || 0);
}
// 檢查是否來自其他設備
let sourceDevice = pasteData.getProperty('sourceDevice');
if (sourceDevice) {
console.log(' Cross-device data from:', sourceDevice);
}
}
};
pasteboard.on('systemPasteboardChanged', listener);
console.info('Clipboard monitor attached');
// 返回清理函數
return () => {
pasteboard.off('systemPasteboardChanged', listener);
console.info('Clipboard monitor detached');
};
}
}
總結與展望
核心能力總結
跨設備剪貼板是HarmonyOS生態系統中的一項強大功能,為開發者提供了以下關鍵能力:
- 無縫內容共享:實現不同設備間的文本、富文本和自定義數據的即時共享
- 多類型數據支持:從簡單文本到複雜的自定義結構化數據,滿足各種應用場景需求
- 實時數據同步:基於分佈式數據同步技術,確保內容快速可靠地傳輸
- 事件監聽機制:通過監聽器模式,實時響應剪貼板內容變化
開發建議
在實現跨設備剪貼板功能時,建議遵循以下最佳實踐:
- 權限聲明:確保正確聲明
DISTRIBUTED_DATASYNC權限 - 錯誤處理:實現全面的錯誤處理,特別是對跨設備操作可能出現的網絡或同步問題
- 數據安全:敏感信息處理時,考慮加密和及時清理策略
- 用户體驗:提供明確的用户反饋,讓用户知道內容已成功複製到其他設備
- 性能優化:對大文件採用分塊處理,避免超過剪貼板大小限制
未來發展方向
隨着HarmonyOS生態的不斷髮展,跨設備剪貼板功能有望在以下方面進一步增強:
- 更多媒體類型支持:未來可能支持更多類型的媒體內容,如視頻片段、音頻文件等
- 智能內容識別:自動識別複製內容的類型,並提供相應的處理建議
- 設備篩選:允許用户選擇特定的目標設備進行內容共享
- 歷史記錄管理:提供剪貼板歷史記錄,方便用户回溯和再次使用
通過合理利用跨設備剪貼板功能,開發者可以打造更具沉浸感和連貫性的多設備應用體驗,讓用户在不同設備間無縫切換工作流程,大大提高使用效率和便利性。
最終建議:在實際應用中,始終保持對用户隱私的尊重,避免濫用剪貼板功能,並提供清晰的用户控制選項。