OpenHarmony分布式协同开发实践

跨设备协同开发实践

鸿蒙系统凭借其独特的分布式架构与强大的多设备协同能力,实现了近乎零延迟的数据共享与高效的任务分发。开发者借助分布式任务调度机制,能够轻松构建多设备协同的应用场景,为用户带来无缝的跨设备交互体验。

1. 分布式能力介绍

跨设备数据同步:确保不同设备间的数据实时、准确地更新。
多设备协同任务调度:根据设备性能智能分配任务,实现资源的最优利用。
跨设备UI迁移:支持用户界面在不同设备间灵活切换,提升使用便利性。

2. 分布式开发核心概念

OpenHarmony进一步升级分布式能力,重新定义设备间的协同逻辑,其核心机制涵盖以下关键部分:

分布式软总线:作为设备通信的基石,它支持设备动态发现与连接,建立稳定可靠的数据传输通道,保障数据与指令的高效交互。
分布式数据管理:提供强一致性的数据分布式存储方案,确保多个设备对数据的操作始终保持实时同步,保证数据的准确性与完整性。
分布式任务调度:应用可依据设备性能动态选择任务执行节点,优化资源配置,显著提升用户体验。

3. 实践案例

方案介绍

在使用分布式数据对象进行跨设备数据同步时,首先需申请ohos.permission.DISTRIBUTED_DATASYNC权限。该权限类型为user_grant,意味着必须通过弹窗向用户请求授权。

完成权限申请后,即可创建分布式数据对象并将其加入可信组网。值得注意的是,参与跨设备数据同步的设备需满足以下条件:登录相同的华为账号、处于同一网络环境,并且开启蓝牙功能。

注:当一个分布式对象加入组网时,如果其自身数据与组网内已有数据存在差异,默认会刷新组网内的数据。若希望在加入组网时不刷新组网数据,同时获取组网内的数据,可以将相关属性设置为undefined 。例如,在后续代码中的Message对象可设置为new Message(undefined,undefined)

实现效果

以A、B两台设备为例,分别在其上点击创建分布式数据对象,并开启数据变更监听功能。此时,在A设备中输入数据,B设备能够立即自动同步,实现数据的实时共享。

开发步骤

一、申请权限

使用分布式数据对象实现跨设备数据同步,ohos.permission.DISTRIBUTED_DATASYNC权限必不可少。由于该权限需用户手动授权,因此需要通过弹窗向用户请求。若用户拒绝授权,需引导其前往“设置–隐私和安全–多设备协同”中进行授权操作。在调用分布式数据对象相关功能前,建议先检查用户的授权状态。权限申请代码示例如下:

//EntryAbility.ets
function reqPermissionsFromUser(permissions: Array<Permissions>,context:common.UIAbilityContext): void {
            
  let 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) {
            
        // 用户已授权,可继续执行后续操作
        console.info(`User authorization succeeded.`)
      }
      else {
            
        // 用户拒绝授权,提示用户必须授权才能使用当前功能,并引导至系统设置页面开启权限
        console.error("user did not grant!")
        openPermissionsInSystemSettings(context);
      }
    }
    // 授权成功处理逻辑
  }).catch((err: String) => {
            
    // 授权失败处理逻辑
  })
}

function openPermissionsInSystemSettings(context: common.UIAbilityContext): void {
            
  let wantInfo: Want = {
            
    bundleName: 'com.huawei.hmos.settings',
    abilityName: 'com.huawei.hmos.settings.MainAbility',
    uri: 'application_info_entry',
    parameters: {
            
      pushParams: 'com.example.distributeddata' // 打开指定应用的详情页面
    }
  }
  context.startAbility(wantInfo).then(() => {
            
    // 成功打开系统设置页面的处理逻辑
  }).catch((err: BusinessError) => {
            
    // 打开系统设置页面失败的处理逻辑
  })
}

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
            
  const permissions: Array<Permissions> = ['ohos.permission.DISTRIBUTED_DATASYNC'];
  reqPermissionsFromUser(permissions,this.context)
  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
二、创建分布式对象并加入组网

实现方案
通过封装DistributedDataModel类,在创建实例时传入source:Object对象(本案例中传入Message对象,该对象包含createTimemessage两个属性),借助distributedDataObject.create方法创建对应的分布式数据对象,并使用setSessionId方法为其设置sessionId

同时,封装单例类GlobalThis用于存储message,并通过Observed装饰器实现数据变更时自动刷新到TextArea组件。

