HarmonyOS 6学习:异步操作中Toast提示框消失之谜与UIContext解决方案实战
摘要:本文深入分析了HarmonyOS6应用开发中Toast提示在异步操作中"隐身"的问题。通过案例研究发现,当在异步回调中直接调用promptAction.showToast()时,由于UI上下文丢失导致提示框随机性不显示。根本原因是异步操作可能在不同线程执行,而Toast显示需要明确的UI上下文。解决方案包括:1)使用UIContext.getPromptAction()获
当Toast在异步中"隐身":一次UI上下文丢失的深度追踪
在HarmonyOS 6应用开发中,我最近遇到了一个让人困惑的问题。我们的团队正在开发一个智能家居控制应用,其中有一个场景是用户点击"同步设备"按钮后,应用需要从云端获取最新的设备状态并更新到本地。功能逻辑一切正常,数据能正确同步,但有一个小问题让测试团队反复提bug:"同步成功后,为什么没有成功提示?"
更奇怪的是,这个提示框有时候会出现,有时候又完全消失。查看代码,发现开发者在异步回调中使用了promptAction.showToast来显示成功提示:
// ❌ 问题代码:异步回调中的Toast
async function syncDevices() {
try {
// 模拟网络请求
const result = await fetchDeviceDataFromCloud();
// 处理数据
await processDeviceData(result);
// 显示成功提示 - 这里有时不显示!
promptAction.showToast({
message: '设备同步成功!',
duration: 2000
});
console.log('同步完成,应该显示Toast');
} catch (error) {
console.error('同步失败:', error);
}
}
控制台日志显示"同步完成,应该显示Toast",但屏幕上就是看不到那个绿色的成功提示框。有测试同事开玩笑说:"你们的Toast是不是害羞,躲在异步回调里不敢出来?"
今天,我就把这次完整的Toast显示问题排查经历记录下来,从异步操作的"黑洞"到UI上下文的"钥匙",帮你彻底解决HarmonyOS中异步操作UI更新的核心问题。
问题诊断:为什么异步中的Toast会"隐身"?
实际测试场景
在我们的智能家居应用中,Toast提示框在以下场景中表现异常:
正常工作的场景:
- 同步操作:在主线程中直接调用
showToast,100%显示 - 按钮点击:在按钮的
onClick回调中调用,正常显示 - 定时器:在
setTimeout中调用,大部分时间正常
异常场景:
- 网络请求回调:在
fetch或axios的then回调中,经常不显示 - Promise异步链:在
async/await的函数中,随机性不显示 - WebSocket消息:在WebSocket的
onmessage回调中,几乎从不显示 - 子线程操作:在
TaskPool或Worker中调用,完全看不到
问题现象统计:
- 开发环境:约30%的概率Toast不显示
- 测试环境:约50%的概率Toast不显示
- 生产环境:用户反馈"经常看不到成功提示"
问题代码深度分析
让我们看看问题代码的完整版本:
// ❌ 完整的问题代码示例
import { promptAction } from '@kit.ArkUI';
import { BusinessError } from '@ohos.base';
@Component
struct FaultyDeviceSync {
@State syncStatus: string = '等待同步';
// 同步设备数据
async syncDevices(): Promise<void> {
this.syncStatus = '同步中...';
try {
// 模拟异步网络请求
const devices = await this.fetchDevicesFromCloud();
// 模拟数据处理
await this.processDevices(devices);
// 更新状态
this.syncStatus = '同步完成';
// ❌ 问题所在:在异步回调中直接调用showToast
promptAction.showToast({
message: `成功同步 ${devices.length} 个设备`,
duration: 3000
});
console.log('Toast应该显示,但可能看不到');
} catch (error) {
this.syncStatus = '同步失败';
console.error('同步错误:', error);
// ❌ 同样的问题:错误提示也不显示
promptAction.showToast({
message: '同步失败,请重试',
duration: 3000
});
}
}
// 模拟网络请求
private async fetchDevicesFromCloud(): Promise<any[]> {
return new Promise((resolve) => {
setTimeout(() => {
// 模拟返回设备数据
resolve([
{ id: 1, name: '客厅灯', status: 'on' },
{ id: 2, name: '空调', status: 'off' },
{ id: 3, name: '窗帘', status: 'on' }
]);
}, 1000);
});
}
// 模拟数据处理
private async processDevices(devices: any[]): Promise<void> {
return new Promise((resolve) => {
setTimeout(() => {
// 模拟数据处理逻辑
console.log(`处理了 ${devices.length} 个设备`);
resolve();
}, 500);
});
}
build() {
Column() {
Text('设备同步')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
Text(`状态: ${this.syncStatus}`)
.fontSize(18)
.margin({ bottom: 30 })
Button('开始同步')
.onClick(() => {
this.syncDevices();
})
.width(200)
.height(50)
}
.padding(20)
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
根本原因:UI上下文在异步操作中丢失
HarmonyOS的UI渲染机制
要理解这个问题,需要先了解HarmonyOS的UI渲染机制:
- UI线程(主线程):负责界面渲染和用户交互
- 异步任务:可能运行在子线程或任务池中
- UI上下文(UIContext):UI操作的"通行证"
- 线程边界:不同线程间的UI操作需要正确的上下文传递
问题根源分析
根据华为官方文档的说明,问题的核心在于:在异步方法中,当前的UI上下文可能不明确或丢失。
具体来说:
- 上下文绑定:
promptAction.showToast需要明确的UI上下文来确定在哪个窗口显示 - 异步切换:当代码执行到异步回调时,可能已经离开了原始的UI上下文
- 线程隔离:某些异步操作会在不同的线程中执行,这些线程没有UI上下文
- 生命周期变化:在异步操作期间,页面的生命周期状态可能发生变化
错误信息的深层含义
当出现"UI上下文不明确"的情况时,实际上发生了以下事情:
// 伪代码:showToast的内部逻辑
function showToast(options) {
// 1. 尝试获取当前UI上下文
const uiContext = getCurrentUIContext();
// 2. 检查上下文是否有效
if (!uiContext || !uiContext.isValid()) {
// ❌ 上下文不明确或无效,Toast无法显示
// 但为了不崩溃,这里可能只是静默失败
console.warn('UI上下文不明确,Toast未显示');
return;
}
// 3. 在正确的上下文中显示Toast
uiContext.showToastInternal(options);
}
解决方案:使用UIContext.getPromptAction()
官方推荐方案
根据华为官方文档,正确的解决方案是使用UIContext中的getPromptAction方法获取PromptAction实例,然后通过这个实例调用showToast。
// ✅ 正确示例:使用UIContext获取PromptAction
import { promptAction, UIContext } from '@kit.ArkUI';
import { BusinessError } from '@ohos.base';
@Component
struct CorrectDeviceSync {
@State syncStatus: string = '等待同步';
// 获取UI上下文
private getUIContext(): UIContext | undefined {
try {
const context = getContext(this) as UIContext;
return context;
} catch (error) {
console.error('获取UI上下文失败:', error);
return undefined;
}
}
// 同步设备数据 - 正确版本
async syncDevices(): Promise<void> {
this.syncStatus = '同步中...';
try {
// 模拟异步网络请求
const devices = await this.fetchDevicesFromCloud();
// 模拟数据处理
await this.processDevices(devices);
// 更新状态
this.syncStatus = '同步完成';
// ✅ 正确方式:通过UIContext显示Toast
this.showToastWithContext(`成功同步 ${devices.length} 个设备`);
} catch (error) {
this.syncStatus = '同步失败';
console.error('同步错误:', error);
// ✅ 错误提示也使用正确方式
this.showToastWithContext('同步失败,请重试');
}
}
// 使用UIContext显示Toast
private showToastWithContext(message: string): void {
// 获取UI上下文
const uiContext = this.getUIContext();
if (!uiContext) {
console.error('无法获取UI上下文,使用备用方案');
this.showToastFallback(message);
return;
}
try {
// 通过UIContext获取PromptAction实例
const promptActionInstance = uiContext.getPromptAction();
// 使用获取的实例显示Toast
promptActionInstance.showToast({
message: message,
duration: 3000
});
console.log('Toast显示成功(使用UIContext)');
} catch (error) {
console.error('通过UIContext显示Toast失败:', error);
// 降级处理
this.showToastFallback(message);
}
}
// 备用方案:尝试在主线程中显示
private showToastFallback(message: string): void {
try {
// 尝试直接显示(可能在某些情况下有效)
promptAction.showToast({
message: message,
duration: 3000
});
console.log('Toast显示成功(使用备用方案)');
} catch (error) {
console.error('备用方案也失败:', error);
// 最后的手段:记录日志
console.warn(`需要显示Toast但失败: ${message}`);
}
}
// 模拟网络请求
private async fetchDevicesFromCloud(): Promise<any[]> {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: 1, name: '客厅灯', status: 'on' },
{ id: 2, name: '空调', status: 'off' },
{ id: 3, name: '窗帘', status: 'on' }
]);
}, 1000);
});
}
// 模拟数据处理
private async processDevices(devices: any[]): Promise<void> {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`处理了 ${devices.length} 个设备`);
resolve();
}, 500);
});
}
build() {
Column() {
Text('设备同步(正确版本)')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
Text(`状态: ${this.syncStatus}`)
.fontSize(18)
.margin({ bottom: 30 })
Button('开始同步')
.onClick(() => {
this.syncDevices();
})
.width(200)
.height(50)
}
.padding(20)
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
方案对比分析
| 方法 | 代码示例 | 可靠性 | 适用场景 | 注意事项 |
|---|---|---|---|---|
| 直接调用 | promptAction.showToast() |
低 | 主线程同步操作 | 异步中可能失败 |
| UIContext方式 | uiContext.getPromptAction().showToast() |
高 | 所有场景,特别是异步 | 需要获取UIContext |
| 备用方案 | setTimeout(() => promptAction.showToast()) |
中 | 简单异步场景 | 不是根本解决方案 |
深入原理:UIContext的工作原理
UIContext是什么?
UIContext是HarmonyOS中UI操作的上下文环境,它包含了:
- 窗口信息:Toast应该显示在哪个窗口
- 生命周期状态:当前UI组件的生命周期状态
- 线程信息:UI操作应该在哪个线程执行
- 资源引用:UI操作所需的资源引用
为什么需要UIContext?
在异步操作中,代码可能在不同的执行上下文中运行:
// 示例:异步操作中的上下文变化
async function example() {
// 阶段1:主线程,有UI上下文
console.log('阶段1:在主线程中');
// 这里可以直接使用promptAction.showToast()
await someAsyncOperation();
// 阶段2:可能在微任务队列中,UI上下文可能丢失
console.log('阶段2:在异步回调中');
// 这里直接使用promptAction.showToast()可能失败
setTimeout(() => {
// 阶段3:在宏任务队列中,UI上下文肯定丢失
console.log('阶段3:在setTimeout回调中');
// 这里直接使用promptAction.showToast()几乎肯定失败
}, 0);
}
UIContext的获取方式
有多种方式可以获取UIContext:
// 方法1:通过getContext(this)获取(在组件中)
const uiContext = getContext(this) as UIContext;
// 方法2:通过UIAbility上下文获取
import { common } from '@kit.AbilityKit';
const abilityContext = getContext(this) as common.UIAbilityContext;
const uiContext = abilityContext.uiContext;
// 方法3:通过窗口管理器获取(高级用法)
import { window } from '@kit.ArkUI';
const windowClass = window.getLastWindow(this);
const uiContext = windowClass.uiContext;
最佳实践:异步操作中的UI更新完整方案
方案一:UIContext封装工具类
// ✅ 最佳实践:UIContext工具类封装
import { promptAction, UIContext } from '@kit.ArkUI';
import { BusinessError } from '@ohos.base';
class UIUpdateManager {
private static instance: UIUpdateManager;
private uiContext: UIContext | null = null;
// 单例模式
static getInstance(): UIUpdateManager {
if (!UIUpdateManager.instance) {
UIUpdateManager.instance = new UIUpdateManager();
}
return UIUpdateManager.instance;
}
// 初始化UIContext
init(context: UIContext): void {
this.uiContext = context;
console.log('UIUpdateManager 初始化完成');
}
// 安全显示Toast
async showToast(message: string, duration: number = 3000): Promise<boolean> {
try {
// 尝试使用UIContext
if (this.uiContext) {
const promptActionInstance = this.uiContext.getPromptAction();
promptActionInstance.showToast({ message, duration });
console.log(`Toast显示成功: ${message}`);
return true;
}
// 备用方案:尝试在主线程中执行
return await this.showToastOnMainThread(message, duration);
} catch (error) {
console.error('显示Toast失败:', error);
return false;
}
}
// 在主线程中显示Toast
private async showToastOnMainThread(message: string, duration: number): Promise<boolean> {
return new Promise((resolve) => {
// 使用setTimeout确保在主线程的消息队列中执行
setTimeout(() => {
try {
promptAction.showToast({ message, duration });
console.log(`Toast显示成功(主线程): ${message}`);
resolve(true);
} catch (error) {
console.error('主线程显示Toast失败:', error);
resolve(false);
}
}, 0);
});
}
// 显示加载对话框
async showLoading(message: string = '加载中...'): Promise<boolean> {
try {
if (this.uiContext) {
const promptActionInstance = this.uiContext.getPromptAction();
promptActionInstance.showDialog({
title: '提示',
message: message,
buttons: [
{
text: '取消',
color: '#666666'
}
]
});
return true;
}
return false;
} catch (error) {
console.error('显示加载对话框失败:', error);
return false;
}
}
// 显示确认对话框
async showConfirm(
title: string,
message: string,
confirmText: string = '确定',
cancelText: string = '取消'
): Promise<boolean> {
return new Promise((resolve) => {
try {
if (this.uiContext) {
const promptActionInstance = this.uiContext.getPromptAction();
promptActionInstance.showDialog({
title: title,
message: message,
buttons: [
{
text: cancelText,
color: '#666666',
action: () => resolve(false)
},
{
text: confirmText,
color: '#007DFF',
action: () => resolve(true)
}
]
});
} else {
// 备用方案
promptAction.showDialog({
title: title,
message: message,
buttons: [
{
text: cancelText,
color: '#666666',
action: () => resolve(false)
},
{
text: confirmText,
color: '#007DFF',
action: () => resolve(true)
}
]
});
}
} catch (error) {
console.error('显示确认对话框失败:', error);
resolve(false);
}
});
}
}
// 在组件中使用
@Component
struct DeviceManagement {
aboutToAppear() {
// 初始化UIUpdateManager
const uiContext = getContext(this) as UIContext;
UIUpdateManager.getInstance().init(uiContext);
}
async syncDevices() {
// 显示加载中
await UIUpdateManager.getInstance().showLoading('同步设备中...');
try {
// 执行同步操作
const result = await this.performSync();
// 隐藏加载中(实际中需要更复杂的逻辑)
// 显示成功提示
await UIUpdateManager.getInstance().showToast(`同步成功: ${result.deviceCount}个设备`);
} catch (error) {
// 显示错误提示
await UIUpdateManager.getInstance().showToast('同步失败: ' + error.message);
}
}
async deleteDevice(deviceId: string) {
// 显示确认对话框
const confirmed = await UIUpdateManager.getInstance().showConfirm(
'删除设备',
'确定要删除这个设备吗?',
'删除',
'取消'
);
if (confirmed) {
// 执行删除操作
await this.performDelete(deviceId);
await UIUpdateManager.getInstance().showToast('设备删除成功');
}
}
// ... 其他方法
}
方案二:基于Promise的UI操作队列
// ✅ 高级方案:UI操作队列管理器
import { promptAction, UIContext } from '@kit.ArkUI';
class UIOperationQueue {
private static instance: UIOperationQueue;
private operationQueue: Array<() => Promise<void>> = [];
private isProcessing: boolean = false;
private uiContext: UIContext | null = null;
static getInstance(): UIOperationQueue {
if (!UIOperationQueue.instance) {
UIOperationQueue.instance = new UIOperationQueue();
}
return UIOperationQueue.instance;
}
// 初始化
init(context: UIContext): void {
this.uiContext = context;
}
// 添加UI操作到队列
enqueue(operation: () => Promise<void>): void {
this.operationQueue.push(operation);
this.processQueue();
}
// 处理队列
private async processQueue(): Promise<void> {
if (this.isProcessing || this.operationQueue.length === 0) {
return;
}
this.isProcessing = true;
while (this.operationQueue.length > 0) {
const operation = this.operationQueue.shift();
if (operation) {
try {
await operation();
} catch (error) {
console.error('UI操作执行失败:', error);
}
}
}
this.isProcessing = false;
}
// 显示Toast(队列版)
showToastQueued(message: string, duration: number = 3000): void {
this.enqueue(async () => {
await this.executeShowToast(message, duration);
});
}
// 执行显示Toast
private async executeShowToast(message: string, duration: number): Promise<void> {
return new Promise((resolve) => {
// 确保在主线程中执行
setTimeout(async () => {
try {
if (this.uiContext) {
const promptActionInstance = this.uiContext.getPromptAction();
promptActionInstance.showToast({ message, duration });
} else {
promptAction.showToast({ message, duration });
}
} catch (error) {
console.error('执行显示Toast失败:', error);
} finally {
resolve();
}
}, 0);
});
}
}
// 使用示例
@Component
struct AdvancedComponent {
aboutToAppear() {
const uiContext = getContext(this) as UIContext;
UIOperationQueue.getInstance().init(uiContext);
}
async complexOperation() {
// 多个UI操作会自动排队执行
UIOperationQueue.getInstance().showToastQueued('操作开始');
// 执行一些异步操作
await this.doAsyncWork1();
UIOperationQueue.getInstance().showToastQueued('第一步完成');
await this.doAsyncWork2();
UIOperationQueue.getInstance().showToastQueued('第二步完成');
await this.doAsyncWork3();
UIOperationQueue.getInstance().showToastQueued('所有操作完成');
}
// ... 其他方法
}
方案三:React式UI状态管理
// ✅ 现代方案:使用响应式状态管理
import { promptAction, UIContext } from '@kit.ArkUI';
@Component
struct ReactiveUIComponent {
@State toastMessage: string = '';
@State showToast: boolean = false;
@State toastDuration: number = 3000;
private uiContext: UIContext | null = null;
aboutToAppear() {
this.uiContext = getContext(this) as UIContext;
}
// 设置Toast消息
setToastMessage(message: string, duration: number = 3000): void {
this.toastMessage = message;
this.toastDuration = duration;
this.showToast = true;
}
// 监听showToast变化
onShowToastChange(): void {
if (this.showToast && this.toastMessage) {
this.displayToast();
}
}
// 实际显示Toast
private displayToast(): void {
// 使用setTimeout确保在UI更新周期后执行
setTimeout(() => {
try {
if (this.uiContext) {
const promptActionInstance = this.uiContext.getPromptAction();
promptActionInstance.showToast({
message: this.toastMessage,
duration: this.toastDuration
});
} else {
promptAction.showToast({
message: this.toastMessage,
duration: this.toastDuration
});
}
} catch (error) {
console.error('显示Toast失败:', error);
} finally {
// 重置状态
setTimeout(() => {
this.showToast = false;
this.toastMessage = '';
}, this.toastDuration);
}
}, 0);
}
async performAsyncOperation(): Promise<void> {
// 异步操作前设置状态
this.setToastMessage('操作开始...');
try {
await this.doAsyncWork();
this.setToastMessage('操作成功!');
} catch (error) {
this.setToastMessage('操作失败: ' + error.message);
}
}
build() {
// 监听状态变化
this.onShowToastChange();
Column() {
// UI内容
Button('执行操作')
.onClick(() => {
this.performAsyncOperation();
})
}
}
}
实战总结:异步UI操作的核心要点
1. 为什么UIContext能解决问题?
UIContext.getPromptAction()能解决异步中Toast不显示的问题,原因在于:
- 上下文绑定:
UIContext与具体的UI组件实例绑定 - 线程安全:通过
UIContext执行的操作会自动切换到正确的线程 - 生命周期感知:
UIContext知道当前组件的生命周期状态 - 窗口关联:
UIContext知道Toast应该显示在哪个窗口
2. 不同场景下的推荐方案
| 场景 | 推荐方案 | 代码复杂度 | 可靠性 |
|---|---|---|---|
| 简单异步回调 | 直接使用UIContext.getPromptAction() |
低 | 高 |
| 复杂异步链 | UIUpdateManager工具类 | 中 | 极高 |
| 高频UI更新 | UIOperationQueue队列管理 | 高 | 极高 |
| 响应式应用 | React式状态管理 | 中 | 高 |
3. 常见陷阱与避坑指南
陷阱1:在Promise链中混合使用
// ❌ 错误:混合使用
async function example() {
await step1();
promptAction.showToast({ message: '步骤1完成' }); // 可能失败
await step2();
uiContext.getPromptAction().showToast({ message: '步骤2完成' }); // 正确但不一致
await step3();
// 忘记显示Toast
}
✅ 正确:统一使用UIContext
// ✅ 正确:统一方式
async function example() {
const uiContext = getUIContext();
const prompt = uiContext.getPromptAction();
await step1();
prompt.showToast({ message: '步骤1完成' });
await step2();
prompt.showToast({ message: '步骤2完成' });
await step3();
prompt.showToast({ message: '所有步骤完成' });
}
陷阱2:忽略错误处理
// ❌ 错误:没有错误处理
try {
await asyncOperation();
promptAction.showToast({ message: '成功' });
} catch (error) {
// 错误处理中也没有Toast
console.error(error);
}
✅ 正确:完整的错误处理
// ✅ 正确:完整错误处理
try {
await asyncOperation();
await showToastSafe('操作成功');
} catch (error) {
console.error('操作失败:', error);
await showToastSafe(`操作失败: ${error.message}`);
}
async function showToastSafe(message: string): Promise<void> {
try {
const uiContext = getUIContext();
const prompt = uiContext.getPromptAction();
prompt.showToast({ message, duration: 3000 });
} catch (toastError) {
console.error('显示Toast失败:', toastError);
// 可以尝试备用方案或记录日志
}
}
4. 性能优化建议
- 避免频繁创建UIContext:在组件初始化时获取并复用
- 使用单例模式:对于工具类,使用单例避免重复初始化
- 批量UI操作:多个Toast可以合并显示
- 延迟显示:对于快速连续的操作,可以延迟显示Toast
// ✅ 性能优化示例
class OptimizedToastManager {
private static instance: OptimizedToastManager;
private uiContext: UIContext | null = null;
private toastQueue: string[] = [];
private isShowing: boolean = false;
private timer: number | null = null;
static getInstance(): OptimizedToastManager {
if (!OptimizedToastManager.instance) {
OptimizedToastManager.instance = new OptimizedToastManager();
}
return OptimizedToastManager.instance;
}
init(context: UIContext): void {
this.uiContext = context;
}
// 显示Toast(带去重和合并)
showToast(message: string): void {
// 去重:如果已经有相同的消息在队列中,不重复添加
if (!this.toastQueue.includes(message)) {
this.toastQueue.push(message);
}
// 延迟处理,避免频繁显示
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(() => {
this.processQueue();
}, 100) as unknown as number;
}
private processQueue(): void {
if (this.isShowing || this.toastQueue.length === 0) {
return;
}
this.isShowing = true;
const message = this.toastQueue.shift();
if (message && this.uiContext) {
try {
const prompt = this.uiContext.getPromptAction();
prompt.showToast({
message: message,
duration: 2000
});
// 显示完成后处理下一个
setTimeout(() => {
this.isShowing = false;
this.processQueue();
}, 2000);
} catch (error) {
console.error('显示Toast失败:', error);
this.isShowing = false;
this.processQueue();
}
}
}
}
结语:掌握UI上下文,驾驭异步世界
在HarmonyOS应用开发中,异步操作中的UI更新是一个常见但容易出错的问题。通过本文的深入分析和实战解决方案,我们掌握了:
- 问题本质:理解了UI上下文在异步操作中丢失的原因
- 解决方案:学会了使用
UIContext.getPromptAction()的正确方式 - 最佳实践:掌握了多种场景下的UI更新策略
- 性能优化:了解了如何优化异步UI操作的性能
记住,在HarmonyOS的异步世界中,UI上下文就是你的"导航仪"。有了它,无论代码执行到哪个线程、哪个回调,你都能准确地将UI更新"送达"到正确的目的地。
从今天起,告别异步中"隐身"的Toast,让你的每一个提示框都能准时、准确地出现在用户面前。这不仅提升了用户体验,也让你的代码更加健壮和可靠。
希望本文能帮助你在HarmonyOS应用开发中,轻松驾驭异步操作中的UI更新挑战,打造出更加流畅、稳定的优秀应用!https://developer.huawei.com/consumer/cn/doc/architecture-guides/tools-v1_2-ts_c91-0000002430270561
更多推荐



所有评论(0)