HarmonyOS Sample 之 Pasteboard 分布式粘贴板

学向勤中得,萤窗万卷书。这篇文章主要讲述HarmonyOS Sample 之 Pasteboard 分布式粘贴板相关的知识,希望能为你提供帮助。
本文正在参与优质创作者激励
@toc
HarmonyOS Sample 之 Pasteboard 分布式粘贴板 1.介绍HarmonyOS提供系统剪贴板服务的操作接口,支持用户程序从系统剪贴板中读取、写入和查询剪贴板数据,以及添加、移除系统剪贴板数据变化的回调。
设备内:
用户通过系统剪贴板服务,可实现应用之间的简单数据传递。例如:在应用A中复制的数据,可以在应用B中粘贴,反之亦可。
设备间:
在分布式粘贴板场景中,粘贴的数据可以跨设备写入。例如,设备A上的应用程序使用系统粘贴板接口将从设备A复制的数据通过IDL接口存储到设备B的系统粘贴板中。如果数据允许,设备B上的应用程序可以读取并粘贴系统粘贴板中的复制数据。实现设备之间粘贴板的分布式协同。
基于以上理解,实现一个分布式粘贴板应用程序,应用程序分为客户端(copy)和服务端(paste)两部分,通过idl实现数据传递。
客户端负责数据采集,服务端负责数据的展示和应用,客户端和服务端可以安装在同一台设备中,也可以安装在不同的设备中,服务端也可以按照在多台设备中,服务端通过分布式数据库实现粘贴板数据的自动同步。
2.效果展示

HarmonyOS Sample 之 Pasteboard 分布式粘贴板

文章图片
HarmonyOS Sample 之 Pasteboard 分布式粘贴板

文章图片

3.搭建环境安装DevEco Studio,详情请参考DevEco Studio下载。
设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
下载源码后,使用DevEco Studio 打开项目,模拟器运行即可。
真机运行需要将config.json中的buddleName修改为自己的,如果没有请到AGC上进行配置,参见 使用模拟器进行调试 。
4.项目结构
HarmonyOS Sample 之 Pasteboard 分布式粘贴板

文章图片

HarmonyOS Sample 之 Pasteboard 分布式粘贴板

文章图片