注:同一设备只能有一个分布式对象加入相同的sessionId。若A对象已加入某个sessionId,B对象再尝试加入同一sessionId将会失败。

import distributedDataObject from '@ohos.data.distributedDataObject';
import common from '@ohos.app.ability.common';
import {
             BusinessError } from '@kit.BasicServicesKit';
import {
             ShowData } from '../utils/ShowData'

let localObject: distributedDataObject.DataObject;

export default class DistributedDataModel {
            
  showData: ShowData = ShowData.getInstance()

  constructor(context: common.Context, source: Object) {
            
    if (localObject == null || localObject === undefined) {
            
      localObject = distributedDataObject.create(context, source);
      localObject.setSessionId('123456')
      console.info('setSessionId end');
    } else {
            
      console.log('请勿重复创建分布式数据对象并加入同一sessionID')
    }
  }

  //设置message
  setMessage(message: string) {
            
    if (localObject != null && localObject!== undefined) {
            
      localObject['message'] = message;
    } else {
            
      console.log('localObject is null or undefined')
    }
  }
}
// 构造单例对象
@Observed
export class ShowData {
            
  private static instance: ShowData;
  public message: string;

  constructor(message: string) {
            
    this.message = message
  }

  public static getInstance(): ShowData {
            
    if (!ShowData.instance) {
            
      ShowData.instance = new ShowData('');
    }
    return ShowData.instance;
  }

  getMessage(): string {
            
    return this.message;
  }

  setMessage(value: string): void {
            
    this.message = value;
  }
}
import DistributedDataModel from '../DistributedData/DistributedDataModel'
import {
             ShowData } from '../utils/ShowData'

class Message {
            
  createTime?: Date
  message?: string
  constructor(createTime?: Date, message?: string) {
            
    this.createTime = createTime
    this.message = message
  }
}

let now = Date.now()
let distributedDataA: DistributedDataModel;

@Entry
@Component
struct Index {
            
  @State messages: Message = new Message(new Date(now), '这是初始数据');
  @State showData: ShowData = ShowData.getInstance()
  controller: TextAreaController = new TextAreaController()

  build() {
            
    Row() {
            
      Column() {
            
        TextArea({
            
          text: this.showData.message,
          placeholder: 'The text area can hold an unlimited amount of text. input your word...',
          controller: this.controller
        })
         .placeholderFont({
             size: 16, weight: 400 })
         .width(336)
         .height(56)
         .margin(20)
         .fontSize(16)
         .fontColor('#182431')
         .backgroundColor('#FFFFFF')
         .onChange((value: string) => {
            
            distributedDataA.setMessage(value)
          })
        Button('创建分布式数据对象')
         .fontSize(20)
         .fontWeight(FontWeight.Bold)
         .onClick(() => {
            
            distributedDataA = new DistributedDataModel(getContext(), this.messages);
            //如果设备加入组网时不希望刷新组网中已有的数据,注意将该分布式数据对象的属性设置为undefined,即messages的属性设置为undefined
            // distributedDataA= new DistributedDataModel(getContext(),new Message(undefined,undefined));
          })
         .margin(10)
      }
     .height('100%')
     .width('100%')
    }
  }
}
三、操作分布式数据对象

监听数据变更
当A、B两台设备完成组网后,在A设备上通过on("change")开启监听。此时,若B设备对数据进行修改,A设备能够实时读取到B设备修改后的数据。当A设备通过off("change")删除监听后,B设备再次修改数据,A设备读取到的数据将保持为B设备修改前的状态 。

import {
             ShowData } from '../utils/ShowData'

showData: ShowData = ShowData.getInstance()

//获取message
getMessage(): string {
            
  if (localObject!= null && localObject!== undefined) {
            
    console.log('获取组网内最新数据 createTime is'+ localObject['createTime'])
    console.log('获取组网内最新数据  message is'+ localObject['message'])
    return localObject['message']
  } else {
            
    console.log('localObject is null or undefined')
    return ''
  }
}

//设置message
setMessage(message: string) {
            
  if (localObject!= null && localObject!== undefined) {
            
    localObject['message'] = message;
  } else {
            
    console.log('localObject is null or undefined')
  }
}

