一、环境配置

  • 平台硬件: RK3568
  • OS版本:OpenHarmony 5.0 Release 32bit(5.0.0.71)
  • sdk:API12(full sdk)
  • DevEco:5.0.3.910(DevEco Studio 5.0.0 Release)

二、项目介绍

  • 本项目实现了分布式认证,分布式相机预览拍照,分布式图库同步显示删除
  • 原文章是基于OpenHarmony 4.1Release的分布式相机分布式图库实现_会飞的企鹅12138-Laval社区
  • 最近看到社区有人在5.0R上复现遇到了问题,特开此篇基于OpenHarmony 5.0.0Release的分布式相机分布式图库实现。主要介绍在4.1R的基础上,5.0.0R实现分布式相机,分布式图库新增了那些需要注意的点,对于上一篇文章中存在的描述,本篇不再描述,但不代表不重要

三、项目实现注意事项

3.1分布式相机在5.0.0Release不支持使用PhotoSession

3.1.1、源码

  • 4.1Release实现方式为

    const NORMAL_PHOTO = camera.SceneMode.NORMAL_PHOTO // 普通拍照模式
    private capSession: camera.PhotoSession = undefined;
    
    // 创建会话
    this.capSession = this.cameraMgr.createSession(NORMAL_PHOTO);
    
  • 5.0.0Release实现方式为

    private capSession: camera.CaptureSession = undefined;
    
    // 创建会话
    this.capSession = this.cameraMgr.createCaptureSession();
    

3.1.2、问题

5.0.0Release使用PhotoSession创建会话,初始化远程相机时会卡住,无法完成远程相机初始化,无法显示预览图象。

3.1.3、解决方案

回退使用CaptureSession解决。

3.1.4、总结

本地相机初始化时4.1和5.0.0都支持PhotoSession,但是5.0.0访问远程相机初始化时必须使用CaptureSession。所以建议5.0.0都使用CaptureSession。 造成新api却不支持新PhotoSession的原因,我猜测是社区的测试用例你没有及时更新,导致新出的api还在测试旧的CaptureSession。

3.2分布式文件子系统在5.0.0Release不能稳定性同步文件

3.2.1、源码

  • 4.1Release实现方式为

      /**
       * 获取图片路径
       * @returns
       */
      getImageList(): string[] {
        // 获取分布式文件夹下的文件
        let files = fs.listFileSync(this.distributedFilepath, listFileOption);
        // 将文件名转换为全路径的uri
        files = files.map((item) => fileUri.getUriFromPath(`${this.distributedFilepath}/${item}`));
        // 排序返回,防止展示顺序不一致
        return files.sort();
      }
    
  • 5.0.0Release实现方式为

      /**
       * 获取图片路径
       * @returns
       */
      async getImageList(): Promise<string[]> {
    
        this.syncLocalRdb();
    
        // 获取分布式文件夹下的文件(概率性获取不到)
        let allFiles = fs.listFileSync(this.distributedFilepath, listFileOption);
    
        // 将文件名转换为全路径的uri
        allFiles = allFiles.map((item) => fileUri.getUriFromPath(`${this.distributedFilepath}/${item}`));
    
        // 获取远端图片
        let remoteFiles = await RDBModel.queryRemoteImage();
    
        remoteFiles.forEach(item => {
          allFiles.push(`file://com.demo.distributeddemo5/data/storage/el2/distributedfiles/${item.split('/').pop()}`);
        });
    
        // 去重
        allFiles = [...new Set([...allFiles])];
    
        // 排序返回,防止展示顺序不一致
        Logger.info(TAG, `${allFiles.toString()}`);
        return allFiles.sort();
      }
    
      syncLocalRdb() {
        // 本地表表上的图片 ${this.distributedFilepath}/${item}
        let DBFiles = RDBModel.queryLocalImage();
    
        // 本地文件夹下的文件 ${this.distributedFilepath}/${item}
        let localFiles =
          fs.listFileSync(this.distributedFilepath, listFileOption).map(item => `${this.distributedFilepath}/${item}`);
    
        if (DBFiles.sort().toString() !== localFiles.sort().toString()) {
          // 表上有,本地文件夹没有的文件,就是被远端删除了,需要本地把表里面的也删除
          let deleteItem = DBFiles.filter(item =>!localFiles.includes(item));
          deleteItem.forEach(item => RDBModel.deleteLocalItem(item));
        }
      }
    