5.代码讲解 5.1 系统粘贴板基础功能介绍
系统粘贴板对象介绍
1.SystemPasteboard //系统粘贴板对象,定义系统粘贴板操作,包括复制、粘贴和设置粘贴板内容更改的侦听器。
2.PasteData//表示粘贴板上的粘贴数据。
3.PasteData.DataProperty //该类定义了系统粘贴板上 PasteData 的属性,包括时间戳、MIME 类型和其他属性数据。
4.PasteData.Record//该类将单个粘贴的数据定义为 Record,它可以是纯文本、html 文本、URI 和意图。 PasteData 对象包含一个或多个记录。
==客户端(copy)CopyAbilitySlice.java==
获取系统粘贴板,监听粘贴板数据变化
/** * 获取系统粘贴板 * 监听粘贴板数据变化 */ private void initPasteboard() { HiLog.debug(LABEL, "initPasteboard"); //获取系统粘贴板对象 pasteboard = SystemPasteboard.getSystemPasteboard(this); //监听粘贴板数据变化 pasteboard.addPasteDataChangedListener(() -> { if (pasteboard.hasPasteData()) { sync_text = getPasteData(); HiLog.debug(LABEL, "%{public}s", "pasteStr:" + sync_text); } }); }

获取粘贴板内容
/** * 获取粘贴板记录 * * @return */ private String getPasteData() { HiLog.debug(LABEL, "getPasteData"); String result = ""; //粘贴板数据对象 PasteData pasteData = https://www.songbingjia.com/android/pasteboard.getPasteData(); if (pasteData == null) { return result; } PasteData.DataProperty dataProperty = pasteData.getProperty(); // boolean hasHtml = dataProperty.hasMimeType(PasteData.MIMETYPE_TEXT_HTML); boolean hasText = dataProperty.hasMimeType(PasteData.MIMETYPE_TEXT_PLAIN); //数据格式类型 if (hasHtml || hasText) { for (int i = 0; i < pasteData.getRecordCount(); i++) { //粘贴板数据记录 PasteData.Record record = pasteData.getRecordAt(i); //不同类型获取方式不同 String mimeType = record.getMimeType(); //HTML文本 if (mimeType.equals(PasteData.MIMETYPE_TEXT_HTML)) { result = record.getHtmlText(); //纯文本 } else if (mimeType.equals(PasteData.MIMETYPE_TEXT_PLAIN)) { result = record.getPlainText().toString(); // } else { HiLog.info(LABEL,"%{public}s", "getPasteData mimeType :" + mimeType); } } } return result; }

设置文本到粘贴板中
/** * 设置文本到粘贴板 * * @param component */ private void setTextToPaste(Component component) { HiLog.info(LABEL, "setTextToPaste"); if (pasteboard != null) { String text = syncText.getText(); if (text.isEmpty()) { showTips(this, "请填写内容"); return; } //把记录添加到粘贴板 PasteData pasteData=https://www.songbingjia.com/android/PasteData.creatPlainTextData(text); //设置文本到粘贴板 pasteboard.setPasteData(pasteData); showTips(this,"复制成功"); HiLog.info(LABEL, "setTextToPaste succeeded"); } }

清空粘贴板
/** * 清空粘贴板 * * @param component */ private void clearPasteboard(Component component) { if (pasteboard != null) { pasteboard.clear(); showTips(this, "Clear succeeded"); } }

5.2 分布式粘贴板应用构建思路介绍
HarmonyOS Sample 之 Pasteboard 分布式粘贴板

文章图片

选择远端连接设备
本实例是通过新增加一个DevicesSelectAbility来实现的。
private void showDevicesDialog() { Intent intent = new Intent(); //打开选择设备的Ability页面DevicesSelectAbility Operation operation = new Intent.OperationBuilder() .withDeviceId("") .withBundleName(getBundleName()) .withAbilityName(DevicesSelectAbility.class) .build(); intent.setOperation(operation); //携带一个设备选择请求标识,打开设备选择页面(DevicesSelectAbility) TODO startAbilityForResult(intent, Constants.PRESENT_SELECT_DEVICES_REQUEST_CODE); }/** * 打开设备选择Ability后,选择连接的设备执行setResult后触发 * * @param requestCode * @param resultCode * @param resultIntent */ @Override protected void onAbilityResult(int requestCode, int resultCode, Intent resultIntent) { HiLog.debug(LABEL, "onAbilityResult"); if (requestCode == Constants.PRESENT_SELECT_DEVICES_REQUEST_CODE & & resultIntent != null) { //获取用户选择的设备 String devicesId = resultIntent.getStringParam(Constants.PARAM_DEVICE_ID); //连接粘贴板服务端 connectService(devicesId); return; } }

连接粘贴板服务端ServiceAbility服务
idl文件放在ohos.samples.pasteboard.paste目录下,
Gradl窗口,执行compileDebugIdl 后,系统生成代理对象。
interface ohos.samples.pasteboard.paste.ISharePasteAgent { /* * 设置系统粘贴板 */ void setSystemPaste([in] String param); }

连接服务端ServiceAbility,如果组网中没有其他设备就连接本地的服务端。
连接成功后,初始化idl的SharePasteAgentProxy代理,用于下一步的同步数据。
//idl共享粘贴板代理 private SharePasteAgentProxy remoteAgentProxy; /** * 连接粘贴板服务中心 */ private void connectService(String deviceId) { HiLog.debug(LABEL, "%{public}s", "connectService"); if (!isConnect) { boolean isConnectRemote = deviceId != null; //三元表达式,判断连接本地还是远端Service Intent intent = isConnectRemote ? getRemoteServiceIntent(REMOTE_BUNDLE, REMOTE_SERVICE, deviceId) : getLocalServiceIntent(REMOTE_BUNDLE, REMOTE_SERVICE); HiLog.debug(LABEL, "%{public}s", "intent:" + intent); //连接 Service connectAbility(intent, new IAbilityConnection() { @Override public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) { //发个通知,Service 连接成功了 eventHandler.sendEvent(EVENT_ABILITY_CONNECT_DONE); //初始化代理 remoteAgentProxy = new SharePasteAgentProxy(iRemoteObject); HiLog.debug(LABEL, "%{public}s", "remoteAgentProxy:" + remoteAgentProxy); }@Override public void onAbilityDisconnectDone(ElementName elementName, int resultCode) { //发个通知,Service 断开连接了,主动断开不会执行,关闭服务端会执行 eventHandler.sendEvent(EVENT_ABILITY_DISCONNECT_DONE); } }); } } /** * 获取远端粘贴板服务中心 * * @param bundleName * @param serviceName * @return */ private Intent getRemoteServiceIntent(String bundleName, String serviceName, String deviceId) { HiLog.debug(LABEL, "%{public}s", "getRemoteServiceIntent"); Operation operation = new Intent.OperationBuilder() .withDeviceId(deviceId) .withBundleName(bundleName) .withAbilityName(serviceName) //重要 .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE) .build(); Intent intent = new Intent(); intent.setOperation(operation); return intent; }/** * 获取本地粘贴板服务中心 * * @param bundleName * @param serviceName * @return */ private Intent getLocalServiceIntent(String bundleName, String serviceName) { HiLog.debug(LABEL, "%{public}s", "getLocalServiceIntent"); Operation operation = new Intent.OperationBuilder().withDeviceId("") .withBundleName(bundleName) .withAbilityName(serviceName) .build(); Intent intent = new Intent(); intent.setOperation(operation); return intent; }

同步数据到服务端
/** * 同步粘贴板记录到粘贴板服务中心 * * @param component */ private void syncData(Component component) { HiLog.debug(LABEL, "sync_text:" + sync_text); if (!sync_text.isEmpty()) { if (isConnect & & remoteAgentProxy != null) { //调用服务端IPC方法 try { remoteAgentProxy.setSystemPaste(sync_text); //更换文本 syncText.setText(getRandomText()); sync_text = ""; showTips(this, "同步成功"); } catch (RemoteException remoteException) { remoteException.printStackTrace(); } } else { showTips(this, "正在连接设备"); } } else { showTips(this, "点击复制到粘贴板"); } }

随机生成粘贴文本
/** * 随机文本,模拟数据 * * @return */ public String getRandomText() { List< String> list = Arrays.asList( "快马加鞭未下鞍,离天三尺三", "我自横刀向天笑,去留肝胆两昆仑", "飞流直下三千尺,疑是银河落九天", "君子求诸己,小人求诸人", "吾日三省吾身:为人谋而不忠乎?与朋友交而不信乎?传不习乎?"); int random = new SecureRandom().nextInt(list.size()); return list.get(random); }

==服务端(paste)ServiceAbility.java==
设置粘贴板服务
idl文件放在ohos.samples.pasteboard.paste目录下,
Gradl窗口,执行compileDebugIdl 后,系统生成代理对象,idl提供了setSystemPaste接口供远端调用。
interface ohos.samples.pasteboard.paste.ISharePasteAgent { /* * 设置系统粘贴板 */ void setSystemPaste([in] String param); }

//idl的服务端实现, SharePasteAgentStub sharePasteAgentStub = new SharePasteAgentStub(DESCRIPTOR) { @Override public void setSystemPaste(String param) { HiLog.info(LABEL, "%{public}s", "param:" + param); //插入数据库 ItemChild itemChild = new ItemChild(); String currentTime = DateUtils.getCurrentDate("yyMMdd HH:mm:ss"); itemChild.setWriteTime(currentTime); itemChild.setWriteContent(param); itemChild.setIndex(String.valueOf(UUID.randomUUID())); //默认添加到未分类 itemChild.setTagName(Const.CATEGORY_TAG_UNCATEGOORIZED); //添加粘贴板记录到分布式数据库 kvManagerUtils.addItemChild(itemChild); } }; @Override protected IRemoteObject onConnect(Intent intent) { HiLog.info(LABEL, "%{public}s", "ServiceAbility onConnect"); return sharePasteAgentStub; }**初始化数据库** ```java //初始化数据库工具 kvManagerUtils = KvManagerUtils.getInstance(this); //初始化数据库管理对象 kvManagerUtils.initDbManager(eventHandler); //初始化数据库数据按钮 Image initDb = (Image) findComponentById(ResourceTable.Id_init_db); initDb.setClickedListener(component -> { //默认选中“未定义”标签 current_select_category_index = 0; //初始化数据库数据 kvManagerUtils.initDbData(); showTip("初始化完成"); });

初始化数据列表
/** * 从分布式数据库中查询数据 */ public void queryData() { HiLog.debug(LABEL, "queryData"); try { //加载选中类别下的数据列表 initItemChild(kvManagerUtils.queryDataByTag(CategoryData.tagList.get(current_select_category_index))); } catch (KvStoreException exception) { HiLog.info(LABEL, "the value must be String"); } }/** * 初始化选中标签的子项列表 * * @param itemChildList itemChildList, the bean of itemChild */ private void initItemChild(List< ItemChild> itemChildList) { HiLog.debug(LABEL, "initItemChild:" + itemChildList); if (itemChildList == null) { return; } //清空组件 itemChildLayout.removeAllComponents(); // Create itemChild for (ItemChild itemChild : itemChildList) { //获取子项类别所在的组件 Component childComponent = LayoutScatter.getInstance(this).parse(ResourceTable.Layout_paste_record_per, null, false); //写入时间 Text writeTime = (Text) childComponent.findComponentById(ResourceTable.Id_writeTime); writeTime.setText(itemChild.getWriteTime()); //粘贴板内容 Text writeContent = (Text) childComponent.findComponentById(ResourceTable.Id_writeContent); writeContent.setText(itemChild.getWriteContent()); //复制按钮 Text copy = (Text) childComponent.findComponentById(ResourceTable.Id_itemChildPerCopy); //复制按钮的监听事件 copy.setClickedListener(component -> { //复制内容到粘贴板 pasteboard.setPasteData(PasteData.creatPlainTextData(itemChild.getWriteContent())); showTip("已复制到粘贴板"); }); //收藏按钮 Text favorite = (Text) childComponent.findComponentById(ResourceTable.Id_itemChildPerFavorite); //收藏按钮的监听事件 favorite.setClickedListener(component -> { //修改标签微已收藏 itemChild.setTagName(Const.CATEGORY_TAG_FAVORITED); //保存数据 kvManagerUtils.addItemChild(itemChild); showTip("已加入到收藏中"); }); /**************just for test********************/ //复选框 Checkbox noteId = (Checkbox) childComponent.findComponentById(ResourceTable.Id_noteId); //子项列表的点击事件 childComponent.setClickedListener(component -> { if (noteId.getVisibility() == Component.VISIBLE) { noteId.setChecked(!noteId.isChecked()); } }); //子项列表的长按事件,长按显示复选框 childComponent.setLongClickedListener(component -> { //checkbox显示 noteId.setVisibility(Component.VISIBLE); //设置复选框样式,以及其他文本组件的缩进 Element element = ElementScatter.getInstance(getContext()).parse(ResourceTable.Graphic_check_box_checked); noteId.setBackground(element); noteId.setChecked(true); writeTime.setMarginLeft(80); writeContent.setMarginLeft(80); }); //复选框的状态变化监听事件,state表示是否被选中 noteId.setCheckedStateChangedListener((component, state) -> { // 状态改变的逻辑 Element element; if (state) { //设置选中的样式 element = ElementScatter.getInstance(getContext()) .parse(ResourceTable.Graphic_check_box_checked); } else { //设置未选中的样式 element = ElementScatter.getInstance(getContext()) .parse(ResourceTable.Graphic_check_box_uncheck); } noteId.setBackground(element); }); /**************just for test********************///添加子项列表组件到布局 itemChildLayout.addComponent(childComponent); } }

标签分类显示
//初始化列表列表的点击的监听事件 categoryList.setItemClickedListener( (listContainer, component, index, l1) -> { //点的就是当前类别 if (categoryListProvider.getSelectIndex() == index) { return; } //切换类别索引 categoryListProvider.setSelectIndex(index); //设置选中的标签索引 current_select_category_index = index; //获取当前选中的标签名称 String tagName = CategoryData.tagList.get(index); //从数据库中查询标签子项列表 initItemChild(kvManagerUtils.queryDataByTagAndKewWord(searchTextField.getText(), tagName)); //通知数据更新 categoryListProvider.notifyDataChanged(); //滚动条到最顶部 itemListScroll.fluentScrollYTo(0); });

搜索粘贴记录
//搜索key监听事件 searchTextField.setKeyEventListener( (component, keyEvent) -> { if (keyEvent.isKeyDown() & & keyEvent.getKeyCode() == KeyEvent.KEY_ENTER) { //获取当前选中的标签名称 String tagName = CategoryData.tagList.get(current_select_category_index); List< ItemChild> itemChildList = kvManagerUtils.queryDataByTagAndKewWord(searchTextField.getText(), tagName); //从数据库中查询标签子项列表 initItemChild(itemChildList); //通知数据更新 categoryListProvider.notifyDataChanged(); //滚动条到最顶部 itemListScroll.fluentScrollYTo(0); } return false; });

==分布式数据库工具KvManagerUtils.java==
数据变化通知
提供了分布式数据库管理工具KvManagerUtils.java,数据库操作都集中在这里了。
为了在数据库数据发生变化时能及时更新页面显示,页面在初始化数据库时,传递eventHandler对象,这样在数据库变化是可以通知到页面。
/** * 订阅数据库更改通知 * @param singleKvStore Data operation */ private void subscribeDb(SingleKvStore singleKvStore) { HiLog.info(LABEL, "subscribeDb"); //数据库观察者客户端 KvStoreObserver kvStoreObserverClient = new KvStoreObserverClient(); //订阅远程数据更改 singleKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_REMOTE, kvStoreObserverClient); }/** * 自定义分布式数据库观察者客户端 * 数据发生变化时触发对应函数 * Receive database messages */ private class KvStoreObserverClient implements KvStoreObserver { @Override public void onChange(ChangeNotification notification) { HiLog.error(LABEL, "onChange"); eventHandler.sendEvent(Const.DB_CHANGE_MESS); } }

数据自动同步
默认开启自动同步
/** * Initializing Database Management * 初始化数据库管理员 */ public void initDbManager(EventHandler eventHandler) { this.eventHandler = eventHandler; HiLog.info(LABEL, "initDbManager"); if (singleKvStore == null || kvManager == null) { HiLog.info(LABEL, "initDbData"); //创建数据库管理员 kvManager = createManager(); //创建数据库 singleKvStore = createDb(kvManager); subscribeDb(singleKvStore); } }/** * Create a distributed database manager instance * 创建数据库管理员 * * @return database manager */ private KvManager createManager() { HiLog.info(LABEL, "createManager"); KvManager manager = null; try { // KvManagerConfig config = new KvManagerConfig(context); manager = KvManagerFactory.getInstance().createKvManager(config); } catch (KvStoreException exception) { HiLog.error(LABEL, "some exception happen"); } return manager; }/** * Creating a Single-Version Distributed Database * 创建数据库 * * @param kvManager Database management * @return SingleKvStore */ private SingleKvStore createDb(KvManager kvManager) { HiLog.info(LABEL, "createDb"); SingleKvStore kvStore = null; try { Options options = new Options(); //单版本数据库,不加密,没有可用的 KvStore 数据库就创建 //单版本分布式数据库,默认开启组网设备间自动同步功能, //如果应用对性能比较敏感建议设置关闭自动同步功能setAutoSync(false),主动调用sync接口同步。 options.setCreateIfMissing(true) .setEncrypt(false) .setKvStoreType(KvStoreType.SINGLE_VERSION); //创建数据库 kvStore = kvManager.getKvStore(options, STORE_ID); } catch (KvStoreException exception) { HiLog.error(LABEL, "some exception happen"); } return kvStore; }

权限config.json
"reqPermissions": [ { "name": "ohos.permission.DISTRIBUTED_DATASYNC", "reason": "同步粘贴板数据", "usedScene": { "when": "inuse", "ability": [ "ohos.samples.pasteboard.paste.MainAbility", "ohos.samples.pasteboard.paste.ServiceAbility" ] } }, { "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO" }, { "name": "ohos.permission.KEEP_BACKGROUND_RUNNING" } ]

6.思考总结
1.粘贴板板传递数据可能会存在安全问题,需要注意,要根据具体场景来使用。
设备内每次传输的粘贴数据大小不能超过 800 KB。每次设备间传输的数据不能超过64KB,且数据必须为文本格式。
2.idl的使用,在上述案例中,客户端(copy) 和 服务端(paste) 项目idl下内容完全一致即可。
HarmonyOS Sample 之 Pasteboard 分布式粘贴板

文章图片
HarmonyOS Sample 之 Pasteboard 分布式粘贴板

文章图片
7.完整代码https://harmonyos.51cto.com/resource/1489

想了解更多关于鸿蒙的内容,请访问:
51CTO和华为官方战略合作共建的鸿蒙技术社区
https://harmonyos.51cto.com/#bkwz
::: hljs-center
HarmonyOS Sample 之 Pasteboard 分布式粘贴板

文章图片

【HarmonyOS Sample 之 Pasteboard 分布式粘贴板】:::

    推荐阅读