鸿蒙中是如何实现UI自动刷新的?
本文作者
作者:Pika
链接:
https://juejin.cn/post/7380357384776073266
本文由作者授权发布。
值得注意的是,本章会涉及到api 9以上的内容,比如api 11,这些内容虽然在华为官网还没有暴露给普通开发者,但是我们可以通过open harmony docs 中提取查看这些内容,比如@Track 装饰器。因此如果大家想要提前了解鸿蒙Next的api,即便公司没有和华为签约,我们也还是能够通过open harmony docs去查看更高版本的内容。
https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/arkts-track.md
了解常见的状态管理,比如@State装饰器 是如何进行状态刷新。 鸿蒙api 9 class全量刷新导致的问题以及ArkUI后续优化的实现原理,比如@Track 装饰器。 了解到属性刷新驱动UI刷新的过程。
我们拿一个最简单的例子介绍一下状态管理,我们定义一个StateCase的Component,其中有一个button,当点击button的时候就会改变当前的ui展示,这里面的ui数据通过showRow这边变量管理。
@Component
struct StateCase{
@State showRow:TestTrack = new TestTrack()
build(){
Row(){
if (this.showRow.param1){
Row(){
Text("i am row")
}
}else{
Column(){
Text("i am colomn")
}
}
Button("点我").onClick(()=>{
this.showRow.param1 = !this.showRow.param1
})
}
}
}
class TestTrack{
param1:boolean = true
}
class StateCase extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1) {
super(parent, __localStorage, elmtId);
this.__showRow = new ObservedPropertyObjectPU(new TestTrack(), this, "showRow");
this.setInitiallyProvidedValue(params);
}
setInitiallyProvidedValue(params) {
if (params.showRow !== undefined) {
this.showRow = params.showRow;
}
}
__showRow 的get方法
get () {
return this.__showRow.get();
}
__showRow的set方法
set showRow(newValue) {
this.__showRow.set(newValue);
}
this.observeComponentCreation((elmtId, isInitialRender) => {
....
Button.onClick(() => {
this.showRow.param1 = !this.showRow.param1;
});
....
});
响应式,其实本质上都是通过回调的思想实现的,ArkTS中在内部把这些回调的细节统统隐藏了,因此开发者们可以在不用关心这些细节的基础上,就很容易的实现UI的刷新。下面我们就来看,为什么ArkTS要千辛万苦的把我们普通声明的状态变量变成一个ObservedPropertyObjectPU对象。
// class definitions for backward compatibility
class ObservedPropertyObjectPU<T> extends ObservedPropertyPU<T> {
}
ObservedPropertyPU set方法
Button("点我").onClick(()=>{
this.showRow.param1 = !this.showRow.param1
})
ObservedPropertyPU 类中
set方法
public set(newValue: T): void {
如果两者是同一个变量,用=== 判断,则直接return不进行刷新,本次是无效刷新
if (this.wrappedValue_ === newValue) {
stateMgmtConsole.debug(`ObservedPropertyObjectPU[${this.id__()}, '${this.info() || "unknown"}']: set with unchanged value - ignoring.`);
return;
}
stateMgmtConsole.propertyAccess(`${this.debugInfo()}: set: value about to changed.`);
把旧的,也就是上一个值用oldValue变量记录,方便后续进行UI刷新的判断。
const oldValue = this.wrappedValue_;
setValueInternal方法中会把this.wrappedValue_ 更新为newValue
if (this.setValueInternal(newValue)) {
TrackedObject.notifyObjectValueAssignment(/* old value */ oldValue, /* new value */ this.wrappedValue_,
这里触发了UI刷新,在鸿蒙api 9 的版本只会走notifyPropertyHasChangedPU里面的内容刷新,这里大家可以思考一下
this.notifyPropertyHasChangedPU.bind(this),
this.notifyTrackedObjectPropertyHasChanged.bind(this));
}
}
状态复制管理
private setValueInternal(newValue: T): boolean {
stateMgmtProfiler.begin("ObservedPropertyPU.setValueInternal");
if (newValue === this.wrappedValue_) {
stateMgmtConsole.debug(`ObservedPropertyObjectPU[${this.id__()}, '${this.info() || "unknown"}'] newValue unchanged`);
stateMgmtProfiler.end();
return false;
}
if (!this.checkIsSupportedValue(newValue)) {
stateMgmtProfiler.end();
return false;
}
// 解除旧的绑定
this.unsubscribeWrappedObject();
if (!newValue || typeof newValue !== 'object') {
// undefined, null, simple type:
// nothing to subscribe to in case of new value undefined || null || simple type
this.wrappedValue_ = newValue;
} else if (newValue instanceof SubscribableAbstract) {
stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an SubscribableAbstract, subscribing to it.`);
this.wrappedValue_ = newValue;
(this.wrappedValue_ as unknown as SubscribableAbstract).addOwningProperty(this);
} else if (ObservedObject.IsObservedObject(newValue)) {
stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an ObservedObject already`);
ObservedObject.addOwningProperty(newValue, this);
this.shouldInstallTrackedObjectReadCb = TrackedObject.needsPropertyReadCb(newValue);
this.wrappedValue_ = newValue;
} else {
stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an Object, needs to be wrapped in an ObservedObject.`);
this.wrappedValue_ = ObservedObject.createNew(newValue, this);
this.shouldInstallTrackedObjectReadCb = TrackedObject.needsPropertyReadCb(this.wrappedValue_);
}
stateMgmtProfiler.end();
return true;
}
进行内部状态值更新,并设置回调。 绑定回调方,比如当属性发生通知的时候,通过回调告诉回调方。 把UI设置为脏处理,应用于后面UI的刷新流程。
private unsubscribeWrappedObject() {
if (this.wrappedValue_) {
if (this.wrappedValue_ instanceof SubscribableAbstract) {
(this.wrappedValue_ as SubscribableAbstract).removeOwningProperty(this);
} else {
ObservedObject.removeOwningProperty(this.wrappedValue_, this);
// make sure the ObservedObject no longer has a read callback function
// assigned to it
ObservedObject.unregisterPropertyReadCb(this.wrappedValue_);
}
}
}
abstract class ViewPU extends NativeViewPartialUpdate
implements IViewPropertiesChangeSubscriber
interface IViewPropertiesChangeSubscriber extends IPropertySubscriber {
// ViewPU get informed when View variable has changed
// informs the elmtIds that need update upon variable change
viewPropertyHasChanged(varName: PropertyInfo, dependentElmtIds: Set<number>): void ;
}
if (this.setValueInternal(newValue)) {
TrackedObject.notifyObjectValueAssignment(/* old value */ oldValue, /* new value */ this.wrappedValue_,
this.notifyPropertyHasChangedPU.bind(this),
this.notifyTrackedObjectPropertyHasChanged.bind(this));
}
ArkUI的状态管理优化之路
@Component
struct StateCase{
@State showRow:TestTrack = new TestTrack()
build(){
Row(){
if (this.showRow.param1){
Row(){
Text("i am row")
}
}else{
Column(){
Text("i am colomn")
}
}
// 冗余渲染,因为param2没有改变但是也会随着button点击发生重建
Text(this.showRow.param2?"我是Text":"").width(this.param2Text())
Button("点我").onClick(()=>{
this.showRow.param1 = !this.showRow.param1
})
}
}
发生渲染时
param2Text(){
console.log("发生了渲染")
return 100
}
}
class TestTrack{
param1:boolean = true
param2:boolean = true
}
TrackedObject.notifyObjectValueAssignment(/* old value */ oldValue, /* new value */ this.wrappedValue_,
this.notifyPropertyHasChangedPU.bind(this),
this.notifyTrackedObjectPropertyHasChanged.bind(this));
}
public static notifyObjectValueAssignment(obj1: Object, obj2: Object,
notifyPropertyChanged: () => void, // notify as assignment (none-optimised)
notifyTrackedPropertyChange: (propName) => void): boolean {
// 默认处理,依赖class的属性控件都调用notifyPropertyChanged
if (!obj1 || !obj2 || (typeof obj1 !== 'object') || (typeof obj2 !== 'object') ||
(obj1.constructor !== obj2.constructor) ||
TrackedObject.isCompatibilityMode(obj1)) {
stateMgmtConsole.debug(`TrackedObject.notifyObjectValueAssignment notifying change as assignment (non-optimised)`)
notifyPropertyChanged();
return false;
}
// 有@Track 装饰器 处理,通过属性变量查找到对应的属性,然后只刷新依赖属性的UI
stateMgmtConsole.debug(`TrackedObject.notifyObjectValueAssignment notifying actually changed properties (optimised)`)
const obj1Raw = ObservedObject.GetRawObject(obj1);
const obj2Raw = ObservedObject.GetRawObject(obj2);
let shouldFakePropPropertyBeNotified: boolean = false;
Object.keys(obj2Raw)
.forEach(propName => {
// Collect only @Track'ed changed properties
if (Reflect.has(obj1Raw, `${TrackedObject.___TRACKED_PREFIX}${propName}`) &&
(Reflect.get(obj1Raw, propName) !== Reflect.get(obj2Raw, propName))) {
stateMgmtConsole.debug(` ... '@Track ${propName}' value changed - notifying`);
notifyTrackedPropertyChange(propName);
shouldFakePropPropertyBeNotified = true;
} else {
stateMgmtConsole.debug(` ... '${propName}' value unchanged or not @Track'ed - not notifying`);
}
});
}
public static isCompatibilityMode(obj: Object): boolean {
return !obj || (typeof obj !== "object") || !Reflect.has(obj, TrackedObject.___IS_TRACKED_OPTIMISED);
}
protected notifyPropertyHasChangedPU() {
stateMgmtProfiler.begin("ObservedPropertyAbstractPU.notifyPropertyHasChangedPU");
stateMgmtConsole.debug(`${this.debugInfo()}: notifyPropertyHasChangedPU.`)
if (this.owningView_) {
if (this.delayedNotification_ == ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay) {
// send viewPropertyHasChanged right away
this.owningView_.viewPropertyHasChanged(this.info_, this.dependentElmtIdsByProperty_.getAllPropertyDependencies());
} else {
// mark this @StorageLink/Prop or @LocalStorageLink/Prop variable has having changed and notification of viewPropertyHasChanged delivery pending
this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending;
}
}
this.subscriberRefs_.forEach((subscriber) => {
if (subscriber) {
if ('syncPeerHasChanged' in subscriber) {
(subscriber as unknown as PeerChangeEventReceiverPU<T>).syncPeerHasChanged(this);
} else {
stateMgmtConsole.warn(`${this.debugInfo()}: notifyPropertyHasChangedPU: unknown subscriber ID 'subscribedId' error!`);
}
}
});
stateMgmtProfiler.end();
}
viewPropertyHasChanged 这里终于来到我们之前说过的UI渲染逻辑,它会在内部调用markNeedUpdate方法把当前UI节点设置为脏状态,同时如果有@Watch 装饰器修饰的方法,在这个时候也会被回调。@Watch 装饰器也是api9 以上新增的方法,用于监听某个属性刷新然后触发方法调用。
https://juejin.cn/post/7349722583158521882
viewPropertyHasChanged(varName: PropertyInfo, dependentElmtIds: Set<number>): void {
stateMgmtProfiler.begin("ViewPU.viewPropertyHasChanged");
stateMgmtTrace.scopedTrace(() => {
if (this.isRenderInProgress) {
stateMgmtConsole.applicationError(`${this.debugInfo__()}: State variable '${varName}' has changed during render! It's illegal to change @Component state while build (initial render or re-render) is on-going. Application error!`);
}
this.syncInstanceId();
if (dependentElmtIds.size && !this.isFirstRender()) {
if (!this.dirtDescendantElementIds_.size && !this.runReuse_) {
// mark ComposedElement dirty when first elmtIds are added
// do not need to do this every time
进行标记,进入UI刷新的流程
this.markNeedUpdate();
}
stateMgmtConsole.debug(`${this.debugInfo__()}: viewPropertyHasChanged property: elmtIds that need re-render due to state variable change: ${this.debugInfoElmtIds(Array.from(dependentElmtIds))} .`)
for (const elmtId of dependentElmtIds) {
if (this.hasRecycleManager()) {
this.dirtDescendantElementIds_.add(this.recycleManager_.proxyNodeId(elmtId));
} else {
this.dirtDescendantElementIds_.add(elmtId);
}
}
stateMgmtConsole.debug(` ... updated full list of elmtIds that need re-render [${this.debugInfoElmtIds(Array.from(this.dirtDescendantElementIds_))}].`)
} else {
stateMgmtConsole.debug(`${this.debugInfo__()}: viewPropertyHasChanged: state variable change adds no elmtIds for re-render`);
stateMgmtConsole.debug(` ... unchanged full list of elmtIds that need re-render [${this.debugInfoElmtIds(Array.from(this.dirtDescendantElementIds_))}].`)
}
回调@Watch 装饰器修饰方法
let cb = this.watchedProps.get(varName)
if (cb) {
stateMgmtConsole.debug(` ... calling @Watch function`);
cb.call(this, varName);
}
this.restoreInstanceId();
}, "ViewPU.viewPropertyHasChanged", this.constructor.name, varName, dependentElmtIds.size);
stateMgmtProfiler.end();
}
protected notifyTrackedObjectPropertyHasChanged(changedPropertyName : string) : void {
stateMgmtProfiler.begin("ObservedPropertyAbstract.notifyTrackedObjectPropertyHasChanged");
stateMgmtConsole.debug(`${this.debugInfo()}: notifyTrackedObjectPropertyHasChanged.`)
if (this.owningView_) {
if (this.delayedNotification_ == ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay) {
// send viewPropertyHasChanged right away
this.owningView_.viewPropertyHasChanged(this.info_, this.dependentElmtIdsByProperty_.getTrackedObjectPropertyDependencies(changedPropertyName, "notifyTrackedObjectPropertyHasChanged"));
} else {
// mark this @StorageLink/Prop or @LocalStorageLink/Prop variable has having changed and notification of viewPropertyHasChanged delivery pending
this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending;
}
}
this.subscriberRefs_.forEach((subscriber) => {
if (subscriber) {
if ('syncPeerTrackedPropertyHasChanged' in subscriber) {
(subscriber as unknown as PeerChangeEventReceiverPU<T>).syncPeerTrackedPropertyHasChanged(this, changedPropertyName);
} else {
stateMgmtConsole.warn(`${this.debugInfo()}: notifyTrackedObjectPropertyHasChanged: unknown subscriber ID 'subscribedId' error!`);
}
}
});
stateMgmtProfiler.end();
}
回到上面的例子,如果param2不需要被param1刷新,我们只需要使用@Track装饰器标记param1即可,因此后续变化只会追踪param1的变化。更多例子可以观看这里 @Track。
https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/arkts-track.md
class TestTrack{
@Track param1:boolean = true
param2:boolean = true
}
ObservedPropertyPU get方法
public get(): T {
stateMgmtProfiler.begin("ObservedPropertyPU.get");
stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get`);
this.recordPropertyDependentUpdate();
if (this.shouldInstallTrackedObjectReadCb) {
stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get: @Track optimised mode. Will install read cb func if value is an object`);
ObservedObject.registerPropertyReadCb(this.wrappedValue_, this.onOptimisedObjectPropertyRead.bind(this));
} else {
stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get: compatibility mode. `);
}
stateMgmtProfiler.end();
return this.wrappedValue_;
}
this.observeComponentCreation((elmtId, isInitialRender) => {
...
if (this.showRow) {
this.ifElseBranchUpdateFunction(0, () => {
this.observeComponentCreation((elmtId, isInitialRender) => {
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!