【HarmonyOS - UIAbility组件和UI的数据同步】

简述

基于HarmonyOS的应用模型,可以通过以下几种方式来实现UIAbility组件与UI之间的数据同步。

  • 使用EventHub进行数据通信:基于发布订阅模式来实现,事件需要先订阅后发布,订阅者收到消息后进行处理。
  • 使用globalThis进行数据同步:ArkTS引擎实例内部的一个全局对象,在ArkTS引擎实例内部都能访问。
  • 使用AppStorage/LocalStorage进行数据同步:ArkUI提供了AppStorage和LocalStorage两种应用级别的状态管理方案,可用于实现应用级别和UIAbility级别的数据同步。

看上去第一眼可能会有人觉得这和状态管理是一样的,都是对于数据进行管理,而且第三点的AppStorage/LocalStorage就是状态管理方案,那为什么这里还要再单独介绍呢?

我个人看来官网将状态管理和UIAbility组件和UI的数据同步拆成两部分介绍,可能是因为在状态管理中主要是对状态进行存取操作,而在UIAbility组件和UI的数据同步部分着重介绍了EventHub和globalThis这两个API,主要是对数据的操作修改,所以还是有一丢丢差别。

下面将着重介绍EventHub和globalThis两部分,至于AppStorage/LocalStorage由于和状态管理重合,所以这里不再赘述,感兴趣的同学可以查看这篇文章【HarmonyOS - ArkTS - 状态管理

使用EventHub进行数据通信

看命名,估计了解Vue的同学立马会想到事件总线EventBus。其实两者不管是命名还是功能都是比较相似的。EventHub采用了发布订阅的模式赋予了UIAbility、ExtensionAbility组件级别的事件机制,以UIAbility、ExtensionAbility为中心提供了对事件的订阅、取消订阅、触发订阅的功能。

订阅、取消订阅、触发订阅就是对于EventHub.on、EventHub.off、EventHub.emit这三个API,下面会详细介绍。

在使用中也和Vue类似,通过on来订阅事件、emit来触发事件、处理完成之后通过off来取消订阅。

PS:下面所有例子都是基于UIAbility来进行讨论的,ExtensionAbility类似。

Context上下文

在了解EventHub之前,我们先要了解一下在UIAbility和Page中如何获取context上下文,因为EventHub就是绑定在context上下文上的,必须通过context来进行调用。

在前面的学习中我们知道一个AbilityStage会有一个基类Context,然后其上层的UIAbility、ExtensionAbility会基于基类Context来生成一个具有自己能力的UIAbilityContext。

感兴趣的同学可以查看这篇文章的第一部分,介绍了Stage应用模型的一些基本概念。【HarmonyOS-Stage应用模型-UIAbility生命周期

在UIAbility中获取Context

由于EventHub是以UIABblity为中心,所以在UIAbility的文件中(Ability.ts文件)可以直接通过this访问,下面是在生命周期onCreate中示例:下面代码表示在UiAbility处于前台时会订阅一个event1的事件然后调用func1.

javascript 复制代码
import UIAbility from '@ohos.app.ability.UIAbility';

const TAG: string = '[Example].[Entry].[EntryAbility]';

export default class EntryAbility extends UIAbility {
    func1(...data) {
        // 触发事件,完成相应的业务操作
        console.info(TAG, '1. ' + JSON.stringify(data));
    }

    onCreate(want, launch) {
        // 获取eventHub
        let eventhub = this.context.eventHub;
        // 执行订阅操作
        eventhub.on('event1', this.func1);
    }
}
在Page内部获取Context

在UI界面Page组件中需要使用提供的API - getContext(this)的方式来获取context,然后使用common类来进行类型规范。

javascript 复制代码
import common from '@ohos.app.ability.common';

@Entry
@Component
struct Index {
  // 获取context 
  private context = getContext(this) as common.UIAbilityContext;

  // 页面展示
  build() {
    // ...
  }
}

EventHub的使用

下面主要是在应用启动时,在UiAbility的生命周期中进行事件的订阅,然后在UI界面中进行事件触发,最后在使用完成的时候进行取消订阅。

EventHub.on 订阅

on(event: string, callback: Function): void;

参数名 类型 必填 说明
event string 订阅的事件名称
callback Function 触发事件时,执行的回调

这里简单记录一下在csdn中插入表格时,主要是注意第二行md原生语法 |:- | :-: | - | -: | 分别代表 居左、居中、居中、居右,注意要英文符号

下面伪代码主要是在onCreate生命周期中订阅了event1事件,当触发时会执行func1和匿名箭头函数。

javascript 复制代码
import UIAbility from '@ohos.app.ability.UIAbility';

const TAG: string = '[Example].[Entry].[EntryAbility]';

export default class EntryAbility extends UIAbility {
    func1(...data) {
        // 触发事件,完成相应的业务操作
        console.info(TAG, '1. ' + JSON.stringify(data));
    }

    onCreate(want, launch) {
        // 获取eventHub
        let eventhub = this.context.eventHub;
        // 执行订阅操作
        eventhub.on('event1', this.func1);
        eventhub.on('event1', (...data) => {
            // 触发事件,完成相应的业务操作
            console.info(TAG, '2. ' + JSON.stringify(data));
        });
    }
}

