鸿蒙NEXT开发实战往期必看文章:

一分钟了解”纯血版!鸿蒙HarmonyOS Next应用开发!

“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线!(从零基础入门到精通)

HarmonyOS NEXT应用开发案例实践总结合(持续更新......)

HarmonyOS NEXT应用开发性能优化实践总结(持续更新......)


引言

在HarmonyOS的广阔生态中,六大原生特性——原生安全、原生精致、原生易用、原生智能、原生流畅与原生互联,共同构筑了系统独特的魅力。其中“原生互联”以其前所未有的创新性,成为了系统最耀眼的灵魂所在。这一特性深植于鸿蒙系统的核心,通过其强大的分布式软总线能力,不仅赋予了系统独一无二的魅力,更为开发者开启了一扇通往无限可能的大门。

将分布式软总线技术巧妙融入您的应用中,不仅能够显著提升用户体验的广度和深度,还能极大地拓宽应用的适用场景与边界。用户将享受到前所未有的跨设备无缝协同,无论是智能穿戴、智能家居,还是车载系统,都能实现信息的自由流转与服务的无缝衔接,从而探索出更多元、更便捷的生活方式。这样的融合,无疑是对“用户至上”理念的深刻践行,也是HarmonyOS生态繁荣发展的强大驱动力。

数据库跨设备数据同步

在开发指导中有这两种数据库的同步:键值型数据库跨设备数据同步-同应用跨设备数据同步(分布式)-ArkData(方舟数据管理)-应用框架 | 华为开发者联盟 (huawei.com)关系型数据库跨设备数据同步-同应用跨设备数据同步(分布式)-ArkData(方舟数据管理)-应用框架 | 华为开发者联盟 (huawei.com)

键值型数据库与关系型数据库的核心差异体现在其数据结构和应用场景上。键值型数据库(如Redis),以极简的键值对结构著称,它灵活高效,特别适用于高并发读写、缓存等场景,能够在鸿蒙的分布式环境中实现无缝的数据同步,确保所有设备间数据版本的即时一致性,无需复杂的合并逻辑或应用状态限制。

相对地,关系型数据库(如MySQL)以其结构化的数据存储和强大的查询能力闻名,适合处理复杂业务逻辑和需要高度数据一致性与完整性的场景。然而,在鸿蒙分布式环境中进行关系型数据库的同步时,开发者需自行处理多版本数据的合并逻辑,并确保同步操作在设备前台进行,这增加了实现的复杂度。

针对这些差异,开发者在选择数据库时需仔细评估项目需求。对于既需要关系型数据库的复杂查询能力,又希望享受分布式同步便利性的场景,一种策略是:在应用层面采用关系型数据库处理业务逻辑,当需要进行跨设备同步时,将数据转换为键值型格式进行同步,同步完成后再转换回关系型数据库。尽管这一流程较为繁琐,但在数据量不大时仍是一个可行的解决方案。具体操作可以看我们的开源仓库:atomgit.com/althenwaysatan/biandan-stage 。这里不再展开。

import { RebuildNotification } from './NotificationController';
import { get_accent } from './AccentController';
import prompt from '@ohos.promptAction';
import { Backup } from '../dao/controller/InitAndTable';
import distributedData from '@ohos.data.distributedKVStore';
import { refreshResource } from './EventsController';
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import common from '@ohos.app.ability.common';
import { refreshAllForm } from './FormController';


PersistentStorage.PersistProp('TerminalsSyncSwitch', 0)
PersistentStorage.PersistProp('AutoTerminalsSyncSwitch', 0)
PersistentStorage.PersistProp('LocalVersionNumber', 0)

const Version_Key: string = 'version'
const Data_Key: string = 'data'
const Length_Key: string = 'length'
const MaxLength: number = 400000;

export async function getPermission_DISTRIBUTED_DATASYNC(context: Context): Promise<boolean> {
  let array: Array<Permissions> = ['ohos.permission.DISTRIBUTED_DATASYNC'];
  let atManager = abilityAccessCtrl.createAtManager();
  let PermissionRequestResult = await atManager.requestPermissionsFromUser(context, array);
  let authResults0 = PermissionRequestResult.authResults[0];
  console.log("====>requestData====> " + authResults0 + ' ,');
  let ret: boolean = ((authResults0 == 0))
  if (!ret) {
    AppStorage.Set('TerminalsSyncSwitch', 0)
    AppStorage.Set('AutoTerminalsSyncSwitch', 0)
  }
  return ret
}

