基于OpenHarmony 5.0.0Release的分布式相机分布式图库实现
一、环境配置 平台硬件: RK3568OS版本:OpenHarmony 5.0 Release 32bit(5.0.0.71)sdk:API12(full sdk)DevEco:5.0.3.910(DevEco Studio 5.0.0 Release) 二、
一、环境配置
- 平台硬件: 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。
更多推荐
所有评论(0)