鸿蒙应用开发-在Worker中实时修改页面数据(在不同线程中修改主线程中的UI界面)
文章目录
- 功能概要说明
- 功能说明
- 难点
- 前置条件
- 宿主线程中使用了AppStorageV2来管理应用UI状态
- Worker已经创建好了
- 解决问题
- 思路
- 具体实施
- 封装emitter,让它变得好用一些
- 在应用的生命周期函数onWindowStageCreate中使用emitter订阅“Setting修改事件”;并开启Worker;处理Worker与UI状态的同步。
- 在页面中直接使用Setting Store就可以了
功能概要说明
提示:同时适用OpenHarmony和HarmonyOS;版本5.0.0中正常,其他版本没有试过。
功能说明
我们在做一个OpenHarmony应用程序,其中需要实现一个功能,需要用Worker监听几个固件设备采集的数据,然后将采集来的数据实时展示在界面中。
难点
Worker 有自己独立的执行上下文,它和主线程的上下文是隔离的。这句话就意味着,你在Worker中使用的全局变量、单例类以及类中定义的静态属性都是分开的。
前置条件
提示:以下是目前已经拥有的功能或能力
宿主线程中使用了AppStorageV2来管理应用UI状态
不了解AppStorageV2功能的可以查看官方文档:AppStorageV2: 应用全局UI状态存储
//SettingModel.ets
@ObservedV2
export default class Setting {
@Trace showCompletedTask: boolean=false //设备是否工作完成,默认未完成
}
//store.ets 获取并初始化Setting的工具方法
//以下代码的意思是:全局获取名字为Setting的store,如果存在就直接获取来,不存在就new Setting(),后面的!表示一定能拿到Setting这个store(加上!是为了在界面中应用时报类型错误, Setting|undefined 与 Setting类型不匹配)
export const useSettingStore =
() => AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
//SettingNavD.ets 组件,这是一个简单的显示状态的按钮
import Setting from "../../model/SettingModel";
import { useSettingStore } from "../../store";
@ComponentV2
export struct SettingNavD {
@Local setting: Setting = useSettingStore();
build() {
NavDestination() {
Column() {
Text('设置')
.fontSize(40)
.margin({ bottom: 10 })
Row() {
Text('显示已完成任务');
Toggle({ type: ToggleType.Switch, isOn: this.setting.showCompletedTask })
.onChange((isOn) => {
this.setting.showCompletedTask = isOn;
})
}
}
.alignItems(HorizontalAlign.Start)
}
}
}
以上代码可以在宿主线程中通过 useSettingStore()获取到settingStore,修改settingStore的showCompletedTask值,UI就会跟着变化。
Worker已经创建好了
不了解Worker功能的可以查看官方文档:Worker简介
import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
import { JobTypeEnum } from '../service/utils/constant/LfConstantType';
import { LfJob } from './LfJob';
import { EmitterUtil } from '../views/utils/EmitterUtil';
import Setting from '../views/model/SettingModel';
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
/**
* Defines the event handler to be called when the worker thread receives a message sent by the host thread.
* The event handler is executed in the worker thread.
*
* @param event message data
*/
workerPort.onmessage = (event: MessageEvents) => {
const data: JobTypeEnum = event.data;
try {
let id: number = 0;
switch (data) {
case JobTypeEnum.SYNC_DATA:
const setting = new Setting()
let i = 0;
id = setInterval(() => {
// 这里会通过采集设备数据,然后修改宿主线程的UI状态,假设每五秒修改一次设置
setting.showCompletedTask = !setting.showCompletedTask
EmitterUtil.sendData<Setting>(1, setting)//***********这句就是用来同步Worker与宿主线程中UI状态的代码***********
// 执行任务
console.log('数据同步');
}, 1000 * 5);
break;
case JobTypeEnum.COLL_DATA:
id = setInterval(() => {
// 执行任务
console.log('数据采集');
}, 1000 * 5);
break;
}
if (id !== undefined && id !== null) {
LfJob.addInterval(data, id);
}
} catch (error) {
workerPort.postMessage({ type: 'error', message: error })
}
};
/**
* Defines the event handler to be called when the worker receives a message that cannot be deserialized.
* The event handler is executed in the worker thread.
*
* @param event message data
*/
workerPort.onmessageerror = (event: MessageEvents) => {
};
/**
* Defines the event handler to be called when an exception occurs during worker execution.
* The event handler is executed in the worker thread.
*
* @param event error message
*/
workerPort.onerror = (event: ErrorEvent) => {
};
解决问题
思路
通过使用Emitter进行线程间通信来解决Worker与宿主进程上下文分离的问题。
- 首先在应用的生命周期函数onWindowStageCreate中使用emitter订阅“Setting修改事件”
- 然后在worker中修改了Setting后,将修改后的Setting通过emitter进行发布
- 当定于的“Setting修改事件”触发后,将得到的新的Setting数据同步给AppStorageV2中的Setting Store,所有使用到Setting Store页面就会跟着变化了。
具体实施
封装emitter,让它变得好用一些
import emitter from '@ohos.events.emitter';
export class EmitterUtil {
static sendData<T extends object>(eventId: number, data: T) {
const eventData: emitter.EventData = {
data
}
let innerEvent: emitter.InnerEvent = {
eventId,
priority: emitter.EventPriority.HIGH
};
emitter.emit(innerEvent, eventData)
}
static receiveData<T>(eventId: number, callback: (data: T) => void) {
let innerEvent: emitter.InnerEvent = {
eventId,
};
emitter.on(innerEvent, (eventData: emitter.EventData) => {
const data = eventData.data as T
callback(data)
})
}
}
在应用的生命周期函数onWindowStageCreate中使用emitter订阅“Setting修改事件”;并开启Worker;处理Worker与UI状态的同步。
onWindowStageCreate(windowStage: window.WindowStage): void {
// 订阅Setting变化的监听器
EmitterUtil.receiveData<Setting>(1, (setting) => {
//******* 同步从worker中获取到的setting,给全局Setting Store ********
const settingStore = useSettingStore()
settingStore.showCompletedTask = setting.showCompletedTask
})
//初始化Worker任务
if (LfConstant.execJobs.length > 0) {
for (let job of LfConstant.execJobs) {
LfJob.getWorker().postMessage(job);
}
}
// 指定主页面入口
windowStage.loadContent('views/pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}
在页面中直接使用Setting Store就可以了
import { useSettingStore } from "../../store";
@ComponentV2
export struct SettingNavD {
@Local setting: Setting = useSettingStore();
build() {
NavDestination() {
Column() {
Text('设置')
.fontSize(40)
.margin({ bottom: 10 })
Row() {
Text('显示已完成任务');
Toggle({ type: ToggleType.Switch, isOn: this.setting.showCompletedTask })
.onChange((isOn) => {
this.setting.showCompletedTask = isOn;
})
}
}
.alignItems(HorizontalAlign.Start)
}
}
}