鸿蒙电脑 Harmony OS 6了,再不入局就晚了。
尊贵的鸿蒙电脑用户,付费能力可以说是全球最强,遥遥...
Harmony OS 5的时候不入局是对的,装机少,系统bug多,适配的app也少。
自从升级Harmony OS 6,各方各面都完善起来,市场风向也从
下着玩玩-到了-用户刚需。
很多手机端优秀app的开发者发现,一旦兼容2IN1,(2IN1是华为对PC电脑设备的统称)
运营统计中曾经占比渺小的2IN1设备,
开始节节攀升。
摆在我们开发者面前有几个问题需要解决:
1.不重开发,如何兼容2IN1?(请看系列一)
2.有2IN1的用户通常有华为手机,甚至多数有全家桶,应该为APP加入哪些功能增强竞争力?
3.买不起昂贵的PC,该怎么开发、测试。
关注我,将在接下来的系列中,用实战中的经验,解决以上三个问题。
第二问:有2IN1的用户通常是全家桶用户,应该如何增强竞争力?
回答:跨设备协同体验
鸿蒙(HarmonyOS)的“一多开发”(一次开发,多端部署)不仅关注单设备内的多形态适配(如手机、平板、2in1),更核心的是支撑跨设备协同体验,包括:
- 分布式能力
- 应用接续(Continuity)
- 碰一碰(NFC/蓝牙快速发现与连接)
- 鼠标/键盘联动(外设协同)
一、分布式能力:鸿蒙一多的底层基石
划重点,下表一定要逐字看完,欲练神功,必先死记。
| 技术 | 作用 | 开发接口 |
|---|---|---|
| 分布式软总线(SoftBus) | 设备自动发现、安全连接、低延时通信 | @ohos.distributedHardwaredeviceManager |
| 分布式数据管理(DistributedDataManager) | 多设备间数据同步(如剪贴板、配置) | @ohos.data.relationalStorecreateDistributedTable() |
| 分布式任务调度(DTSEngine) | 跨设备拉起服务、迁移任务 | @ohos.ability.featureAbilitystartAbility()+want中指定设备 |
使用场景:
分布式让2IN1设备(2IN1是华为对各种PC设备的统称)能够和手机、平板、手表手环、智慧屏、车机、耳机等一切具有harmony OS 设备进行数据同步。
无论如何,都要让APP具备一些分布式能力,即便APP在之前没有考虑分布式,没关系,升级到分布式没有想象中那么困难。
分布式的三要素:权限、设备、分布式数据
要实现分布式就三步走:
1.获取权限,通过结构主动向用户索要“发现设备”的权限。
2.获取设备表。
3.对每一台设备进行分布式数据的拉取和推送。
权限代码片段:
/** * 1.判断是否已授权,未授权则拉起授权 * @param context * @returns */ async checkPermissions(context:common.UIAbilityContext): Promise<void> { try { let grantStatus1: boolean = await this.checkPermissionGrant('ohos.permission.DISTRIBUTED_DATASYNC') === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;// 获取同步权限状态。 if (!grantStatus1) { // 申请权限。 this.reqPermissionsFromUser(['ohos.permission.DISTRIBUTED_DATASYNC'], context); } else { // 已经授权,可以继续访问目标操作。 emitter.emit('distributedSuccessFulEvt'); } } catch (e) { // 获取权限失败 emitter.emit('distributedErrorEvt'); throw e as SubError; } } /** * 1.1权限判断 * @param permission * @returns */ private async checkPermissionGrant(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> { let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED; // 获取应用程序的accessTokenID。 let tokenId: number = 0; try { let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo; tokenId = appInfo.accessTokenId; } catch (error) { const err: BusinessError = error as BusinessError; console.error(`Failed to get bundle info for self, code: ${err.code}, message: ${err.message}`); } // 校验应用是否被授予权限。 try { grantStatus = await atManager.checkAccessToken(tokenId, permission); } catch (error) { const err: BusinessError = error as BusinessError; console.error(`Failed to check access token, code: ${err.code}, message: ${err.message}`); } return grantStatus; } /** * 1.2拉起授权窗口 * @param permissions * @param context */ private reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void { let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗。 atManager.requestPermissionsFromUser(context, permissions).then((data) => { let grantStatus: Array<number> = data.authResults; let length: number = grantStatus.length; for (let i = 0; i < length; i++) { if (grantStatus[i] === 0) { // 用户授权,等待其他权限。 } else { // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限。 let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); atManager.requestPermissionOnSetting(context, ['ohos.permission.DISTRIBUTED_DATASYNC']).then((data: Array<abilityAccessCtrl.GrantStatus>) => { if(data[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED){ console.info(`2次同步授权完成, result: ${data}`); AppStorage.setOrCreate(IS_DATA_SYNC,true); emitter.emit('distributedSuccessFulEvt'); } else { emitter.emit('distributedErrorEvt'); emitter.emit('distributedReject'); //拒绝了权限申请。 console.info(`2次同步授权被拒绝, result: ${data}`); } }).catch((err: BusinessError) => { emitter.emit('distributedErrorEvt'); throw new SubError({code:err.code,message:err.message}); console.error(`requestPermissionOnSetting fail, code: ${err.code}, message: ${err.message}`); }); return; } } // 授权成功。 console.info(`同步授权完成, result: ${data}`); emitter.emit('distributedSuccessFulEvt'); }).catch((err: BusinessError) => { emitter.emit('distributedErrorEvt'); console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`); }) }获取了权限后,先把数据库设置为分布式。
const tables: string[] = [ 'table1', //表1 'table2', //表2 ]; try { //设置表们为分布式。 await store.setDistributedTables(tables); } catch (error) { console.error(`Set distributed tables failed: ${error.code}, ${error.message}`); }查找设备的代码片段:
searchDevices(): distributedDeviceManager.DeviceBasicInfo[] { // 查询组网内的设备列表 const deviceManager = distributedDeviceManager.createDeviceManager('com.waitpanda.xiexiaoshuo'); try { const deviceList = deviceManager.getAvailableDeviceListSync(); if (!deviceList) { throw new SubError({ code: 998404, message: '没有设备' }); } return deviceList; //设备列表。 } catch (error) { const e = error as SubError; console.error('d2d sync with all devices',e.code,e.message) throw error as SubError; //这个SubError是我自定义的一个错误类,可以直接使用Error } }推送本地数据到其他设备。这个操作一定是你使用insert、update、delete等数据库写操作后进行。
// 构造用于同步分布式表的谓词对象 const predicates = new relationalStore.RdbPredicates(DB_CONSTANTS.TABLE1); predicates.inDevices(deviceList); try { const result = await store.sync(relationalStore.SyncMode.SYNC_MODE_PUSH, predicates); // 获取同步结果 for (let i = 0; i < result.length; i++) { const deviceId = result[i][0]; const syncResult = result[i][1]; if (syncResult === 0) { console.info('rdbDataSync', `device:${deviceId} table:${table} sync success`); } else { // 有些设备没有你的APP,它会报错。 console.error('rdbDataSync', `device:${deviceId} table:${table} sync failed, status:${syncResult}`); } } } catch (e){ console.error('pushToAllDevice同步失败'); }关于分布式数据库(键值对、关系型、用户首选项)的不同:键值对分布式可以各设备之间相互读写,关系型则只能读其他设备,不能写。这个特性一定搞明白。
二、应用接续(Continuity):任务无缝流转
场景示例
- 手机上写邮件 → 点击“接续到2IN1” → 2IN1继续编辑
- 车机导航 → 到家后自动接续到手表步行导航
注意的是,这里要用到一个叫“分布式数据对象”的东西,它和分布式数据库中读出来的内容不是同一个东西,它是个独立的东西。
这个叫分布式数据对象的,主要作用是,将现在内存中进行了一半的内容记下来,另一台设备从这个分布式数据对象中把内容读出,继续进行操作。
除了分布式数据对象,还有个重要的东西叫SessionId,本端将生成的sessionId通过want传递到远端,供远端激活同步使用。
三、碰一碰(Tap to Share / Tap to Connect)
技术原理
- 基于NFC + 蓝牙/WiFi P2P快速建立连接;
- 触发系统级原子化服务(Atomic Service)或FA 拉起。
优势:无需用户打开 App,符合鸿蒙“服务找人”理念。
实战理解
将分享、截图、应用接续打包成碰一碰,主打让用户在人前装*,人后实用。这个功能很能提升用户使用率,有条件一定上一个。
四、鼠标联动(外设协同)
适用场景
- 电脑连接蓝牙鼠标 → 应用需显示 hover 效果、右键菜单;
- PC 模式下支持快捷键(Ctrl+C/V);
- 多屏联动(鼠标能在手机、平板、电脑三者的屏幕上无缝移动)。
实战理解
只需要把图片、文本、列表等设置成能拖入拖出的方式复制、拷贝,就能让你的APP具备鼠标联动功能。这是全家桶用户高频使用的功能,性价比极高的功能。
五、整体架构建议:如何在一多工程中集成这些能力?
1Project/ 2├── common/ # 公共逻辑(含分布式工具类) 3│ └── DistributedHelper.ts 4├── features/ 5│ ├── continuity/ # 应用接续模块 6│ ├── nfc-share/ # 碰一碰分享 7│ └── input-enhance/ # 鼠标/键盘增强 8├── products/ 9│ ├── phone/ # 手机端:简化接续入口 10│ ├── tablet/ # 平板端:常驻侧边栏 + 分栏 + 鼠标支持 11│ └── pc/ # PC端:快捷键 + 右键菜单 12└── module.json5 # 声明分布式权限、NFC、外设能力六、总结:鸿蒙一多 × 分布式 = 全场景体验
表格
| 特色能力 | 技术方案 | 一多开发要点 |
|---|---|---|
| 分布式 | 软总线 + 分布式数据/任务 | 一套逻辑,多设备协同执行 |
| 应用接续 | ContinuationRegisterManager | 状态序列化 + 目标设备UI适配 |
| 碰一碰 | NFC + 原子化服务/卡片 | 无感触发,服务直达 |
| 鼠标联动 | PointerEvent + 快捷键 | 大屏设备增强交互,小屏忽略 |
🔥核心思想:
“一多”不仅是适配不同屏幕,更是构建“以人为中心的超级终端”。
你的应用不再属于某一台设备,而是运行在用户所有的鸿蒙设备组成的“分布式网络”中。这正是鸿蒙生态对开发者的最大红利。