//监听对象数据变更。可监听对端数据的变更,以callback作为变更回调实例。
onChange() {
            
  localObject.on('change', (sessionId: string, fields: Array<string>) => {
            
    console.info('change' + sessionId);
    if (fields!= null && fields!= undefined) {
            
      for (let index: number = 0; index < fields.length; index++) {
            
        this.showData.setMessage(localObject[fields[index]])
        console.info(`message is ${
              this.showData.getMessage()}`)
        console.info(`The element ${
              localObject[fields[index]]} changed.`);
      }
    }
  });
}

// 删除数据变更监听 当不再进行数据变更监听时,使用此接口删除对象的变更监听
deleteChange() {
            
  if (localObject!= null && localObject!== undefined) {
            
    // 删除数据变更回调changeCallback
    localObject.off('change', (sessionId: string, fields: Array<string>) => {
            
      console.info('change' + sessionId);
      if (fields!= null && fields!= undefined) {
            
        for (let index: number = 0; index < fields.length; index++) {
            
          console.info('changed!' + fields[index] +'' + localObject[fields[index]]);
        }
      }
    });
    // 删除所有的数据变更回调
    localObject.off('change');
  } else {
            
    console.log('localObject is null or undefined')
  }
}

监听组网内分布式数据对象的上下线及移除监听
通过on("status")方法可以监听分布式对象的上下线状态。当对端设备上线或下线时,将触发预先设定的业务回调函数。若不再需要监听,可使用off("status")方法移除监听。

//监听分布式设备上下线
onStatus() {
            
  if (localObject!= null && localObject!== undefined) {
            
    localObject.on('status', (sessionId: string, networkId: string, status: 'online' | 'offline') => {
            
      console.info('status changed'  + sessionId +'' + status +'' + networkId);
    });
  } else {
            
    console.log('localObject is null or undefined')
  }
}

//  删除设备上下线监听 当不再进行对象上下线监听时,使用此接口删除对象的上下线监听。
deleteStatus() {
            
  if (localObject!= null && localObject!== undefined) {
            
    // 删除上下线回调changeCallback
    /*localObject.off('status', (sessionId: string, networkId: string, status: 'online' | 'offline') => {
      console.info('status changed'+ sessionId +'' + status +'' + networkId);
    });*/
    // 删除所有的上下线回调
    localObject.off('status');
  } else {
            
    console.log('localObject is null or undefined')
  }
}

保存分布式数据对象及撤回保存的分布式数据对象
save(deviceId: string)方法用于保存分布式数据对象。其中,deviceId参数用于指定保存数据的设备编号,默认值为"local",表示将数据保存在本地设备。通过save("local")保存对象数据后,只要应用处于运行状态,数据将一直保留;当应用退出后重新启动,可恢复保存在设备上的数据。

revokeSave()方法用于撤回已保存的分布式数据对象。若对象保存在本地设备,执行该方法将删除所有受信任设备上保存的数据;若对象保存在其他设备,则会删除本地设备上的数据。

注意:revokesave仅能撤销尚未恢复的数据。例如,若A设备将数据save到B设备,B设备创建分布式对象时自动恢复了数据,此时A设备调用revokesave无法撤销B设备已恢复的数据 。

deviceId获取方式

在使用on('status')监听时,可以获取标识对象设备的networkId,该networkId可作为deviceId使用。
在接续场景中,可从wantParam中获取targetDevice作为deviceId

// 保存分布式数据对象
saveData() {
            
  if (localObject!= null && localObject!== undefined) {
            
    // 保存数据对象,如果应用退出后组网内设备需要恢复对象数据时调用
    localObject.save('local').then((result: distributedDataObject.SaveSuccessResponse) => {
            
      console.info(`Succeeded in saving. SessionId:${
              result.sessionId},
        version:${
              result.version},deviceId:${
              result.deviceId}`);
    }).catch((err: BusinessError) => {
            
      console.error(`Failed to save. Code:${
              err.code},message:${
              err.message}`);
    });
  } else {
            
    console.log('localObject is null or undefined')
  }
}

// 撤回保存的分布式数据对象
revokeSaveData() {
            
  if (localObject!= null && localObject!== undefined) {
            
    // 撤回保存的数据对象
    localObject.revokeSave().then((result: distributedDataObject.RevokeSaveSuccessResponse) => {
            
      console.info(`Succeeded in revokeSaving. Session:${
              result.sessionId}`);
    }).catch((err: BusinessError) => {
            
      console.error(`Failed to revokeSave. Code:${
              err.code},message:${
              err.message}`);
    });
  } else {
            
    console.log('localObject is null or undefined')
  }
}
© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容