在之前的文章中([鸿蒙自定义 Dialog 的 6 种方式]),提到了自定义 Dialog 某些情况下无法渲染显示的问题,其本质是 UI 组件构造时,UI 上下文获取异常,一般在异步回调或者非 UI 组件环境中构造全局类的组件(例如弹窗或者 HUD)时,容易遇到这个问题。
1. 问题复现
使用我的 [XTEasyHUD],不预先在 UI 组件生命周期中对其进行初始化配置,直接在异步场景中调用,就会导致 HUD 无法显示。
ts
代码解读
复制代码
import axios, { AxiosResponse } from '@ohos/axios'
import {
XTEasyHUD
} from '@jxt/xt_hud'
class viewModel {
async asyncShowToast() {
let response: AxiosResponse<string> = await axios.get('https://www.baidu.com')
console.log('end', response.data)
// 这个 toast 无法显示
XTEasyHUD.showToast('请求完成')
}
}
@Entry
@Component
struct Index {
vm: viewModel = new viewModel()
build() {
Column() {
Button('异步显示 Toast')
.onClick(() => {
this.vm.asyncShowToast()
})
}
.height('100%')
.width('100%')
}
}
我在该组件库的文档中,有做该问题的特别说明和解决方案描述:[已知问题特别注意]
解决方案:
import axios, { AxiosResponse } from '@ohos/axios'
import {
XTEasyHUD
} from '@jxt/xt_hud'
class viewModel {
async asyncShowToast() {
let response: AxiosResponse<string> = await axios.get('https://www.baidu.com')
console.log('end', response.data)
XTEasyHUD.showToast('请求完成')
}
}
@Entry
@Component
struct Index {
vm: viewModel = new viewModel()
// Warning: 强烈建议在全局根页面组件中事先做一次XTEasyHUD的全局配置,如果使用默认样式,可以直接进行空配置
// 这样可以保证后续使用时,异步场景中的 HUD 可以正常加载
aboutToAppear(): void {
XTEasyHUD.globalConfigToast()
XTEasyHUD.globalConfigLoading()
XTEasyHUD.globalConfigProgress()
}
build() {
Column() {
Button('异步显示 Toast')
.onClick(() => {
this.vm.asyncShowToast()
})
}
.height('100%')
.width('100%')
}
}
这个本质做的是,在 UI 组件环境中,执行了 HUD 组件的预初始化,避免后续执行 HUD 显示时(此时 HUD 可能还未初始化,组件库内部逻辑是懒加载的)的 UI 上下文获取异常。
2. 更优雅的方案:runScopedTask
其实 ArkUI 框架中有对应的 UIContext 回溯方法:[runScopedTask]
在系统很多 API 的执行过程中,该方法都很有用:
- [DisplaySync.start():start接口是将DisplaySync关联到UI实例和窗口,若在非UI页面中或者一些异步回调中进行start操作,可能无法跟踪到当前UI的上下文,导致start接口失败,会进一步导致订阅函数无法执行]
- [Environment和UIContext相关联]
import {
XTEasyHUD
} from '@jxt/xt_hud'
import { window } from '@kit.ArkUI'
class viewModel {
async asyncShowToast() {
let response: AxiosResponse<string> = await axios.get('https://www.baidu.com')
console.log('end', response.data)
// 回溯 UI 上下文
let windowClass = await window.getLastWindow(getContext())
let uiContext = windowClass.getUIContext()
uiContext.runScopedTask(() => {
XTEasyHUD.showToast('请求完成')
})
}
}
@Entry
@Component
struct Index {
vm: viewModel = new viewModel()
build() {
Column() {
Button('异步显示 Toast')
.onClick(() => {
this.vm.asyncShowToast()
})
}
.height('100%')
.width('100%')
}
}
更好的方式,自然是我在我的组件库内部逻辑中,做好对应的 UI 上下文回溯操作:
static async showToast(text: string, options?: XTHUDToastOptions) {
if (!(text.length > 0)) {
return
}
// 懒加载
await _EasyHUDManager.initXTEasyHUDToast(null)
_EasyHUDManager.showToast(text, options)
}
async initXTEasyHUDToast(globalOptions?: ((options: XTHUDToastOptions) => void) | null) {
if (!_XTEasyHUDManager._toast) {
// 在 UI 上下文环境中,执行 HUD 组件初始化
let windowClass = await window.getLastWindow(getContext())
let uiContext = windowClass.getUIContext()
uiContext.runScopedTask(() => {
_XTEasyHUDManager._toast = new XTEasyHUDToast(undefined, {
globalOptions: globalOptions
})
_XTEasyHUDManager._toast.mounted()
})
}
}
但这样操作有很大的弊端:
首先,因为getLastWindow
操作是异步的,会导致整体函数变为异步操作,破坏了整体 API 的执行时序和封装;其次,getContext()
操作并未传入任何组件(可选入参),也就是该上下文获取操作,很可能会执行失败,导致后续runScopedTask
回溯失败。
实测在 Ability 启动阶段,如果强制执行上述操作,可能会导致同样的异常问题,这个依旧算不上安全。
3. 最佳实践
如果想在任意环境中构建 UI 组件,最好的方案,还是使用 [ComponentContent]。
ComponentContent 在实例化时,需要显示的传入 UIContext,至于如何获取 UIContext,那就是另一个问题了。
let contentNode = new ComponentContent(uiContext, wrapBuilder(buildText), new Params(this.message));
我在我的组件库 V3 版本中新增的 [XTPromptHUD],就是利用 ComponentContent 做的,且内置了一套相对完善的 UI 上下文自动获取逻辑,依旧可以做到一句话执行显示 HUD 的操作,但相对合理和安全的,还是用户在使用前自定义传入 UIContext。
/// 异步获取context
private static asyncGetUIContext(callback: (uiContext: UIContext | null) => void) {
let context = getContext()
if (context) {
window.getLastWindow(context).then((windowClass: window.Window) => {
let uiContext = windowClass.getUIContext()
if (callback) {
callback(uiContext)
}
}).catch(() => {
if (callback) {
callback(null)
}
})
} else {
if (callback) {
callback(null)
}
}
}
/// 异步自动初始化 toast
private static asyncConfigToast(callback: (toast: XTPromptHUDToastClass | null) => void) {
XTPromptHUD.asyncGetUIContext((uiContext: UIContext | null) => {
if (uiContext) {
let toastHUD = XTPromptHUD.initToastInstance(uiContext)
if (callback) {
callback(toastHUD)
}
} else {
if (callback) {
callback(null)
}
}
})
}
static showToast(text: string, options?: XTHUDToastOptions): void {
if (!(text.length > 0)) {
return
}
if (XTPromptHUD._toast) {
XTPromptHUD._toast.showToast(text, options)
} else {
XTPromptHUD.asyncConfigToast((toast: XTPromptHUDToastClass | null) => {
if (toast) {
console.warn('[XTPromptHUD.showToast] warning: globalConfigToast is not executed, the default context is used!')
toast.showToast(text, options)
} else {
console.error('[XTPromptHUD.showToast] error: globalConfigToast must be executed first!')
// throw new Error('globalConfigToast must be executed first!')
}
})
}
}
最后呢,很多开发朋友不知道需要学习那些鸿蒙技术?鸿蒙开发岗位需要掌握那些核心技术点?为此鸿蒙的开发学习必须要系统性的进行。
而网上有关鸿蒙的开发资料非常的少,假如你想学好鸿蒙的应用开发与系统底层开发。你可以参考这份资料,少走很多弯路,节省没必要的麻烦。由两位前阿里高级研发工程师联合打造的《鸿蒙NEXT星河版OpenHarmony开发文档》里面内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(Harmony NEXT)技术知识点
如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。下面是鸿蒙开发的学习路线图。
高清完整版请点击→《鸿蒙NEXT星河版开发学习文档》
针对鸿蒙成长路线打造的鸿蒙学习文档。话不多说,我们直接看详细资料鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,帮助大家在技术的道路上更进一步。
《鸿蒙 (OpenHarmony)开发学习视频》
《鸿蒙生态应用开发V2.0白皮书》
《鸿蒙 (OpenHarmony)开发基础到实战手册》
《鸿蒙开发基础》
《鸿蒙开发进阶》
《鸿蒙开发实战》
获取这份鸿蒙星河版学习资料,请点击→《鸿蒙NEXT星河版开发学习文档》
总结
鸿蒙—作为国家主力推送的国产操作系统。部分的高校已经取消了安卓课程,从而开设鸿蒙课程;企业纷纷跟进启动了鸿蒙研发。
并且鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,未来将会支持 50 万款的应用。那么这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行!