鸿蒙应用开发-在Worker中实时修改页面数据(在不同线程中修改主线程中的UI界面)

功能概要说明

提示:同时适用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与宿主进程上下文分离的问题。

  1. 首先在应用的生命周期函数onWindowStageCreate中使用emitter订阅“Setting修改事件”
  2. 然后在worker中修改了Setting后,将修改后的Setting通过emitter进行发布
  3. 当定于的“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)
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全村最肉的人

如果对你有用,就赏博主1分钱!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值