async function getKVStore(context: Context): Promise<any> {
  const kvManagerConfig = {
    context: context,
    bundleName: 'cn.biandangroup.biandanas'
  }
  let kvManager = distributedData.createKVManager(kvManagerConfig)

  const storeId = 'LatestVersion'
  const options = {
    createIfMissing: true,
    encrypt: false,
    backup: false,
    autoSync: true,
    //    autoSync: false,
    kvStoreType: distributedData.KVStoreType.SINGLE_VERSION,
    securityLevel: distributedData.SecurityLevel.S2,
  };
  let kvStore = await kvManager.getKVStore(storeId, options)
  return kvStore
}

const backUpImpl: Backup = new Backup()

/**
 * 以promise形式获取备份信息
 */
async function getBackupObject(context: Context): Promise<object> {
  return new Promise((resolve) => {
    backUpImpl.exportData((obj: object) => {
      resolve(obj)
    }, context)
  })
}

/**
 * 打开数据库监听
 */
export async function OpenSyncListener(context: Context) {
  const kvStore = await getKVStore(context)
  kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, function (data) {
    let TerminalsSyncSwitch = AppStorage.Get<number>('TerminalsSyncSwitch')
    if (TerminalsSyncSwitch == 0) return;
    let newVersion: number = AppStorage.Get<number>('LocalVersionNumber')
    kvStore.get(Version_Key, function (err, SyncVersion) {
      console.log("get success Version_Key: " + SyncVersion);
      console.log("get error: " + err);
      if (SyncVersion != undefined && SyncVersion > newVersion) {
        AlertDialog.show({
          title: '检测到数据更新',
          message: '有数据可以从其他设备同步,请接收。',
          primaryButton: {
            value: $r('app.string.cancel'),
            fontColor: get_accent(),
            action: () => {
              prompt.showToast({
                message: '已取消,可在 设置-同步 界面重新拉取同步',
                duration: 1000,
              });
            }
          },
          secondaryButton: {
            value: $r('app.string.confirm'),
            fontColor: get_accent(),
            action: () => {
              getNewAndImportData(context)
              refreshResource(context)
            }
          },
          cancel: () => {
            prompt.showToast({
              message: '已取消,可在 设置-同步 界面重新拉取同步',
              duration: 1000,
            });
          }
        })
      }
    });
    console.log("[Sync] dataChange callback call data: " + JSON.stringify(data));
  });
}

/**
 * 关闭数据库监听
 */
export async function CloseSyncListener(context: Context) {
  const kvStore = await getKVStore(context)
  kvStore.off('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, function (data) {
    console.log("[Sync] dataChange callback call data: " + JSON.stringify(data));
  });
}

/**
 * 导出数据新版本到数据库
 * @param showPrompt 是否前台导出
 */
export async function ExportNewVersion(context: Context, showPrompt: boolean = false) {
  const kvStore = await getKVStore(context)

  let data: object = await getBackupObject(context)
  let str: string = JSON.stringify(data)
  console.log('[Sync] backup value: ' + str);

  let newVersion: number = AppStorage.Get<number>('LocalVersionNumber')

  console.log('[Sync] newVersion: ' + newVersion);
  kvStore.get(Version_Key, function (err, SyncVersion) {
    console.log("get success Version_Key: " + SyncVersion);
    console.log("get error: " + err);
    if (SyncVersion == undefined || SyncVersion < newVersion) {
      console.log('[Sync] newVersion: ' + newVersion);
      kvStore.put(Version_Key, newVersion)

      let i: number = 0;
      for (; i < str.length; i += MaxLength) {
        kvStore.put(Data_Key + (i.toString()), str.substring(i, i + MaxLength))
        //        result.push(str.substring(i, i + MaxLength));
      }
      kvStore.put(Length_Key, str.length)
      //      kvStore.put(Data_Key, str)

      if (showPrompt)
        prompt.showToast({
          message: '发送成功,快去看看吧!',
          duration: 1000,
        });
    } else {
      if (showPrompt)
        prompt.showToast({
          message: '发送失败,发送数据不为最新',
          duration: 1000,
        });
    }
  });
}