上面的console.info(TAG)是ArkTS提供的用于日志打印的库,便于问题定位排查

EventHub.emit触发订阅

emit(event: string, ...args: Object[]): void;

参数名 类型 必填 说明
event string 触发订阅的事件名称
...args Object[] 可变参数,事件触发时,传递给on订阅回调函数的参数。

在UI界面中通过eventHub.emit()方法触发该事件,在触发事件的同时,根据需要传入参数信息。

javascript 复制代码
import common from '@ohos.app.ability.common';

@Entry
@Component
struct Index {
  private context = getContext(this) as common.UIAbilityContext;

  eventHubFunc() {
    // 不带参数触发自定义"event1"事件
    this.context.eventHub.emit('event1');
    // 带1个参数触发自定义"event1"事件
    this.context.eventHub.emit('event1', 1);
    // 带2个参数触发自定义"event1"事件
    this.context.eventHub.emit('event1', 2, 'test');
    // 开发者可以根据实际的业务场景设计事件传递的参数
  }

  // 页面展示
  build() {
    // ...
  }
}
EventHub.off

off(event: string, callback?: Function): void;

参数名 类型 必填 说明
event string 取消订阅的事件名称
callback Function 取消指定callback事件回调。如果不传callback,则取消订阅该事件下所有callback。

取消订阅指定事件。当callback传值时,取消订阅指定的callback;未传值时,取消订阅该事件下所有callback。

javascript 复制代码
import Ability from '@ohos.app.ability.UIAbility';

export default class MainAbility extends Ability {
    onForeground() {
        this.context.eventHub.on('event1', this.func1);
        this.context.eventHub.off('event1', this.func1); //取消订阅func1
        this.context.eventHub.on('event2', this.func1);
        this.context.eventHub.on('event2', this.func2);
        this.context.eventHub.off('event2');  //取消订阅func1和func2
    }
    func1() {
        console.log('func1 is called');
    }
    func2() {
        console.log('func2 is called');
    }
}

使用globalThis进行数据同步

globalThis是挂载在全局ArkTS引擎实例内部上的一个全局对象,引擎内部UIAbility、ExtensionAbility、Page都可以使用该对象,由于Stage应用模型是基于一个ArkTS引擎实例来运行的,所以可以使用globalThis来作为全局对象进行数据同步。

如上图所示,下面从这三个方面来具体介绍globalThis的使用:

  • UIAbility和Page之间使用globalThis
  • UIAbility和UIAbility之间使用globalThis
  • globalThis使用的注意事项

UIAbility和Page之间使用globalThis

globalThis为ArkTS引擎实例下的全局对象,可以通过globalThis绑定属性/方法来进行UIAbility组件与UI的数据同步。例如在UIAbility组件中绑定want参数,即可在UIAbility对应的Page界面上使用want参数信息。

下面的例子是在UIAbility上通过onCreate生命周期在global上挂载了want参数,而在该UIAbility中的Page页面中通过global就可以访问want参数。

1、调用startAbility()方法启动一个UIAbility实例时,被启动的UIAbility创建完成后会进入onCreate()生命周期回调,且在onCreate()生命周期回调中能够接受到传递过来的want参数,可以将want参数绑定到globalThis上。

javascript 复制代码
import UIAbility from '@ohos.app.ability.UIAbility'

export default class EntryAbility extends UIAbility {
    onCreate(want, launch) {
    	// 将传入的want参数挂载到global上
        globalThis.entryAbilityWant = want;
        // ...
    }

    // ...
}

2、在Page界面中即可通过globalThis获取到want参数信息。

javascript 复制代码
let entryAbilityWant;

@Entry
@Component
struct Index {
  aboutToAppear() {
  	// 获取到UIAbility传入的want参数 
    entryAbilityWant = globalThis.entryAbilityWant;
  }

  // 页面展示
  build() {
    // ...
  }
}

UIAbility和UIAbility之间使用globalThis

同一个应用中UIAbility和UIAbility之间的数据传递,可以通过将数据绑定到全局变量globalThis上进行同步,如在AbilityA中将数据保存在globalThis,然后跳转到AbilityB中取得该数据:

1、AbilityA中保存数据一个字符串数据并挂载到globalThis上。

javascript 复制代码
import UIAbility from '@ohos.app.ability.UIAbility'

export default class AbilityA extends UIAbility {
    onCreate(want, launch) {
        globalThis.entryAbilityStr = 'AbilityA'; // AbilityA存放字符串"AbilityA"到globalThis
        // ...
    }
}

2、AbilityB中获取对应的数据。

javascript 复制代码
import UIAbility from '@ohos.app.ability.UIAbility'

export default class AbilityB extends UIAbility {
    onCreate(want, launch) {
        // AbilityB从globalThis读取name并输出
        console.info('name from entryAbilityStr: ' + globalThis.entryAbilityStr);
        // ...
    }
}

globalThis使用的注意事项

