HarmonyOS 5 上下文的使用:UIContext 与 WindowStage 的关系

大家好,我是不想掉发的鸿蒙开发工程师 城中的雾 ,在上一期中,我们理清了 Context 的关系,了解了 ApplicationContext,在 API 10 以前,我们习惯用全局 API(如 routerpromptAction)来干这些事。但随着鸿蒙 多窗口 (Multi-Window) 能力的增强,这些全局 API 开始显得"力不从心"------系统不知道你这个弹窗该弹在哪个窗口上,也不知道按照哪个屏幕的密度去计算像素,这一期,我们来聊聊这位专管界面的"管家"UIContext

1. 为什么要引入 UIContext?

在早期的鸿蒙开发中,我们习惯这样写代码:

复制代码
import router from '@ohos.router';
import promptAction from '@ohos.promptAction';

// 旧写法(全局单例)
router.pushUrl({ url: 'pages/Detail' });
promptAction.showToast({ message: 'Hello' });

这种写法有什么问题?

如果你的 App 同时在手机主屏和外接显示器上运行(或者在折叠屏的分屏模式下),这时候有两个窗口。当你调用 router.pushUrl 时,系统会懵圈:"大哥,你到底想在哪个窗口跳转?"

这就是官方文档中反复提到的 "UI 上下文不明确" 问题。

解决方案:UIContext

UIContext 是直接绑定在 WindowStage(窗口舞台)上的。每一个 UIContext 都死死盯着一个具体的窗口。通过它去调用弹窗或路由,系统就非常清楚要把内容画在哪里。

2. 获取方式:this.getUIContext()

在 API 10+ 的 ArkTS 组件 (@Component) 中,获取 UIContext 的标准姿势只有一种:

复制代码
@Entry
@Component
struct Index {
  build() {
    Button('点击我')
      .onClick(() => {
        // ✅ 推荐:直接获取当前组件绑定的 UIContext
        let uiContext = this.getUIContext();
        
        // ❌ 不推荐:不要再试图通过 getContext(this) 去强转了
        // 虽然也能拿到,但不够直接,而且类型定义可能不全
      })
  }
}

3. UIContext 的四大核心能力

既然官方强推,那它到底接管了哪些能力?

(1) 弹窗与提示 (PromptAction)

替代全局的 @ohos.promptAction

复制代码
let uiContext = this.getUIContext();
let prompt = uiContext.getPromptAction();

// 弹 Toast
prompt.showToast({
  message: '操作成功',
  duration: 2000
});

// 弹 Dialog
prompt.showDialog({
  title: '提示',
  message: '确定要删除吗?',
  buttons: [
    { text: '取消', color: '#000000' },
    { text: '确定', color: '#FF0000' }
  ]
});

(2) 路由跳转 (Router)

替代全局的 @ohos.router

复制代码
let uiContext = this.getUIContext();
let router = uiContext.getRouter();

router.pushUrl({
  url: 'pages/SecondPage'
});

(3) 屏幕尺寸转换 (vp2px)

以前我们要在工具类里算 vppx 很麻烦,现在直接找 UIContext,因为它知道当前屏幕的像素密度(Density)。

复制代码
let uiContext = this.getUIContext();

// 将 20vp 转换为当前屏幕对应的 px 值
let widthInPx = uiContext.vp2px(20);
let widthInVp = uiContext.px2vp(100);

(4) 浮层管理 (Overlay)

如果你想在当前窗口最上层悬浮一个自定义组件(比如全局 Loading、悬浮球),UIContext 提供了 getOverlay() 能力。

复制代码
// 这是一个进阶功能,适合做全局自定义弹窗
this.getUIContext().getOverlayManager().addComponentContent(...)

4. 避坑实战:异步回调里的"幽灵"

这是 UIContext 最容易踩的坑。

场景:

用户点击按钮后,发起一个网络请求,3秒后请求回来,弹出一个 Toast。

错误写法

复制代码
@Component
struct WrongDemo {
  build() {
    Button('请求网络')
      .onClick(async () => {
        // 模拟网络请求
        await new Promise(r => setTimeout(r, 3000));
        
        // 风险:如果 3秒内用户把这个页面关了,或者组件销毁了
        // this.getUIContext() 可能会失效或报错
        this.getUIContext().getPromptAction().showToast({ message: '成功' });
      })
  }
}