/**
 * 从数据库导入数据新版本
 */
async function ImportNewVersion(context: Context): Promise<[boolean, number, string]> {
  let TerminalsSyncSwitch = AppStorage.Get<number>('TerminalsSyncSwitch')
  if (TerminalsSyncSwitch == 0)
    return [false, 0, '']

  const kvStore = await getKVStore(context)

  let newVersion: number = AppStorage.Get<number>('LocalVersionNumber')

  console.log('[Sync] newVersion: ' + newVersion);

  return new Promise((resolve) => {
    kvStore.get(Version_Key, async function (err, SyncVersion) {
      console.log("get success Version_Key: " + SyncVersion);
      console.log("get error: " + err);
      if (SyncVersion != undefined && SyncVersion > newVersion) {
        let end: number = await kvStore.get(Length_Key)
        let obj: string = ''
        for (let i = 0; i < end; i += MaxLength) {
          obj += await kvStore.get(Data_Key + (i.toString()))
        }
        console.log("get success Data_Key: " + obj);
        console.log("get error: " + err);
        console.log('[Sync] import: ' + JSON.stringify(obj));
        let ret = JSON.parse(obj)
        console.log('[Sync] import value: ' + JSON.stringify(ret));
        backUpImpl.importData(ret, (flag: boolean, reason: string) => {
          resolve([flag, SyncVersion, reason])
        }, context)
      } else {
        resolve([false, SyncVersion, '未接收到新数据'])
      }
    });
  })
}

/**
 * 为当前数据版本编号
 * @param newVersionNumber 指定数据版本
 */
export function UpdateVersionNumber(newVersionNumber: number = new Date().getTime()) {
  if (newVersionNumber <= AppStorage.Get<number>('LocalVersionNumber'))
    return;
  console.log('[Sync] UpdateVersionNumber:' + newVersionNumber)
  AppStorage.Set<number>('LocalVersionNumber', newVersionNumber)
  let context: Context = getContext(this) as common.Context;
  setTimeout(()=>{refreshAllForm(context)}, 100);
}

/**
 * 获取新的数据并导入
 * @param showFail 是否展示失败提醒
 */
export async function getNewAndImportData(context: Context, showFail: boolean = true) {
  let TerminalsSyncSwitch = AppStorage.Get<number>('TerminalsSyncSwitch')
  if (TerminalsSyncSwitch == 0) return;
  let ret: [boolean, number, string] = await ImportNewVersion(context)
  if (ret[0]) {
    UpdateVersionNumber(ret[1])
    await RebuildNotification(context)
    prompt.showToast({
      message:  '接收数据成功',
      duration: 1000,
    });
  } else if (showFail) {
    prompt.showToast({
      message: '接收数据失败,' + ret[2],
      duration: 1000,
    });
  }
}

值得注意的是,鸿蒙生态正在不断进步,据悉未来可能通过新的API版本支持单版本分布式关系型数据库(因为数据库从SQLite换成了高斯?),这一变化有望进一步简化数据同步与管理的复杂度,为开发者带来更加便捷高效的数据管理体验。更多技术更新与实战策略,敬请关注我们的开源仓库及鸿蒙官方发布信息。

分布式数据对象跨设备数据同步

传统数据同步方法要求开发者处理复杂的通信与错误管理,随设备数量增加,调试难度骤增。分布式数据对象(distributedDataObject)作为创新解决方案,通过全局访问“变量”简化了这一过程,提供统一接口管理数据对象,并自动同步变更,减轻开发者负担。

在便单APP中,我们采用单一Object同步所有计时器状态至全局变量,集中管理简化了跨设备状态追踪,提升开发效率与系统维护性。为避免状态冲突,设计将多端与本地变量分离,本地变化即时覆盖多端并触发同步,确保各设备依赖本地变量执行逻辑,多端变量仅作同步桥梁。

DDO的引入极大简化了设备间同步机制,通过全局变量实时共享,不仅优化了系统性能,还增强了用户体验。

Logo

更多推荐