HarmonyOS - 实现多设备协同开发实战教程~

前言

现在随着个人设备越来越多,越来越需要多个设备之间相互感知和连接,设备和设备之间可以相互联动,形成互联互通的场景,而搭载HarmonyOS的设备恰好可以满足这一点 。下面通过开发一个HarmonyOS的多端分布式表白应用来实现设备之间的相互联动。

项目介绍

H5页面可以实现一些比较特殊的页面效果,所以选择在应用中集成H5页面。应用可以将页面直接投放到附近其他HarmonyOS设备上,实现多端设备分布式显示,同时应用可以跨端控制,更新应用页面,形成多设备协同的效果。

下面是效果展示:

多设备协同原理

HarmonyOS 给应用开发者提供了一套在多个设备不同应用之间进行任务流转的API接口,实现设备协同需要关注 流转任务管理服务分布式任务调度

流转任务管理服务:在流转发起端,接受用户应用程序注册,提供流转入口、状态显示、退出流转等管理能力。

分布式任务调度:提供远程服务启动、远程服务连接、远程迁移等能力,并通过不同能力组合,支撑用户应用程序完成跨端迁移或多端协同的业务体验。

分布式安全:提供E2E的加密通道,为用户应用程序提供安全的跨端传输机制,保证“正确的人,通过正确的设备,正确地使用数据”。

分布式软总线:使用基于手机、平板、智能穿戴、智慧屏等分布式设备的统一通信基座,为设备之间的互联互通提供统一的分布式通信能力。

任务流转流程:设备A和设备B登录相同的华为账号,在同一网络下。设备A向设备B发起协同,应用程序需先向系统的流转任务管理服务 注册回调,获取到设备B的 DeviceId 等设备信息,设备A初始化分布式任务调度,通过设备DeviceId指定设备发起协同,设备B接收到协同请求,初始化分布式任务调度,启动对应的应用程序,把流转的状态上报给流转任务管理服务,流转任务管理服务返回流转结果,完成一次设备A到设备B的任务流转。

理解分布式软总线

分布式:指的是一种运行方式,简单来说就是任务可以在一个设备上运行,也可以多个设备连接起来一起运行,在多个设备中没有一个绝对的中心。

总线:简单了解一下“总线”的概念,在计算机系统中,各个部件之间传送信息的通道叫总线,外部设备通过相应的接口与总线相连接,组成了整个计算机系统。

分布式软总线:所以不难理解分布式软总线其实就是实现多个设备之间的连接,传递消息,实现任务多端运行的一种技术。在HarmonyOS中,底层已经帮我们实现了设备之间的组网、发现和连接,所以并不需要关心设备怎么通信,只需要调用底层封装好的接口,实现多设备协同就可以了。

实现步骤

实现分布式多设备协同,需要实现跨端启动应用、后台PA服务、分布式数据同步的功能,具体实现流程如下

一、跨设备启动应用

多设备协同实现的前提,需要在多端安装相同的应用,而在现实使用环境中,在多个设备中安装一个相同的应用还是一个比较麻烦的事。而HarmonyOS的原子化服务则不需要用户手动安装,由系统程序框架后台安装后即可使用,在HarmonyOS的服务中心以服务卡片的形式展示。

应用由原子化服务平台(Huawei Ability Gallery)管理和分发,只需要上传到原子化服务平台(Huawei Ability Gallery)即可,在多设备协同中,当设备A的应用向设备B的应用发起多端协同,如果设备B上没有安装对应服务,HarmonyOS会自动下载相关原子化服务,和A端的应用一起进行多端协同。

跨设备启动应用,也就是设备A上的应用可以拉起设备B上的应用。因为原子化服务应用免安装的特性,所以不用关心应用在多设备上的安装,只需实现跨设备启动应用即可。

1. 创建原子化服务

以原子化服务的形式创建项目, 原子化服务的特点是支持免安装,没有应用图标,只在 HarmanoyOS 服务中心以卡片的形式展现,支持跨端迁移和多端协同。
在新建项目时选择Atomic Service,创建一个原子化服务,同时打开Show in service center 开关,自动创建服务卡片,去掉TV的勾选状态,目前服务卡片不支持TV设备。