由上图能看出:

  • Stage模型下进程内的UIAbility组件共享ArkTS引擎实例,使用globalThis时需要避免存放相同名称的对象。例如AbilityA和AbilityB可以使用globalThis共享数据,在存放相同名称的对象时,先存放的对象会被后存放的对象覆盖。
  • FA模型因为每个UIAbility组件之间引擎隔离,不会存在该问题。
  • 对于绑定在globalThis上的对象,其生命周期与ArkTS虚拟机实例相同,建议在使用完成之后将其赋值为null,以减少对应用内存的占用。

Stage模型上同名对象覆盖导致问题的场景举例说明

1、在AbilityA文件中使用globalThis存放了UIAbilityContext。

javascript 复制代码
import UIAbility from '@ohos.app.ability.UIAbility'

export default class AbilityA extends UIAbility {
    onCreate(want, launch) {
        globalThis.context = this.context; // AbilityA存放context到globalThis
        // ...
    }
}

2、在AbilityA的页面中获取该UIAbilityContext并进行使用。使用完成后将AbilityA实例切换至后台。

javascript 复制代码
@Entry
@Component
struct Index {
  onPageShow() {
    let ctx = globalThis.context; // 页面中从globalThis中取出context并使用
    let permissions = ['com.example.permission']
    ctx.requestPermissionsFromUser(permissions,(result) => {
       // ...
    });
  }
  // 页面展示
  build() {
    // ...
  }
}

3、在AbilityB文件中使用globalThis存放了UIAbilityContext,并且命名为相同的名称。

javascript 复制代码
import UIAbility from '@ohos.app.ability.UIAbility'

export default class AbilityB extends UIAbility {
    onCreate(want, launch) {
        // AbilityB覆盖了AbilityA在globalThis中存放的context
        globalThis.context = this.context;
        // ...
    }
}

4、在AbilityB的页面中获取该UIAbilityContext并进行使用。此时获取到的globalThis.context已经表示为AbilityB中赋值的UIAbilityContext内容。

javascript 复制代码
@Entry
@Component
struct Index {
  onPageShow() {
    let ctx = globalThis.context; // Page中从globalThis中取出context并使用
    let permissions = ['com.example.permission']
    ctx.requestPermissionsFromUser(permissions,(result) => {
      console.info('requestPermissionsFromUser result:' + JSON.stringify(result));
    });
  }
  // 页面展示
  build() {
    // ...
  }
}

5、在AbilityB实例切换至后台,将AbilityA实例从后台切换回到前台。此时AbilityA的onCreate生命周期不会再次进入。

javascript 复制代码
import UIAbility from '@ohos.app.ability.UIAbility'

export default class AbilityA extends UIAbility {
    onCreate(want, launch) { // AbilityA从后台进入前台,不会再走这个生命周期
        globalThis.context = this.context;
        // ...
    }
}

6、在AbilityA的页面再次回到前台时,其获取到的globalThis.context表示的为AbilityB的UIAbilityContext,而不是AbilityA的UIAbilityContext,在AbilityA的页面中使用则会出错。

javascript 复制代码
@Entry
@Component
struct Index {
  onPageShow() {
    let ctx = globalThis.context; // 这时候globalThis中的context是AbilityB的context
    let permissions=['com.example.permission'];
    ctx.requestPermissionsFromUser(permissions,(result) => { // 使用这个对象就会导致进程崩溃
       console.info('requestPermissionsFromUser result:' + JSON.stringify(result));
    });
  }
  // 页面展示
  build() {
    // ...
  }
}

总结上面的问题就是Stage模型下由于共享ArkTS引擎实例,有可能存在相同命名下的覆盖问题,而FA模型是独享ArkTS引擎实例,所以在Stage模型下需要注意命名重复。

相关推荐
前端菜鸟日常3 小时前
鸿蒙版(ArkTs) 贪吃蛇,包含无敌模式 最高分 暂停和继续功能
华为·harmonyos
橙色小博4 小时前
PyTorch中的各种损失函数的详细解析与通俗理解!
人工智能·pytorch·python·深度学习·神经网络·机器学习
James. 常德 student7 小时前
多GPU训练
人工智能·pytorch·深度学习
Y1nhl7 小时前
搜广推校招面经六十六
pytorch·python·深度学习·机器学习·广告算法·推荐算法·搜索算法
鸿蒙布道师8 小时前
鸿蒙NEXT开发数值工具类(TS)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
benben04410 小时前
Unity3D仿星露谷物语开发34之单击Drop项目
游戏·ui·unity·游戏引擎
weixin_4452381210 小时前
Pytorch|RNN-心脏病预测
人工智能·pytorch·rnn
Cl_rown去掉l变成C12 小时前
第P10周:Pytorch实现车牌识别
人工智能·pytorch·python
塞尔维亚大汉13 小时前
鸿蒙南向开发 ——轻量系统芯片移植指南(二)
物联网·嵌入式·harmonyos
AIGC_ZY13 小时前
PyTorch 实现图像版多头注意力(Multi-Head Attention)和自注意力(Self-Attention)
人工智能·pytorch·python