使用剪贴板进行延迟复制粘贴
=============

#### 场景介绍

[@ohos.pasteboard](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-pasteboard)主要提供管理系统剪贴板的能力，为系统复制、粘贴功能提供支持。

反复执行复制操作时，剪贴板缓存中会存储多余数据从而导致内存增加，为了优化内存以及后续支持指定数据类型粘贴，剪贴板提供了延迟复制粘贴的功能。

用户复制使用延迟复制技术的应用内的数据时，该条真实数据不会立即写入剪贴板服务的缓存中，而是等需要粘贴时，再从应用获取数据。

#### 约束限制

*   剪贴板内容包含剪贴板系统服务元数据和应用设置的数据，总大小上限默认为128MB，PC/2in1设备可通过系统配置修改上限，有效范围为128MB~2GB。
    
*   NDK接口仅支持Record级别的延迟复制粘贴。
    
*   当复制的数据量较小且准备数据所需时间不会影响用户体验时，不建议应用程序使用延迟复制功能，推荐将数据直接写入剪贴板。
    

#### 使用基于Record级别的延迟复制粘贴

本方案可以在粘贴前查询数据type信息，应用可以据此决定是否向剪贴板请求数据，因此建议使用本方案实现延迟复制功能。

从API version 21开始，应用退出不仅可以调用延迟复制接口[OH\_Pasteboard\_SetData](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-oh-pasteboard-h#oh_pasteboard_setdata)主动提交所有复制数据，还可以使用同步延迟数据接口[OH\_Pasteboard\_SyncDelayedDataAsync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-oh-pasteboard-h#oh_pasteboard_syncdelayeddataasync)通知剪贴板获取全量数据。

1.  当应用使用延迟复制功能复制时，仅将应用支持的数据类型写入剪贴板。应用应在退出时，重新调用[OH\_Pasteboard\_SetData](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-oh-pasteboard-h#oh_pasteboard_setdata)接口主动提交所有复制数据或调用[OH\_Pasteboard\_SyncDelayedDataAsync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-oh-pasteboard-h#oh_pasteboard_syncdelayeddataasync)接口通知剪贴板获取全量数据，等待数据同步完成再继续退出，否则可能导致其他应用粘贴获取不到数据。
    
2.  调用[OH\_Pasteboard\_SyncDelayedDataAsync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-oh-pasteboard-h#oh_pasteboard_syncdelayeddataasync)接口会延长退出过程，建议应用在复制数据时直接设置数据到剪贴板，而不是调用延迟复制接口[OH\_UdmfRecordProvider\_SetData](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-udmf-h#oh_udmfrecordprovider_setdata)和同步延迟数据接口[OH\_Pasteboard\_SyncDelayedDataAsync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-oh-pasteboard-h#oh_pasteboard_syncdelayeddataasync)。
    
3.  延迟复制场景应用异常退出时，无法触发应用退出延迟数据同步流程，会导致其他应用粘贴时获取不到数据。
    

#### \[h2\]接口说明

详细接口见[Pasteboard文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-pasteboard)和[UDMF接口文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-udmf)。

| 名称 | 说明 |
| :-- | :-- |
| OH\_UdmfRecordProvider\* OH\_UdmfRecordProvider\_Create() | 创建一个指向统一数据提供者的指针。 |
| int OH\_UdmfRecordProvider\_SetData(OH\_UdmfRecordProvider\* provider, void\* context, const OH\_UdmfRecordProvider\_GetData callback, const UdmfData\_Finalize finalize) | 设置统一数据提供者的回调函数。 |
| int OH\_UdmfRecord\_SetProvider(OH\_UdmfRecord\* pThis, const char\* const\* types, unsigned int count, OH\_UdmfRecordProvider\* provider) | 将统一数据提供者配置到OH\_UdmfRecord中。 |
| int OH\_Pasteboard\_SetData(OH\_Pasteboard\* pasteboard, OH\_UdmfData\* data) | 向剪贴板中写入数据。 |
| OH\_UdmfData \* OH\_Pasteboard\_GetData(OH\_Pasteboard\* pasteboard, int\* status) | 获取剪贴板中的数据。 |
| OH\_UdmfRecord\*\* OH\_UdmfData\_GetRecords(OH\_UdmfData\* pThis, unsigned int\* count) | 获取OH\_UdmfData中全部的数据记录。 |
| void OH\_Pasteboard\_SyncDelayedDataAsync(OH\_Pasteboard\* pasteboard, void (\*callback)(int errorCode)) | 通知剪贴板从应用同步所有延迟数据。 当应用使用延迟复制功能复制时，仅将应用支持的数据类型写入剪贴板。应用应在退出时，重新调用[OH\_Pasteboard\_SetData](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-oh-pasteboard-h#oh_pasteboard_setdata)接口主动提交所有复制数据或调用[OH\_Pasteboard\_SyncDelayedDataAsync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-oh-pasteboard-h#oh_pasteboard_syncdelayeddataasync)接口通知剪贴板获取全量数据，等待数据同步完成再继续退出，否则可能导致其他应用粘贴获取不到数据。 |

#### \[h2\]开发步骤

下面以纯文本类型和HTML类型数据为例，说明如何向剪贴板服务设置延迟复制数据。

为了代码可读性，代码中省略了各个步骤操作结果的校验，实际开发中需要确认每次调用的成功。

1.  引用头文件。
    
    #include <cstring>
    #include <hilog/log.h>
    #include <database/pasteboard/oh\_pasteboard.h>
    #include <database/udmf/udmf.h>
    #include <database/udmf/uds.h>
    #include <database/udmf/udmf\_meta.h>
    #include <accesstoken/ability\_access\_control.h>
    
2.  定义OH\_UdmfRecordProvider的数据提供函数和实例注销回调函数。
    
    // 1. 获取数据时触发的提供剪贴板数据的回调函数。
    void\* GetDataCallback(void\* context, const char\* type)
    {
        // 纯文本类型
        if (memcmp(type, UDMF\_META\_PLAIN\_TEXT, sizeof(UDMF\_META\_PLAIN\_TEXT) - 1) == 0) {
            // 创建纯文本类型的Uds对象。
            OH\_UdsPlainText\* udsText = OH\_UdsPlainText\_Create();
            // 设置纯文本内容。
            OH\_UdsPlainText\_SetContent(udsText, "hello world");
            return udsText;
        } else if (strcmp(type, UDMF\_META\_HTML) == 0) {
            // 创建HTML类型的Uds对象。
            OH\_UdsHtml\* udsHtml = OH\_UdsHtml\_Create();
            // 设置HTML内容。
            OH\_UdsHtml\_SetContent(udsHtml, "<div>hello world</div>");
            return udsHtml;
        }
        return nullptr;
    }
    // 2. OH\_UdmfRecordProvider销毁时触发的回调函数。
    void ProviderFinalizeCallback(void\* context)
    {
        OH\_LOG\_INFO(LOG\_APP, "OH\_UdmfRecordProvider finalize.");
    }
    
3.  定义OH\_Pasteboard\_SyncDelayedDataAsync的回调函数。
    
    // 3. 定义应用退出时调用延迟同步接口触发的回调函数。
    void SyncCallback(int errorCode)
    {
        // 继续退出
    }
    
4.  在剪贴板中准备延迟复制数据。此步骤完成后纯文本类型数据与HTML类型数据并未真正写入剪贴板服务，只有当数据使用者从OH\_UdmfRecord中获取OH\_UdsPlainText或OH\_UdsHtml时，才会触发上文定义的GetDataCallback数据提供函数，从中得到数据。
    
    OH\_Pasteboard\* CreateAndSetPasteboardData()
    {
        // 4. 创建OH\_UdmfRecord对象。
        OH\_UdmfRecord\* record = OH\_UdmfRecord\_Create();
        // 5. 创建OH\_UdmfRecordProvider对象，并设置用于提供延迟数据、析构的两个回调函数。
        OH\_UdmfRecordProvider\* provider = OH\_UdmfRecordProvider\_Create();
        OH\_UdmfRecordProvider\_SetData(provider, (void \*)record, GetDataCallback, ProviderFinalizeCallback);
        // 6. 将provider绑定到record，并设置支持的数据类型。
        #define TYPE\_COUNT 2
        const char\* types\[TYPE\_COUNT\] = {UDMF\_META\_PLAIN\_TEXT, UDMF\_META\_HTML};
        OH\_UdmfRecord\_SetProvider(record, types, TYPE\_COUNT, provider);
        // 7. 创建OH\_UdmfData对象，并向OH\_UdmfData中添加OH\_UdmfRecord。
        OH\_UdmfData\* setData = OH\_UdmfData\_Create();
        if (setData != nullptr) {
            OH\_UdmfData\_AddRecord(setData, record);
        }
        // 8. 创建OH\_Pasteboard对象，将数据写入剪贴板中。
        OH\_Pasteboard\* pasteboard = OH\_Pasteboard\_Create();
        if (setData != nullptr) {
            OH\_Pasteboard\_SetData(pasteboard, setData);
        }
        OH\_UdmfRecordProvider\_Destroy(provider);
        OH\_UdmfRecord\_Destroy(record);
        OH\_UdmfData\_Destroy(setData);
        return pasteboard;
    }
    
5.  从剪贴板获取延迟复制数据。
    
    void ProcessRecordType(OH\_UdmfRecord\* record, const char\* recordType)
    {
        OH\_UdsPlainText\* udsText = nullptr;
        OH\_UdsHtml\* udsHtml = nullptr;
        if (strcmp(recordType, UDMF\_META\_PLAIN\_TEXT) == 0) {
            // 创建纯文本类型的Uds对象
            udsText = OH\_UdsPlainText\_Create();
            if (udsText != nullptr) {
                // 从record中获取纯文本类型的Uds对象
                OH\_UdmfRecord\_GetPlainText(record, udsText);
                // 从Uds对象中获取内容
                const char\* content = OH\_UdsPlainText\_GetContent(udsText);
            } else if (strcmp(recordType, UDMF\_META\_HTML) == 0) {
                // 创建HTML类型的Uds对象
                udsHtml = OH\_UdsHtml\_Create();
                if (udsHtml != nullptr) {
                    // 从record中获取HTML类型的Uds对象
                    OH\_UdmfRecord\_GetHtml(record, udsHtml);
                    // 从Uds对象中获取内容
                    const char\* content = OH\_UdsHtml\_GetContent(udsHtml);
                }
            }
        }
    }
    void ProcessRecord(OH\_UdmfRecord\* record)
    {
        // 13. 查询OH\_UdmfRecord中的数据类型。
        unsigned typeCount = 0;
        char\*\* recordTypes = OH\_UdmfRecord\_GetTypes(record, &typeCount);
        // 14. 遍历数据类型。
        for (unsigned int typeIndex = 0; typeIndex < typeCount; ++typeIndex) {
            const char\* recordType = recordTypes\[typeIndex\];
            ProcessRecordType(record, recordType);
        }
    }
    
    static napi\_value NAPI\_Pasteboard\_time(napi\_env env, napi\_callback\_info info)
    {
        OH\_Pasteboard\* pasteboard = CreateAndSetPasteboardData();
        // 9. 记录当前的剪贴板数据变化次数。
        uint32\_t changeCount = OH\_Pasteboard\_GetChangeCount(pasteboard);
        // 10. 从剪贴板获取OH\_UdmfData。
        int status = -1;
        bool hasPermission = OH\_AT\_CheckSelfPermission("ohos.permission.READ\_PASTEBOARD");
        if (!hasPermission) {
            OH\_LOG\_ERROR(LOG\_APP, "No Permission READ\_PASTEBOARD");
        };
        OH\_UdmfData\* getData = OH\_Pasteboard\_GetData(pasteboard, &status);
        if (getData == nullptr) {
            // 处理错误情况，清理资源
            OH\_LOG\_ERROR(LOG\_APP, "Failed to get data from pasteboard, status: %d\\n", status);
        }
        // 11. 获取OH\_UdmfData中的所有OH\_UdmfRecord。
        unsigned int recordCount = 0;
        OH\_UdmfRecord\*\* getRecords = OH\_UdmfData\_GetRecords(getData, &recordCount);
        OH\_UdsPlainText\* udsText = nullptr;
        OH\_UdsHtml\* udsHtml = nullptr;
        // 12. 遍历OH\_UdmfRecord。
        for (unsigned int recordIndex = 0; recordIndex < recordCount; ++recordIndex) {
            OH\_UdmfRecord\* record = getRecords\[recordIndex\];
            ProcessRecord(record);
        }
    
6.  应用退出时，如果剪贴板内的数据没有变化，则通知剪贴板获取全量数据，等待回调完成再继续退出，否则可能导致其他应用粘贴获取不到数据。
    
    // 15. 查询剪贴板内的数据是否变化。
    uint32\_t newChangeCount = OH\_Pasteboard\_GetChangeCount(pasteboard);
    if (newChangeCount == changeCount) {
        // 16. 通知剪贴板获取全量数据。
        OH\_Pasteboard\_SyncDelayedDataAsync(pasteboard, SyncCallback);
        // 需要等待SyncCallback回调完成再继续退出
    } else {
        // 继续退出
        OH\_LOG\_INFO(LOG\_APP, "No newChangeCount in pasteboard.");
    }
    
7.  使用完毕后需要及时释放相关对象的内存。
    
        OH\_UdsPlainText\_Destroy(udsText);
        OH\_UdsHtml\_Destroy(udsHtml);
        OH\_UdmfData\_Destroy(getData);
        OH\_Pasteboard\_Destroy(pasteboard);
    }