2. 创建HarmonyOS IDL接口

选择项目的module目录,点击鼠标右键,选择New>Idl File,如下图:

IDL是HarmonyOS的接口描述语言,可以实现IPC跨进程间通信,接口的提供方是服务端,客户端绑定应用的服务来进行交互。

在IDL中定义服务端接口,代码如下。

登录后复制

**interface** com.wealchen.multipoint.IMultiPointIdl {*//启动服务*void serviceStart([in] int code);*//发送消息*void sendMsg([in] int code,[in]int extras);*//停止服务*int serviceStop();
}

创建的IDL接口通过编译会在build > generated > source > Idl> 目录 debugrelease 下自动生成对应的接口类、桩类和代理类,如下图。

3. 实现后台接口服务

在HarmonyOS中,客户端绑定服务端后,获取到序列化的IRemoteObject对象,通过IRemoteObject对象实现客户端与服务端的通信,IRemoteObject在编译生成的桩类和代理类中已经完成了对象的创建和消息发送的实现,具体可查看上图中自动生成的代码。

创建一个Service后台服务MultiPointService,提供给客户端连接,并实现IDL中定义的接口,在接口实现中启动FA页面,接收客户端发送的消息,具体代码如下:

登录后复制

**public** **class** MultiPointService **extends** Ability {*// DESCRIPTOR 保持与 MultiPointIdlStub 和 MultiPointIdlProxy中的一致***private** static final String DESCRIPTOR = "com.wealchen.multipoint.IMultiPointIdl";**private** static final String TAG = MultiPointService.class.getName();@Override**protected** void onStart(Intent intent) {**super**.onStart(intent);}@Override**protected** IRemoteObject onConnect(Intent intent) {**return** **new** MultiPointRemoteObject(DESCRIPTOR);}**private** **class** MultiPointRemoteObject **extends** MultiPointIdlStub {**public** MultiPointRemoteObject(String descriptor) {**super**(descriptor);}@Override**public** void serviceStart(int _code) **throws** RemoteException {*//启动WebViewAbility页面*Intent intent = **new** Intent();Operation operation = **new** Intent.OperationBuilder().withBundleName(getBundleName()).withAbilityName(WebViewAbility.class.getName()).build();intent.setOperation(operation);intent.setParam(Constants.INTENT_STAR_PARAM, _code);startAbility(intent);LogUtil.debug(TAG, "serviceStart : start WebViewAbility " + _code);}@Override**public** void sendMsg(int _code, int _extras) **throws** RemoteException {LogUtil.debug(TAG, "sendMsg  code: " + _code + " extras: " + _extras);}@Override**public** int serviceStop() **throws** RemoteException {**return** 0;}}
}
4. 实现跨端启动应用

启动跨端应用需指定设备的DeviceId,通过设备管理器DeviceManager,可获取到当前同一网络下所有不是待机状态的设备,拿到DeviceId,DeviceName等设备信息,具体代码如下:

登录后复制

 *//获取同一网络下的设备*List<DeviceInfo> deviceInfos = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);**if** (deviceInfos == **null** && deviceInfos.size() == 0) {**return**;}**for** (int i = 0; i < deviceInfos.size(); i++) {String devId = deviceInfos.get(i).getDeviceId();String devName = deviceInfos.get(i).getDeviceName();}

通过DeviceId可以与指定设备的后台服务MultiPointService连接,实现长期交互,系统提供了connectAbility方法,实现跨设备PA连接与断开连接的能力,通过AbilitySlice的connectAbility接口跨设备连接到后台服务MultiPointService,发送消息到指定设备,设备在接收到消息之后可以执行相应的任务,从而实现跨设备应用任务的调度,连接服务实现代码如下:

