1 关键字

system_ui;状态栏;下拉背景;

2 问题描述

设备型号:dayu200

系统版本:OpenHarmony 3.1 Release

代码版本:OpenHarmony-v3.1-Release

问题现象:顶部状态栏下拉时,下拉的页面有个背景图遮住了屏幕主页面,多次下拉每次的背景图都会产生变化,最终会导致屏幕闪烁。

3 问题原因

3.1 正常机制

每次下拉顶部状态栏,下拉界面的背景图应该保持一致。

3.2 异常机制

顶部状态栏多次下拉时,下拉背景图会变化。

4 解决方案

  1. 创建新的@State showBackground数据作为背景显示的判断依据

//\applications_systemui\product\phone\dropdownpanel\src\main\ets\pages\index.ets
@Entry
@Component
struct Index {
    ...
    @State mBackground: PixelMap | undefined = undefined;
    // 在mBackground下方定义boolean类型数据showBackground,默认值给false
    @State showBackground: boolean = false;
    ...
}
  1. 在hideSelf方法中修改 showBackground 状态

// \applications_systemui\product\phone\dropdownpanel\src\main\ets\pages\index.ets
...
hideSelf() {
    Log.showInfo(TAG, `hideSelf`)
    this._animateTo({ ...SHOW_ANIM_CONFIG, onFinish: () => {
        Log.showInfo(TAG, `hideSelf, hide anim finish.`);
        this.showComponentName = undefined
        this.mBackground && this.mBackground.release();
        this.mBackground = undefined;
        // 在页面隐藏并清除背景图片时将showBackground设置为false
        this.showBackground = false;
        WindowManager.hideWindow(WindowType.DROPDOWN_PANEL)
    }}, () => {
        this.componentOptAreaTranslateY = (-this.componentOptAreaHeightPX * 0.1) + 'px';
        this.backgroundOpacity = 0;
    })
}
​
  1. 在updateBackground方法中修改 showBackground 状态

// \applications_systemui\product\phone\dropdownpanel\src\main\ets\pages\index.ets
...
updateBackground() {
    let rect = WindowManager.getWindowInfo(WindowType.DROPDOWN_PANEL)?.rect;
    Log.showInfo(TAG, "start get snapShot, rect:" + JSON.stringify(rect));
    screenshot.save({ screenRect: rect }).then((snapImage) => {
        Log.showInfo(TAG, "get snap: + JSON.stringify(snapImage)")
        this.mBackground = snapImage;
        // 在更新背景图片时,将showBackground设置为true
        this.showBackground = true;
    })
}
  1. 在build中修改背景图片判断的逻辑

// \applications_systemui\product\phone\dropdownpanel\src\main\ets\pages\index.ets
...
// 将显示背景图片的判断依据由mBackground改为showBackground
if (this.showBackground) {
    Image(this.mBackground)
        .width('100%')
        .height('100%')
        .objectFit(ImageFit.Fill)
        .blur(20)
        .opacity(this.backgroundOpacity)
}

5 定位过程

  1. 分析背景图片mBackground是在何时生成,在updateBackground方法中,通过screenshot.save方法设置了mBackground。

updateBackground() {
    let rect = WindowManager.getWindowInfo(WindowType.DROPDOWN_PANEL) ? .rect;
    Log.showInfo(TAG, "start get snapShot, rect:"+JSON.stringify(rect));
    // screenshot.save方法是对指定的窗口区域尺寸截图,由此判断出背景图片是主界面的截图。
    screenshot.save({ screenRect: rect }).then((snapImage) => {         
        Log.showInfo(TAG, "get snap:"+ JSON.stringify(snapImage))
        // 设置背景图片
        this.mBackground = snapImage
    })
}
  1. 通过hideSelf方法可以判断出,页面隐藏后将mBackground设置为undefined。

hideSelf() {
    Log.showInfo(TAG, "hideSelf")
    this._animateTo({...SHOW_ANIM_CONFIG,
        onFinish: () => {
            Log.showInfo(TAG, "hideSelf, hide anim finish.");
            this.showComponentName = undefined
            this.mBackground && this.mBackground.release();
            // 将背景图片设置为undefined
            this.mBackground = undefined;
            WindowManager.hideWindow(WindowType.DROPDOWN_PANEL)
        }
    }, () => {
        this.componentOptAreaTranslateY = (-this.componentOptAreaHeightPX * 0.1) + 'px';
        this.backgroundOpacity = 0
    })
}
  1. 按照以上逻辑,在下拉界面时 mBackground 为 undefined, 然后下拉时通过 screenshot.save 截图功能将主界面的截图作为下拉界面的背景图,下拉页面收起隐藏的时,将 mBackground 设置为undefined 清除背景图。但每次下拉的背景不一样,初步判断为截图时出了问题,因此查看每次下拉时updateBackground的日志。