正确写法 (闭包捕获):

在进入异步操作前,先把 uiContext 拿到手里捏死。

复制代码
@Component
struct RightDemo {
  build() {
    Button('请求网络')
      .onClick(async () => {
        // 1. 先保存 UIContext 引用
        // 只要这个闭包还在,uiContext 就在,它依然指向那个窗口
        let uiContext = this.getUIContext();
        
        await new Promise(r => setTimeout(r, 3000));
        
        // 2. 使用保存的引用
        // 即使当前组件销毁了,只要窗口还在,Toast 就能弹出来
        try {
            uiContext.getPromptAction().showToast({ message: '成功' });
        } catch (e) {
            // 如果窗口都销毁了,这里会报错,需要捕获
            console.error('窗口已销毁,无法弹窗');
        }
      })
  }
}

5. 工具类封装指南

如果你想封装一个 ToastUtils,千万别直接 import 全局的 promptAction。

推荐封装方式

复制代码
// ToastUtils.ets
import { common } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export class ToastUtils {
  // 方案 A: 每次调用传 context
  static show(uiContext: UIContext, msg: string) {
    uiContext.getPromptAction().showToast({ message: msg });
  }
  
  // 方案 B: 传入 getContext(this) 拿到的 BaseContext,自动转换
  // 这种方式兼容性好,但有时候从 BaseContext 转 UIContext 比较绕
  /*
  static showByBase(context: common.Context, msg: string) {
     // 略,比较复杂,建议直接传 UIContext
  }
  */
}

调用方

复制代码
// Index.ets
Button('工具类弹窗')
  .onClick(() => {
    ToastUtils.show(this.getUIContext(), '封装成功!');
  })

总结:UIContext 使用准则

  1. 认准入口 :在 .ets 组件里,凡是跟 界面显示 有关的(弹窗、路由、尺寸),首选 this.getUIContext()
  2. 拒绝全局 :尽量少用 @ohos.router@ohos.promptAction 的全局方法,拥抱实例方法。
  3. 异步锁死 :在 setTimeoutawait 之前,先 let uiContext = this.getUIContext() 保存引用,防止 this 上下文丢失。
  4. 桥梁作用 :如果需要获取 AbilityContext(比如要停止自身),请使用 this.getUIContext().getHostContext()

下一期预告:

界面搞定了,业务逻辑怎么办?

  • 怎么跳转到别人的 App?
  • 怎么申请相机权限?
  • 怎么在代码里关闭应用?

📚 充电时间

如果有想加入鸿蒙生态的大佬们,快来加入鸿蒙认证吧!初高级证书还没获取的,点这里:

🔗 HarmonyOS第一课:官方认证培训

相关推荐
音浪豆豆_Rachel2 小时前
Flutter鸿蒙文件选择器实现层解析:消息通道、协议转换与数据处理
flutter·华为·harmonyos
音浪豆豆_Rachel2 小时前
Flutter鸿蒙文件选择器入口解析:插件生命周期与平台绑定
flutter·harmonyos
特立独行的猫a2 小时前
鸿蒙PC三方库移植:x264视频编码库的移植适配实践
华为·音视频·harmonyos·三方库移植·鸿蒙pc
前端世界3 小时前
拆解鸿蒙 IoT 接入:网络通信、分布式软总线和能力调用是怎么配合的
分布式·物联网·harmonyos
小草cys11 小时前
HarmonyOS NEXT平台下实现的文本转语音(TTS)
华为·harmonyos
AirDroid_cn13 小时前
鸿蒙NEXT:500MB 以上文件传输失败,如何开启断点续传?
华为·harmonyos
奔跑的露西ly14 小时前
【HarmonyOS NEXT】实现跨工程模块跳转
华为·harmonyos
讯方洋哥16 小时前
HarmonyOS应用开发-样式复用多态
harmonyos
摘星编程16 小时前
CANN内存管理机制:从分配策略到性能优化
人工智能·华为·性能优化