【HarmonyOS Bug踩坑】主窗口调用的接口,UI在子窗口异常显示

【HarmonyOS Bug踩坑】主窗口调用的UI表现在子窗口异常显示

一、问题现象:

这个问题的标题略显抽象,毕竟涉及到的异常表现形式太多,标题是临时拟定的。

说白了,这个问题是鸿蒙里经典的上下文指定问题

异常的业务场景是,在主窗口之上,添加一个子窗口。当在主窗口里调用某些UI表现,例如:气泡,弹窗,模态窗口,自定义安全键盘,自定义loading等,你会发现,有时候都异常加载到子窗口中了,并没有在主窗口显示。如下图所示:

typescript 复制代码
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct SubWinPage {
  private TAG: string = "SubWinPage";
  private sub_windowClass: window.Window | null = null;

   aboutToAppear() {
    this.showSubWindow("xxxx", 900, 300)
     setTimeout(()=>{
       try {
         this.destroySubWindow();
         // window.getLastWindow(getContext()).then((win)=>{
         //   console.error(this.TAG, 'win:' + JSON.stringify(win));
         //   let height = win.getWindowDecorHeight();
         //   console.error(this.TAG, 'height:' + height);
         // })

         let windowStage_:  window.WindowStage = globalThis.mWindowStage;
         let win = windowStage_.getMainWindowSync();
         let height = win.getWindowDecorHeight();
       }catch (e){
         console.error(this.TAG, 'e:' + JSON.stringify(e));
       }
     },1000)
  }

  private showSubWindow(name: string, num: number, x: number) {
    console.log(this.TAG, 'showSubWindow start');
    let windowStage_:  window.WindowStage = globalThis.mWindowStage;
    // 1.创建应用子窗口。
    if (windowStage_ == null) {
      console.error(this.TAG, 'Failed to create the subwindow. Cause: windowStage_ is null');
    }
    else {
      windowStage_.createSubWindow(name, (err: BusinessError, data) => {
        let errCode: number = err.code;
        if (errCode) {
          console.error(this.TAG, 'Failed to create the subwindow. Cause: ' + JSON.stringify(err));
          return;
        }
        this.sub_windowClass = data;
        console.info(this.TAG, 'Succeeded in creating the subwindow. Data: ' + JSON.stringify(data));
        // 2.子窗口创建成功后,设置子窗口的位置、大小及相关属性等。
        this.sub_windowClass.moveWindowTo(x, 300, (err: BusinessError) => {
          let errCode: number = err.code;
          if (errCode) {
            console.error(this.TAG, 'Failed to move the window. Cause:' + JSON.stringify(err));
            return;
          }
          console.info(this.TAG, 'Succeeded in moving the window.');
        });
        this.sub_windowClass.resize(num, num, (err: BusinessError) => {
          let errCode: number = err.code;
          if (errCode) {
            console.error(this.TAG, 'Failed to change the window size. Cause:' + JSON.stringify(err));
            return;
          }
          console.info(this.TAG, 'Succeeded in changing the window size.');
        });
        // 3.为子窗口加载对应的目标页面。
        this.sub_windowClass.setUIContent("pages/SubWinLoadPage", (err: BusinessError) => {
          let errCode: number = err.code;
          if (errCode) {
            console.error(this.TAG, 'Failed to load the content. Cause:' + JSON.stringify(err));
            return;
          }
          this.sub_windowClass?.setWindowTouchable(true)
          console.info(this.TAG, 'Succeeded in loading the content.');
          // 3.显示子窗口。 (this.sub_windowClass as window.Window)
          this.sub_windowClass?.showWindow((err: BusinessError) => {
            let errCode: number = err.code;
            if (errCode) {
              console.error(this.TAG, 'Failed to show the window. Cause: ' + JSON.stringify(err));
              return;
            }
            console.info(this.TAG, 'Succeeded in showing the window.');
          });
        });
      })
    }
    console.log(this.TAG, 'showSubWindow end');
  }

  destroySubWindow() {
    // 4.销毁子窗口。当不再需要子窗口时,可根据具体实现逻辑,使用destroy对其进行销毁。
    (this.sub_windowClass as window.Window).destroyWindow((err: BusinessError) => {
      let errCode: number = err.code;
      if (errCode) {
        console.error(this.TAG, 'Failed to destroy the window. Cause: ' + JSON.stringify(err));
        return;
      }
      console.info(this.TAG, 'Succeeded in destroying the window.');
    });
  }

  build() {
    Column() {
      Text("点击创建子窗口")
        .id('SubWinPageHelloWorld')
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .onClick(()=>{
          this.showSubWindow("ooooo", 500, 300);
        })

      Text("点击创建子窗口2")
        .id('SubWinPageHelloWorld')
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .onClick(()=>{
          this.showSubWindow("ooooo2", 800, 600);
        })

      Text("移动窗口2")
        .id('SubWinPageHelloWorld')
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .onClick(()=>{

          this.sub_windowClass?.moveWindowTo(700, 300, (err: BusinessError) => {
            let errCode: number = err.code;
            if (errCode) {
              console.error(this.TAG, 'Failed to move the window. Cause:' + JSON.stringify(err));
              return;
            }
            console.info(this.TAG, 'Succeeded in moving the window.');
          });
        })

      Text("点击销毁子窗口")
        .id('SubWinPageHelloWorld')
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .onClick(()=>{
          this.destroySubWindow();
        })

      Text("显示气泡")
        .id('SubWinPageHelloWorld')
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .onClick(async ()=>{
             let win: window.Window = await window.getLastWindow(getContext());
              win.getUIContext().getPromptAction().showToast({
                message: "我是气泡,测试显示问题",
                duration: 5000
              });

          win.getUIContext().px2vp(200)
        })
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}

甚至还有使用老路由router跳转,在主窗口跳转也有可能会加载到子窗口中。或者一些使用逻辑处理的业务也可能异常。

该问题多出现在较大的工程里,使用了Har包,Hsp包。或者使用MVVM架构,纯逻辑层处理业务等。

二、问题原由:

综上所述,说了这么多,其实问题的原因在开头已经提到了,罪魁祸首就是上下文。Context。

如果接触鸿蒙开发,时间比较长的同学,其实对上下文还是很熟悉的,你会发现在日常开发中,经常要用到上下文去调用某些接口。特别是从api7最早开始接触鸿蒙,到如今api20了。这样的老同学体会更深,会发现很多之前不需要使用上下文调用的接口,现在也推荐或者强制让使用上下文引入接口了。

例如气泡,px2vp等等。

typescript 复制代码
getUIContext().getPromptAction().showToast({
                message: "我是气泡,测试显示问题",
                duration: 5000
              });
              
getUIContext().px2vp(200)

其实该问题就是因为上下文依赖,因为鸿蒙特殊的堆叠渲染树,需要通过上下文作为挂靠节点的标志位。有了上下文作为导航,就知道当前要渲染的UI控件,现在挂载到哪个节点下了。

最早的时候,上下文是沉入到系统底层,上层开发感知比较少,很少需要调用到上下文去引出接口。但是随着接入鸿蒙的app越来越多。很多复杂的项目和业务场景迁移到鸿蒙中,发现这样的设计方式有问题。很多UI挂载的预期很离谱。

所以随着API的升级,上下文慢慢开放到应用层,让开发者来灵活的使用,来掌控挂载的预期效果。

该问题大多出现在api12或者之前的老项目中,因为封装的逻辑层,需要用到上下文。多是通过getLastWindow的形式,获取窗口,再从窗口中拿上下文。来做UI的引用操作。但是当有子窗口显示时,getLastWindow其实拿就不是主窗口,而是子窗口,这也导致后续获取的上下文也是子窗口的。

typescript 复制代码
    let win: window.Window = await window.getLastWindow(getContext());
              win.getUIContext().getPromptAction().showToast({
                message: "我是气泡,测试显示问题",
                duration: 5000
              });

像Loading,弹窗,甚至是悬浮活动按钮,都喜欢用子窗口来做,都会导致该问题。虽然子窗口可以高于主窗口显示,方便在顶层做一些UI效果。但是后续的上下文调用处理很麻烦。

三、解决方案:

1、不更换子窗口的情况,UI调用处的上下文获取需要进行修改,可以将上下文获取的逻辑进行删除,通过外部调用方传入上下文的形式,来获取上下文。

该方案的优点是封装者不需要考虑上下文的获取,UI显示也会符合预期。 缺点就是暴露的参数多了一个,并且有时候调用方获取上下文可能也不方便,如果是多层逻辑调用,那就要穿透式新增上下文参数了,改动也比较大。

2、更换子窗口,使用其他容器方案来实现展示在主窗口层级之上的效果。例如浮层OverlayManager。

相关推荐
nashane1 天前
HarmonyOS 6学习:CapsLock键失效诊断与长截图完整实现指南
学习·华为·harmonyos
richard_yuu1 天前
鸿蒙心理测评模块实战|PHQ-9/GAD7双量表答题、实时计分与结果本地化存储
华为·harmonyos
不爱吃糖的程序媛1 天前
2026年Electron 鸿蒙PC环境搭建指南
人工智能·华为·harmonyos
nashane1 天前
HarmonyOS 6学习:长截图功能开发中的滚动拼接与权限处理实战
人工智能·华为·harmonyos
大师兄66681 天前
从零开发一个 HarmonyOS 输入法——KikaInputMethod 完整拆解
harmonyos·服务卡片·harmonyos6·formkit
Python私教1 天前
鸿蒙 NEXT 也能接 MCP?用 ArkTS 跑通 AI Agent 工具链
人工智能·华为·harmonyos
Swift社区2 天前
分布式能力在鸿蒙 PC 上到底怎么用?
分布式·华为·harmonyos
nashane2 天前
HarmonyOS 6学习:外接键盘CapsLock与长截图功能的实战调试与完整解决方案
学习·华为·计算机外设·harmonyos
aqi002 天前
一文理清 HarmonyOS 6.0.2 涵盖的十个升级点
android·华为·harmonyos·鸿蒙·harmony