08-05 11:01:02.515 1382-1382/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> start get snapShot, rect: {"left":0,"top":0,"width":720,"height":1208}
08-05 11:01:02.779 1382-1382/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> get snap: {"_napiwrapper":{}}
08-05 11:01:04.577 1382-1382/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> start get snapShot, rect: {"left":0,"top":0,"width":720,"height":1208}
08-05 11:01:04.819 1382-1382/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> get snap: {"_napiwrapper":{}}
08-05 11:01:06.404 1382-1382/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> start get snapShot, rect: {"left":0,"top":0,"width":720,"height":1208}
08-05 11:01:06.669 1382-1382/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> get snap: {"_napiwrapper":{}}

查看日志发现每次截图screenshot.save的入参screenRect和返回值是一样的。判断截图方法在执行上没有问题,但是截出来的图片与上次的不一样,由此推测截图时页面上有其他东西,但下拉界面除了背景图片外并没有设置其他的东西,背景图片也在下拉界面隐藏是被清除了。

  1. 为了排除下拉背景图片的影响,在hideSelf和updateBackground中分别添加日志:

// hideSelf 在设置mBackground 为undefined之后添加日志打印mBackground的值
hideSelf() {
    Log.showInfo(TAG, "hideSelf");
    this._animateTo({...SHOW_ANIM_CONFIG,
        onFinish: () => {
            Log.showInfo(TAG, "hideSelf, hide anim finish.");
            this.showComponentName = undefined
            this.mBackground && this.mBackground.release();
            // 将背景图片设置为undefined
            this.mBackground = undefined;
            // 添加打印日志
            Log.showInfo(TAG, "hideSelf, hide anim finish mBackground:"+JSON.stringify(this.mBackground));
            WindowManager.hideWindow(WindowType.DROPDOWN_PANEL)
        }
    }, () => {
        this.componentOptAreaTranslateY = (-this.componentOptAreaHeightPX * 0.1) + 'px';
        this.backgroundOpacity = 0
    })
}
​
// updateBackground 在screenshot.save截图之前打印 mBackground 的值
updateBackground() {
    let rect = WindowManager.getWindowInfo(WindowType.DROPDOWN_PANEL) ? .rect;
    Log.showInfo(TAG, "start get snapShot, rect:" + JSON.stringify(rect))
    // 添加打印日志
    Log.showInfo(TAG, "start get snapShot mBackground:" + JSON.stringify(this.mBackground));
    screenshot.save({ screenRect: rect }).then((snapImage) => {
        Log.showInfo(TAG, "get snap:" + JSON.stringify(snapImage))
        // 设置背景图片
        this.mBackground = snapImage
    })
}

日志如下:

// 第一次下拉截图 updateBackground mBackground:undefined -- 正确,截图时没有背景图
08-05 11:20:24.047 1396-1396/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> start get snapShot mBackground:undefined
08-05 11:20:24.295 1396-1396/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> screenshot get snapShot mBackground :{"_napiwrapper":{}}
​
// 第一次收起清除图片 mBackground:{"_napiwrapper":{}} -- 错误,this.mBackground = undefined;执行后并没有清除掉mBackground
08-05 11:20:28.974 1396-1396/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> hideSelf, hide anim finish mBackground:{"_napiwrapper":{}}
​
// 第二次下拉截图 updateBackground mBackground:{"_napiwrapper":{}} -- 错误,截图时存在mBackground背景图片
08-05 11:20:33.835 1396-1396/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> start get snapShot mBackground:{"_napiwrapper":{}}
08-05 11:20:34.077 1396-1396/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> screenshot get snapShot mBackground :{"_napiwrapper":{}}
​
// 第二次收起清除图片 mBackground:{"_napiwrapper":{}} -- 错误,this.mBackground = undefined;执行后并没有清除掉mBackground
08-05 11:20:37.678 1396-1396/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> hideSelf, hide anim finish mBackground:{"_napiwrapper":{}}
​
// 第三次下拉截图 updateBackground mBackground:{"_napiwrapper":{}} -- 错误,截图时存在mBackground背景图片
08-05 11:20:40.986 1396-1396/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> start get snapShot mBackground:{"_napiwrapper":{}}
08-05 11:20:41.223 1396-1396/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> screenshot get snapShot mBackground :{"_napiwrapper":{}}
​
// 第三次收起清除图片 mBackground:{"_napiwrapper":{}} -- 错误,this.mBackground = undefined;执行后并没有清除掉mBackground
08-05 11:20:43.682 1396-1396/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> hideSelf, hide anim finish mBackground:{"_napiwrapper":{}}
​
// 第四次下拉截图 updateBackground mBackground:{"_napiwrapper":{}} -- 错误,截图时存在mBackground背景图片
08-05 11:20:45.873 1396-1396/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> start get snapShot mBackground:{"_napiwrapper":{}}
08-05 11:20:46.119 1396-1396/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> screenshot get snapShot mBackground :{"_napiwrapper":{}}
​
// 第四次收起清除图片 mBackground:{"_napiwrapper":{}} -- 错误,this.mBackground = undefined;执行后并没有清除掉mBackground
08-05 11:20:48.237 1396-1396/com.ohos.systemui I 02200/JsApp: SystemUI_Default tag: DropdownPanel-Index --> hideSelf, hide anim finish mBackground:{"_napiwrapper":{}}
  1. 根据上述日志判断,每次收起下拉界面执行this.mBackground = undefined;之后打印的mBackground 发现还有值存在,导致每次截图时,上一次的截图一直存在,多次截图时图片显示就不一样。但是this.mBackground = undefined;这行代码没有生效是造成mBackground 未被清除的主要原因。查看mBackground 的定义。

