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第一课:官方认证培训

相关推荐
程序猿追29 分钟前
【鸿蒙PC桌面端开发】使用ArkTS做出RGB 色环选择器
华为·harmonyos
C雨后彩虹1 小时前
书籍叠放问题
java·数据结构·算法·华为·面试
zhujian826372 小时前
二十五、【鸿蒙 NEXT】@ObservedV2/@Trace实现组件动态刷新
华为·harmonyos·trace·lazyforeach·observedv2
wszy18092 小时前
rn_for_openharmony_空状态与加载状态:别让用户对着白屏发呆
android·javascript·react native·react.js·harmonyos
SameX2 小时前
鸿蒙应用的“任意门”:Deep Linking 与 App Linking 的相爱相杀
harmonyos
AlbertZein2 小时前
HarmonyOS一杯冰美式的时间 -- @Watch 到 @Monitor
harmonyos
奋斗的小青年!!3 小时前
Flutter跨平台开发适配OpenHarmony:下拉刷新组件的实战优化与深度解析
flutter·harmonyos·鸿蒙
lili-felicity3 小时前
React Native for Harmony:订单列表页面状态筛选完整实现
react native·react.js·harmonyos
lili-felicity4 小时前
React Native 鸿蒙跨平台开发:纯原生IndexBar索引栏 零依赖 快速定位列表
react native·react.js·harmonyos
小雨下雨的雨5 小时前
Flutter鸿蒙共赢——秩序与未知的共鸣:彭罗斯瓷砖在鸿蒙律动中的数字重构
flutter·华为·重构·交互·harmonyos·鸿蒙系统