登录后复制

 *//启动远端设备的FA***private** void startAbilityFa(String devicesId, String event, int localExtras) {Intent intent = **new** Intent();Operation operation =**new** Intent.OperationBuilder().withDeviceId(devicesId).withBundleName(getBundleName()).withAbilityName(MultiPointService.class.getName()).withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE).build();intent.setOperation(operation);boolean connectFlag = connectAbility(intent, **new** IAbilityConnection() {@Override**public** void onAbilityConnectDone(ElementName elementName, IRemoteObject remoteObject, int extra) {LogUtil.debug(TAG, "onAbilityConnectDone extra:" + extra);multiPointIdl = MultiPointIdlStub.asInterface(remoteObject);**try** {**if** (multiPointIdl != **null**) {**switch** (event) {**case** "serviceStart":multiPointIdl.serviceStart(0);**break**;**case** "sendMsg":multiPointIdl.sendMsg(1001, localExtras);**break**;}}} **catch** (RemoteException e) {LogUtil.error(TAG, "connect successful,but have remote exception");}}@Override**public** void onAbilityDisconnectDone(ElementName elementName, int extra) {LogUtil.debug(TAG, "extra " + extra + " elementName " + elementName.getAbilityName());disconnectAbility(**this**);}});**if** (connectFlag) {Toast.toast(**this**, "transmit successful!", TOAST_DURATION);} **else** {Toast.toast(**this**, "transmit failed!Please try again later.", TOAST_DURATION);}}

二、多端设备协同

多设备协同可以实现对跨端设备的控制,使用HarmonyOS的分布式数据服务,不同设备之间的数据可以实时更新并显示在界面上。

1. 分布式数据服务介绍

分布式数据服务是HarmonyOS为应用程序提供不同设备间同步数据的能力,通过使用分布式数据接口,应用程序将数据保存到分布式数据库中,不同设备之间可以通过分布式数据服务互相访问,支持数据在相同帐号的多端设备之间相互同步。

HarmonyOS的分布式数据库是一种NoSQL类型数据库,其数据以键值对key-value的形式进行组织、索引和存储,分布式数据服务包含五部分:

服务接口:提供专门的数据库创建、数据访问、数据订阅等接口给应用程序调用,接口支持KV数据模型,支持常用的数据类型,同时确保接口的兼容性、易用性和可发布性。

服务组件:负责服务内元数据管理、权限管理、加密管理、备份和恢复管理以及多用户管理等、同时负责初始化底层分布式DB的存储组件、同步组件和通信适配层。

存储组件:负责数据的访问、数据的缩减、事务、快照、数据库加密,以及数据合并和冲突解决等特性。

同步组件:连结了存储组件与通信组件,其目标是保持在线设备间的数据库数据一致性,包括将本地产生的未同步数据同步给其他设备,接收来自其他设备发送过来的数据,并合并到本地设备中。

通信适配层:负责调用底层公共通信层的接口完成通信管道的创建、连接,接收设备上下线消息,维护已连接和断开设备列表的元数据,同时将设备上下线信息发送给上层同步组件,同步组件维护连接的设备列表,同步数据时根据该列表,调用通信适配层的接口将数据封装并发送给连接的设备。

2. 创建分布式数据库

创建分布式数据库管理器,设置是否开启加密,自动同步功能,定义数据存储和查询方法,使用手动同步数据的方法实现数据的同步,详细代码代码如下:

登录后复制