@State mBackground: PixelMap | undefined = undefined;

mBackground可以是两种数据类型,分别是PixelMap和undefined,PixelMap本质是一个记录同图片像素信息的对象,查看编译后的代码:

// \applications_systemui\product\phone\dropdownpanel\build\default\intermediates\assets\default\ets\pages\index.js
class Index extends View {
    constructor(compilerAssignedUniqueChildId, parent, params) {
        super(compilerAssignedUniqueChildId, parent);
        ...
        this.__mBackground = new ObservedPropertyObject(undefined, this, "mBackground");
        ...
    }
}

mBackground最终调用的是ObservedPropertyObject方法进行的数据代理。 查看底层数据设置的代码:/foundation/ace/ace_engine/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/observed_property_object.ts 。

class ObservedPropertyObject < T extends Object > extends ObservedPropertyObjectAbstract < T > implements ISinglePropertyChangeSubscriber < T > {
    private wrappedValue_: T;
    constructor(
        value: T,
        owningView: IPropertySubscriber,
        propertyName: PropertyInfo
    ) {
        super(owningView, propertyName);
        this.setValueInternal(value);
    }
​
    private setValueInternal(newValue: T): boolean {
        // 当判断设置的值不为object类型时不进行操作
        if (typeof newValue !== "object") {
            console.debug("ObservedPropertyObject["+this.id__()+", " + this.info() + "] new value is NOT an object. Application error. Ignoring set.");
            return false;
        }
        this.unsubscribeFromOwningProperty();
        if (ObservedObject.IsObservedObject(newValue)) {
            console.debug("ObservedPropertyObject["+this.id__()+", " + this.info() + "] new value is an ObservedObject already");
            ObservedObject.addOwningProperty(newValue, this);
            this.wrappedValue_ = newValue;
        } else if (newValue instanceof SubscribaleAbstract) {
            console.debug("ObservedPropertyObject[" + this.id__()+", " + this.info() + "] new value is an SubscribaleAbstract, subscribiung to it.");
            this.wrappedValue_ = newValue;
            ((this.wrappedValue_ as unknown) as SubscribaleAbstract).addOwningProperty(this);
        } else {
            console.debug("ObservedPropertyObject["+ this.id__() + ", " + this.info() + "] new value is an Object, needs to be wrapped in an ObservedObject.");
            this.wrappedValue_ = ObservedObject.createNew(newValue, this);
        }
        return true;
    }
​
    // 赋值时调用了setValueInternal方法
    public set(newValue: T): void {
        if (this.wrappedValue_ == newValue) {
            console.debug("ObservedPropertyObject[" + this.id__()} + ", " + this.info() + "]: set with unchanged value - ignoring.");
            return;
        }
        console.debug("ObservedPropertyObject[" + this.id__() + ", " + this.info() + "]: set, changed");
        this.setValueInternal(newValue);
        this.notifyHasChanged(newValue);
    }
}

通过上面代码可得知,在设置this.mBackground 为 undefined时,会调用setValueInternal方法,在 setValueInternal 方法中判断了其数据类型不为 object 时不进行操作,因此在hideSelf方法中this.mBackground = undefined;代码不会执行,从而导致背景图片一直没有被清除而造成了每次截图的结果不一样。

6 知识分享

在OpenHarmony的组件开发中,使用@state时需注意其数据类型的设置,建议不要选择两种不同的数据类型作为@State修饰的数据。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