3.2.2、问题

5.0.0Release使用fs.listFileSync获取不到远程文件名,导致无法在图库页生成预览图。

3.2.3、解决方案

参考新文档,新增connectDfs方法(稳定性依然不佳)-->维护一个分布式数据库,建一张表来存储本地的文件,给远端设备查询时使用。

  /**
   * 挂载公共目录触发同步
   * @returns
   */
  static connectDfs():Promise<void>{
    return new Promise<void>((resolve, reject) => {
      // 定义访问公共文件目录的回调
      let listeners: fs.DfsListeners = {
        onStatus: (networkId: string, status: number): void => {
          Logger.error(`Failed to access public directory status : ${status} networkId : ${networkId}`);
        }
      }

      // 访问并挂载公共文件目录
      fs.connectDfs(RemoteDeviceModel.getNetWorkId(), listeners).then(() => {
        Logger.info(TAG, "Success to connectDfs");
        resolve();
      }).catch((error: BusinessError) => {
        let err: BusinessError = error as BusinessError;
        Logger.error(TAG, `Failed to connectDfs Code: ${err.code}, message: ${err.message}`);
        reject(error);
      });
    })
  }
import { relationalStore } from "@kit.ArkData";
import { getGlobalObject } from "../utils/GlobalThis";
import { common } from "@kit.AbilityKit";
import Logger from "../utils/Logger";
import RemoteDeviceModel from "./RemoteDeviceModel";
import { JSON } from "@kit.ArkTS";
import { BusinessError } from "@kit.BasicServicesKit";

const TAG = 'RDBModel';

export class RDBModel {
  private static rdbStore: relationalStore.RdbStore | undefined = undefined;
  private static TABLE_NAME: string = 'IMAGE';
  private static CREATE_TABLE_SQL: string = `CREATE TABLE IF NOT EXISTS ${RDBModel.TABLE_NAME}(path TEXT PRIMARY KEY)`;
  private static predicates = new relationalStore.RdbPredicates(RDBModel.TABLE_NAME);
  private static isCreateDbDone: boolean = false;

  /**
   * 初始化数据库
   */
  static async initRDB() {

    if (RDBModel.isCreateDbDone) {
      Logger.info(TAG, 'getRdbStore isCreateDbDone');
      return;
    }

    const STORE_CONFIG: relationalStore.StoreConfig = {
      name: "RdbTest.db",
      securityLevel: relationalStore.SecurityLevel.S1
    };

    let context = getGlobalObject("context") as common.UIAbilityContext;

    try {
      // 获取数据库存储对象
      RDBModel.rdbStore = await relationalStore.getRdbStore(context, STORE_CONFIG);
    } catch (err) {
      Logger.error(TAG, `getRdbStore err ${JSON.stringify(err)}`);
    }
    Logger.info(TAG, 'getRdbStore end');
    try {
      //数据库存储对象创建成功就建表
      if (RDBModel.rdbStore !== undefined) {
        await RDBModel.rdbStore.executeSql(RDBModel.CREATE_TABLE_SQL);
        // 设置分布式表,表名为image
        await RDBModel.rdbStore.setDistributedTables([RDBModel.TABLE_NAME]);
        // 分布式数据库创建为完成
        RDBModel.isCreateDbDone = true
      }
    } catch (e) {
      Logger.error(TAG, 'getRdbStore:' + JSON.stringify(e));
    }
  }