**public** **class** MyKvStoreManager {**private** static final String TAG = MyKvStoreManager.class.getName();**private** static MyKvStoreManager instance = **null**;**private** static SingleKvStore singleKvStore = **null**;**private** static KvManager kvManager;**private** MyKvStoreManager(Context context) {createKVManager(context);}*/**** 创建分布式数据管理器*** \*/***public** static void createKVManager(Context context) {KvManagerConfig config = **new** KvManagerConfig(context);kvManager = KvManagerFactory.getInstance().createKvManager(config);**try** {Options options = **new** Options();options.setCreateIfMissing(**true**).setAutoSync(**false**).setEncrypt(**false**).setKvStoreType(KvStoreType.SINGLE_VERSION);String storeId = "remoteData";singleKvStore = kvManager.getKvStore(options, storeId);} **catch** (KvStoreException e) {LogUtil.error(TAG, "getKvStore:" + e.getKvStoreErrorCode());}}*/**** 存储数据*** \*/***public** static void putKvStore(String key, String value) {**if** (singleKvStore == **null**) {**return**;}**try** {singleKvStore.putString(key, value);} **catch** (KvStoreException e) {LogUtil.debug(TAG, "putString:" + e.getKvStoreErrorCode());}}*/**** 查询数据*** \*/***public** static String getKvStore(String key) {String valueString = "";**try** {valueString = singleKvStore.getString(key);} **catch** (KvStoreException e) {LogUtil.debug(TAG, "getString:" + e.getKvStoreErrorCode());}**return** valueString;}*/**** 手动同步数据*** \*/***public** static void syncData() {List<DeviceInfo> deviceInfoList = kvManager.getConnectedDevicesInfo(DeviceFilterStrategy.NO_FILTER);List<String> deviceIdList = **new** ArrayList<>();**for** (DeviceInfo deviceInfo : deviceInfoList) {deviceIdList.add(deviceInfo.getId());LogUtil.debug(TAG,"syncData getId: "+deviceInfo.getId());}**if** (deviceIdList.isEmpty()){**return**;}singleKvStore.sync(deviceIdList, SyncMode.PUSH_PULL);}*/**** 订阅数据同步结果*** \*/***public** static void dataChangeObserver(KvStoreObserver storeObserver) {singleKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_REMOTE, storeObserver);}
}
3. 订阅分布式数据变化

订阅分布式数据变化客户端应用需要实现KvStoreObserver接口,KvStoreObserver接口回调中返回数据更新的结果,根据返回结果应用执行相应的任务,或者更新界面,从而实现多端设备的同步,注意:回调方法中不允许更新UI组件等阻塞动作。

更新UI可以通过getMainTaskDispatcher().asyncDispatch()切换到UI主线程更新UI的,详细代码如下:

登录后复制

MyKvStoreManager.dataChangeObserver(**new** KvStoreObserver() {@Override**public** void onChange(ChangeNotification changeNotification) {LogUtil.debug(TAG, "dataChangeObserver");List<Entry> updateEntries = changeNotification.getUpdateEntries();**for** (int i = 0; i < updateEntries.size(); i++) {String key = updateEntries.get(i).getKey();String value = updateEntries.get(i).getValue().getString();LogUtil.debug(TAG, "dataChangeObserver key:" + key + " value:" + value);getMainTaskDispatcher().asyncDispatch(**new** Runnable() {@Override**public** void run() {**switch** (key) {**case** JS_ADD_NAME:*//添加名字*setJsAddName(value);**break**;**case** JS_DEL_NAME:*//删除名字*setJsDelName(value);**break**;**default**:updateListView(key, value);**break**;}}});}}
});

三、WebView与JavaScript的交互

1. 在WebView调用H5页面的JavaScript方法

更新H5页面时,WebView需要调用JavaScript,通过WebView.executeJs()传入H5页面中对应的方法名称,实现应用调用页面内的JavaScript方法,实现的代码如下:

登录后复制

 **private** void setJsAddName(String addName) {LogUtil.debug(TAG, "addName:" + addName);String add_js = String.format("javascript:addName('%s')", addName);webView.executeJs(add_js, **new** AsyncCallback<String>() {@Override**public** void onReceive(String msg) {*// 在此确认返回结果*LogUtil.debug(TAG, "executeJs onReceive:" + msg);}});}H5页面定义的方法:**function** addName(name){**var** tpl = document.getElementById('name').innerText**var** str = name +'\n'document.getElementById('name').innerText = tpl.concat(str)**return** name
}
2. H5页面调用应用中的方法

WebView组件通过注入回调对象到页面内容,实现在H5页面中调用应用中的方法。

H5页面调用应用中的方法:

登录后复制

**function** callToApp() {**if** (window.showDeviceList && window.showDeviceList.call) {**var** result = showDeviceList.call("showDeviceList");}
}应用中定义的方法:webView.addJsCallback("showDeviceList", **new** JsCallback() {@Override**public** String onCallback(String s) {getMainTaskDispatcher().asyncDispatch(**new** Runnable() {@Override**public** void run() {**switch** (s) {**case** "showDeviceList":**if** (!bottomDialog.isShowing()) {bottomDialog.show();}**break**;}}});**return** method;}
});

总结

应用实现了包括原子化服务、任务流转、跨端启动应用、分布式数据服务、多端设备协同、WebView组件与JavaScript交互的功能,其中包含了HarmonyOS系统才具有的功能。以上只是简单介绍了HarmonyOS特有功能的实现,当然HarmonyOS的特性不止这些,更多的功能和实现还需要开发者去探索。

为了能让大家更好的学习鸿蒙 (OpenHarmony) 开发技术,这边特意整理了《鸿蒙 (OpenHarmony)开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙 (OpenHarmony)开发学习手册》

入门必看:https://qr21.cn/FV7h05

  1. 应用开发导读(ArkTS)
  2. ……

HarmonyOS 概念:https://qr21.cn/FV7h05

  1. 系统定义
  2. 技术架构
  3. 技术特性
  4. 系统安全

如何快速入门?:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. 构建第一个JS应用
  4. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/693184.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python coding with ChatGPT 打卡第21天| 二叉树:最近公共祖先

相关推荐 python coding with ChatGPT 打卡第12天| 二叉树&#xff1a;理论基础 python coding with ChatGPT 打卡第13天| 二叉树的深度优先遍历 python coding with ChatGPT 打卡第14天| 二叉树的广度优先遍历 python coding with ChatGPT 打卡第15天| 二叉树&#xff1a;翻转…

hope实验室预备役第4次测试题解

目录 1.Foreign Exchange 2.Takahashi Gets Lost 3.Sasha and the Beautiful Array 4.Sasha and the Drawing 5.Sasha and the Casino 6.Only one of two 7.村村通 8.传送门 1.Foreign Exchange 原题链接 Sample 1 InputcopyOutputcopy 4 5 7 0 3 2 2 4 3 5 25 Sample…

【AI绘画】Stable Diffusion简介_stable diffusion变现

手把手教你入门绘图超强的AI绘画&#xff0c;用户只需要输入一段图片的文字描述&#xff0c;即可生成精美的绘画。给大家带来了全新保姆级教程资料包 &#xff08;文末可获取&#xff09; Stable Diffusion是2022年发布的深度学习文本到图像生成模型&#xff0c;它主要用于根据…

ncnn之三(补充):window环境下vs2022安装ncnn+protobuf

启动VS2022 下面的 x64 Native Tools Command Prompt for VS2022 protobuf git clone gitgithub.com:protocolbuffers/protobuf.git# 或者 下载 https://github.com/google/protobuf/archive/v3.11.2.zip cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPERelease -D…

HTML的特殊字符

HTML的特殊字符 有些特殊的字符在 html 文件中是不能直接表示的&#xff0c;例如: 空格&#xff0c;小于号(<)&#xff0c;大于号(>)&#xff0c;按位与(&)。 空格 示例代码&#xff1a; 运行结果&#xff1a; 由于html 标签就是用 < > 表示的&#xff0…

【快速搞定Webpack5】修改输出文件目录及自动清理上次打包文件(五)

介绍 默认情况下webpack打包后&#xff0c;我们的图片和js等文件都会被打包到dist目录下&#xff0c;文件多了混淆在一起一方面不利于文件的查找和管理&#xff0c;另外一方面看上去也不美观。 所以今天我们学习的内容就是控制输出后的文件进入不同的目录。 一、配置 新增4…

BioTech - 大型蛋白质复合物的组装流程 (CombFold)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/136187314 CombFold是用于预测大型蛋白质复合物结构的组合和分层组装算法&#xff0c;利用AlphaFold2预测的亚基之间的成对相互作用。CombFold的组…

MES系统的功能有哪些?

阅读本文&#xff0c;你将了解&#xff1a;一、MES系统是什么&#xff1b;二、MES系统的功能&#xff1b;三、MES系统的使用场景与案例分析&#xff1b;四、如何更高效地利用MES系统。 这是我们公司正在使用的MES系统&#xff0c;已为大家搭建好模板了&#xff0c;无需下载&…

美团外卖商超销量数据

字段内容&#xff1a; shop_id varchar(50) NOT NULL, shop_id_str varchar(50) NOT NULL, shop_name varchar(400) DEFAULT NULL, shop_min_price varchar(10) DEFAULT NULL, shop_score varchar(10) DEFAULT NULL, shop_wm_score varchar(10) DEFAULT NU…

【Vuforia+Unity】AR02-长方体物体识别

1.创建模型 选择多维长方体图&#xff0c;这个长方体是生活中的真实物体的拍摄图&#xff0c;提前把6个面拍摄好并裁剪干净。 官网创建模型https://developer.vuforia.com/targetmanager/project/targets?projectId0ddbb5c17e7f4bf090834650bbea4995&avfalse 设置长宽高…

0220作业

C语言实现LED1闪烁 led.h #ifndef __LED_H__ #define __LED_H__//RCC寄存器封装 #define RCC_MP_AHB4_ENSETR (*(volatile unsigned int*)0x50000A28) //寄存器封装//GPIO寄存器封装 typedef struct{volatile unsigned int MODER; //00volatile unsigned int OTYPER; //04vol…

java数据结构与算法刷题-----LeetCode144. 二叉树的前序遍历

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 解题思路 利用递归&#xff0c;每次入栈一个结点&#xff08;每次递归都是…

优思学院【六西格玛案例】美国医院急诊部满意度提升

今天&#xff0c;优思学院来分享一个早期六西格玛项目的案例&#xff0c;项目背景是这样的&#xff0c;多年前&#xff0c;美国犹他州盐湖城的LDS医院已经实施了许多最佳实践。医院提供了床旁登记、高级分诊协议、护理点测试和实时放射学检查。一个强大而全面的持续质量改进计划…

CSS三大定位方式(浮动、定位、弹性盒)详细解析

CSS三大定位方式 前言&#xff1a;作为一名前端开发&#xff0c;已经工作2年了。由于自己是半路出家&#xff0c;从嵌入式方向转到前端开发&#xff0c;都是边百度边开发&#xff0c;很多基础都不了解&#xff0c;只要解决问题就好&#xff0c;但是近来为了让自己知识体系化&a…

北京高考数学填空题真题练一练(2014-2023)

距离2024年高考还有不到四个月的时间&#xff0c;今天我们来看看北京市的高考数学题真题。最近几年&#xff0c;只有北京、天津、上海三个直辖市的高考题是自主命题&#xff0c;其他省份全部是使用教育部统一命题的试卷。而且北京、天津、上海的数学现在也不再区分文理卷了&…

利用iSCSI服务部署IP SAN网络存储服务

一、配置环境&#xff08;Vmware WorkStation虚拟环境&#xff09; 服务端与客户端OS&#xff1a;openEuler 22.03-LTS CPU&#xff1a;1U1C 内存&#xff1a;2G 硬盘&#xff1a;5个SCSI磁盘&#xff0c;其中一个作为系统盘&#xff0c;另外四个配置为RAID5阵列 服务器IP…

TOUCHDESIGNER(1-2) 元件

元件来了 一级 二级 三级!!!

2024PMP备考-高质量PMP真题和很详细解析(3)

本专题&#xff0c;华研荟专门为大家讲解最近两年在中国大陆、香港、澳门地区的PMP考试真题&#xff0c;并且提供比较详细的解析&#xff0c;让大家知其然&#xff0c;还知其所以然。帮助大家最后20天有效冲刺&#xff0c;一次性3A通过2024年PMP考试。 2024年PMP考试新考纲-近年…

数据结构:跳表讲解

跳表 1.什么是跳表-skiplist1.1简介1.2设计思路 2.跳表的效率分析3.跳表实现3.1类成员设计3.2查找3.3插入3.4删除3.5完整代码 4.skiplist跟平衡搜索树和哈希表的对比 1.什么是跳表-skiplist 1.1简介 skiplist本质上也是一种查找结构&#xff0c;用于解决算法中的查找问题&…

Python开发户型图编辑器-2D/3D户型图展示

在现代家居设计中&#xff0c;户型图是不可或缺的工具&#xff0c;它为设计师和业主提供了一个直观的展示和规划空间的方式。然而&#xff0c;传统的户型图编辑软件往往复杂难用&#xff0c;限制了设计师的创作灵感。我们为您带来了一款全新的Python开发的户型图编辑器&#xf…