  /**
   * 查询远端数据
   * @returns
   */
  static queryRemoteImage() {
    return new Promise<string[]>(resolve => {
      RDBModel.rdbStore?.remoteQuery(RemoteDeviceModel.getNetWorkId(), RDBModel.TABLE_NAME, RDBModel.predicates,
        ['path'], (err: BusinessError, resultSet: relationalStore.ResultSet) => {
          if (err) {
            Logger.error(TAG, `Failed to remoteQuery data. Code:${err.code},message:${err.message}`);
            return;
          }
          Logger.info(TAG, `ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
          let result = [];
          while (resultSet.goToNextRow()) {
            let path = resultSet.getString(resultSet.getColumnIndex("path"));
            result.push(path);
          }
          Logger.info(TAG, `ResultSet column values length: ${result.length},values:${result.toString()}`);
          resolve(result);
        }
      )
    });
  }

  /**
   * 查询本地数据
   * 用来处理远端设备删除本地文件后,本地数据库未及时修改的问题
   * @returns
   */
  static queryLocalImage() {
    let resultSet: relationalStore.ResultSet = RDBModel.rdbStore.querySync(RDBModel.predicates, ['path']);
    let result = [];
    while (resultSet.goToNextRow()) {
      let path = resultSet.getString(resultSet.getColumnIndex("path"));
      result.push(path);
    }
    return result;
  }

  /**
   * 插入数据
   * @param path
   */
  static insertIntoLocalTable(path: string) {
    try {
      RDBModel.rdbStore.insertSync(RDBModel.TABLE_NAME, { 'path': `${path}` },
        relationalStore.ConflictResolution.ON_CONFLICT_REPLACE);
      Logger.info(TAG, `Insert is successful,`);
    } catch (error) {
      Logger.error(TAG, `Insert is failed, code is ${error.code},message is ${error.message}`);
    }
  }

  /**
   * 删除数据
   * @param path
   */
  static deleteLocalItem(path: string) {
    let predicates = new relationalStore.RdbPredicates(RDBModel.TABLE_NAME);
    predicates.equalTo("path", path);
    try {
      RDBModel.rdbStore.deleteSync(predicates);
      Logger.info(TAG, `Delete rows: ${path}`);
    } catch (err) {
      Logger.error(TAG, `Delete failed, code is ${err.code},message is ${err.message}`);
    }
  }
}

3.2.4、总结

  • 4.1Release的分布式文件获取方式非常简单,直接访问分布式文件夹就可以了,文件自动同步,fs.listFileSync也能及时获取到变更后的文件夹,唯一缺点就是成功率不高。

    但5.0.0Releasefs.listFileSync不能及时获取到变更后的文件夹,所以原来的通过一个函数获取文件列表的方法,变为了两个函数,新增了一个获取远端数据库表里面存储信息的方法。

  • 引入了表也增加了一定的逻辑复杂度,比如远端删除、本地新增,本地删除时都要考虑对表的维护,确保两个应用展示的一致性。

  • relationalStore也有一个数据安全等级标签,注意不要设置高于自身安全等级,这个4.1有提过。

四、总结

分布式设备认证

  • 没有大的逻辑更改,主要是新增了一个获取networkId的方法,用于访问分布式文件时的挂载,和访问远端分布式数据库。如果需要完善,还需要考虑多设备问题,获取指定设备networkId,目前逻辑仅针对两设备相互组网的情况。
  /**
   * 获取可信设备的networkId
   * @returns
   */
  getNetWorkId() {
    let networkId = this.distributedDeviceManager.getAvailableDeviceListSync()[0].networkId;
    return networkId;
  }

分布式相机

  • 为了稳定实现链接对端设备,对相机使用的api进行了回退,目前能够保证稳定性。
  • 同时也新增了一个问题,两个设备调用相机并不能相互挤掉,如果一个设备在使用相机,另一个设备想用时会卡住,而不是像4.1那样挤掉。所以可能需要新增判断。
  • 拍照保存同时维护了一个表

分布式文件

  • 目前只登录分布式账号等待子系统同步稳定性欠佳,改为每个设备维护一个数据库表,每个设备把自己的文件信息维护到这一个表里面,之前的直接查询分布式文件夹改为同时查询分布式文件夹(此时意味着只能查本地的数据)和远端维护的表。将两个数据合并去重(防止分布式子系统自己同步,所以去重出处理)放到前端展示。
  • 拍照时需要把自己的新增的信息插入表里面。
  • 删除时需要把自己的信息从表里面删除(目前不支持远端删除,所以需要对比本地文件是否和表里面的文件不一致,如果不一致则说明远端设备删除了文件,需要找出被删除的表项,并在自己的表里面再次删除,不然远端会出现空白图片)。

五、项目源码

项目源码:distributeddemo: 分布式相机git (gitee.com),注意切换分支到5.0_distributeddemo。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