news 2026/7/25 5:00:56

【OpenHarmony/HarmonyOS】真实项目中的异常治理:hilog、Promise、Toast 与降级策略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【OpenHarmony/HarmonyOS】真实项目中的异常治理:hilog、Promise、Toast 与降级策略

【OpenHarmony/HarmonyOS】真实项目中的异常治理:hilog、Promise、Toast 与降级策略

“捕获了异常”不等于“处理了异常”。一款 HarmonyOS 游戏同时面对窗口初始化失败、Preferences 读写失败、DisplaySync 不可用、音频文件缺失、路由失败、图片选择取消和局域网发送失败。它们不能全部弹 Toast,也不能全部写一句console.error后继续。本文结合“迷宫坦克派对”的真实错误路径,建立从异常分类、日志结构、用户反馈到重试与降级的完整方法。🛡️

一、先把错误分成四类

异常治理的第一步不是选日志 API,而是判断失败后系统还能否履行承诺。

类型项目中的例子用户是否需要知道推荐动作
致命启动错误主页面loadContent失败错误页/退出提示、完整错误日志
可降级能力DisplaySync 创建失败、振动不支持通常不需要切换备用路径,记录一次告警
可重试业务错误云端提交、P2P 邀请发送失败视操作而定有界重试、明确失败状态
用户输入/权限问题未同意协议、相册授权失败可理解的 Toast 或页面提示

同一个catch中最关键的问题是:“接下来还能做什么?”如果答案是可以切换到setTimeout,这叫降级;如果只是把错误注释掉而仍对 UI 声称发送成功,那叫静默失败。

二、项目现在同时使用 hilog 与 console

Stage 模型的EntryAbility使用hilog记录生命周期:

constDOMAIN =0x0000; onCreate(want: Want, launchParam: AbilityConstant.LaunchParam):void{try{this.context.getApplicationContext() .setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); }catch(err) { hilog.error( DOMAIN,'testTag','Failed to set colorMode. Cause: %{public}s', JSON.stringify(err) ); } hilog.info(DOMAIN,'testTag','%{public}s','Ability onCreate'); }

而引擎、Manager 与页面多数使用console.info/warn/error。例如GameEngineGameLoopDataManager都带有模块前缀。这在开发阶段能工作,但长期会出现三个问题:

  1. testTag无法区分窗口、启动和数据初始化;
  2. console文本格式各异,不便按错误码和会话聚合;
  3. 多处直接JSON.stringify(error),既可能只得到{},也可能输出不该公开的数据。

治理并不要求一次性替换所有console。可以先统一字段和模块名,再逐步把系统生命周期、关键业务失败迁移到统一日志门面。

三、日志、用户提示和遥测是三条不同通道

flowchart TD A["捕获错误"] --> B{"是否影响当前操作?"} B --"否,可降级"--> C["记录 warn + 启用 fallback"] B --"是,可恢复"--> D["记录 error + 用户友好提示 + 重试入口"] B --"是,不可恢复"--> E["终止当前流程 + 错误页/返回"] C --> F["结构化诊断日志"] D --> F E --> F F --> G["脱敏遥测与聚合"]
  • 开发日志回答“哪里、什么时候、因为什么失败”;
  • 用户提示回答“刚才的操作有没有成功、我下一步能做什么”;
  • 遥测回答“这个错误影响多少设备、哪个版本开始增加”。

不能把开发异常对象直接交给用户,也不能用一句“操作失败”代替诊断上下文。

四、一个做得较好的降级:DisplaySync → setTimeout

GameLoop在构造阶段尝试创建DisplaySync

try{this.displaySync = displaySync.create();this.useDisplaySync =true; }catch(e) { console.warn('[GameLoop] DisplaySync not supported, falling back to setTimeout');this.useDisplaySync =false; }

启动DisplaySync失败时也会进入loopFallback()。这里具备完整降级的三个要素:

要素当前实现
能力探测尝试displaySync.create()
失败可观测记录 warning/error
备用实现使用setTimeout驱动循环

用户仍然可以进入游戏,因此没有必要连续弹 Toast。更进一步,可以只在一次会话中记录一次能力降级,并附上设备版本、目标 FPS 和 fallback 类型;不要每帧重复输出相同告警。

五、吞掉异常并不总是错,但必须知道代价

项目中有多处空catch或注释掉的日志:

try{this.displaySync.stop(); }catch(e) {// ignore}

停止一个本来就不可用的帧同步对象,忽略异常通常不会影响用户,属于“清理阶段尽力而为”。但 P2P 广播发送失败与音频播放失败也存在静默路径:

try{awaitthis.udpSocket.send(packet); }catch(e) {// Ignore broadcast errors}
soundPool.play(soundId, options).catch((e:Error)=>{//Play erroriscurrently suppressed });

两者影响不同:音效失败不应阻止战斗,适合低频告警与静音降级;发现广播持续失败会让附近玩家永远互相看不见,如果 UI 仍显示“正在发现”,就形成误导。

可以用以下规则判断是否允许静默:

  • 失败不会改变主要业务结果;
  • 已有可靠 fallback;
  • 不需要用户立刻修复;
  • 仍有聚合指标能发现高频失败;
  • catch 不会掩盖编程错误或数据损坏。

六、Toast 不能展示原始异常对象 ⚠️

设置页在语言切换异常时有如下路径:

}catch(e) { console.error("Failed to set language: "+JSON.stringify(e) );try{ promptAction.showToast({ message:'Error: '+JSON.stringify(e) }); }catch(inner) {} }

这会把开发细节暴露给用户。错误对象可能显示为{},也可能包含系统 API 名、路径或内部状态;文本长度还可能超出 Toast 的可读范围。

更合理的是稳定的用户文案加内部错误码:

const errorId ='SETTINGS-LANG-001'; logger.error('language_switch_failed', { errorId, targetLanguage: lang, cause: normalizeError(e) }); promptAction.showToast({ message:'语言切换失败,请稍后重试'});

需要客服协查时,可以在详情页显示短错误编号,而不是把完整异常塞进短暂 Toast。

七、Error 类型归一化,避免日志里全是{}

JavaScript/ArkTS 的catch值不一定是Error,也可能是字符串、业务错误对象甚至null。而标准Error.messagestack常常不是可枚举字段,JSON.stringify(new Error('x'))可能只得到{}

可以集中归一化:

interfaceNormalizedError {name:string;message:string; code?:string; }functionnormalizeError(error:Object):NormalizedError{constcandidate = errorasRecord<string,Object>;return{name:String(candidate['name'] ??'UnknownError'),message:String(candidate['message'] ?? error),code: candidate['code'] ===undefined?undefined:String(candidate['code']) }; }

在严格 ArkTS 环境中,可根据项目实际允许的联合类型调整签名。关键是统一提取允许记录的字段,不直接序列化整个未知对象。

八、隐私边界:URI、IP、昵称和授权回调都要脱敏

当前代码中有几类值得警惕的日志:

  • 头像选择成功后记录完整 URI;
  • P2P 接收邀请时记录昵称和发送方 IP;
  • 设备发现会序列化设备状态;
  • QQ 登录回调会序列化整个授权结果;
  • 短信函数原型会记录请求和验证码,这一问题会在下一篇安全文章单独展开。

QQ 管理器已经明确打印Mock Mode,说明当前是模拟模式而非真实 SDK 登录,但日志习惯一旦保留到真实接入,就可能输出 Token 或 OpenID。

数据是否建议记录原值替代方式
图片 URI记录来源类型、是否成功、文件扩展名
IP 地址调试期谨慎掩码或不可逆哈希,生产默认不记录
玩家昵称通常否玩家内部短 ID 或哈希
授权回调只记录 resultCode、provider、耗时
Token/验证码绝不只记录是否存在与生命周期状态

hilog格式中的 public/private 标记也要有意识使用。当前 Ability 错误使用%{public}s输出整个 JSON;生产代码应先白名单化,再决定字段是否可公开,而不是把“已使用 hilog”误当成自动脱敏。

九、给每次会话一个关联 ID

当一次游戏涉及页面、引擎、音频、数据和 P2P,多模块日志仅靠时间很难拼接。可以在进入一局时创建sessionId

interface LogContext { sessionId:string;module:string; action:string; } logger.info('game_started', { sessionId,module:'GameSession', action:'start', mode, difficulty });

关联 ID 不需要包含用户 ID、手机号或设备号。它只需在一次启动或一局游戏内唯一,并在进入网络请求、结算和异常路径时向下传递。

推荐的最小日志字段如下:

字段作用示例
event稳定事件名game_init_failed
level严重度warn/error
module所属模块GameLoop
sessionId串联一次会话随机短 ID
errorCode稳定分类LOOP-START-002
durationMs操作耗时数值
fallback是否降级setTimeout

十、Promise 的错误必须在职责边界收口

项目路由常使用:

router.replaceUrl({url:'pages/Index',params: {isLoggedIn:true} }).catch((err:Error) =>{console.error(`[StartPage] Failed to replace url. `+`Code:${err.name}, Message:${err.message}`); });

它避免了未处理的 Promise rejection,但仍缺少用户层结果:路由失败后页面停在哪里?按钮是否恢复可点击?是否允许重试?

一个完整的异步操作通常需要:

this.isLoading =true;try{await router.pushUrl({ url:'pages/SettingsPage'}); }catch(error) { logger.error('open_settings_failed', { cause: normalizeError(erroras Object) }); promptAction.showToast({ message: '暂时无法打开设置' }); }finally{this.isLoading =false; }

finally防止 loading 永久不消失。只有调用方知道按钮、页面与用户预期,所以 Promise 错误应在最靠近业务动作的边界收口;底层 Manager 可以抛出带错误码的异常,但不应自行弹 UI。

十一、重试必须有上限、退避和幂等性

并非所有错误都适合立即重试:参数错误、权限拒绝、Schema 不兼容,重试多少次都没用;短暂网络断开或服务繁忙才适合重试。

asyncfunctionretry<T>(task:() =>Promise<T>,maxAttempts:number=3):Promise<T> {letlastError:Object=newError('unknown');for(letattempt =1; attempt <= maxAttempts; attempt++) {try{returnawaittask(); }catch(error) { lastError = errorasObject;if(attempt < maxAttempts) {awaitdelay(200*Math.pow(2, attempt -1)); } } }throwlastError; }

提交分数、发放晶石、创建房间一类写操作还要带幂等键,否则重试可能重复入账。P2P 状态广播则通常“新帧覆盖旧帧”,没有必要重发每一个旧包。

十二、不同模块的推荐策略

模块失败策略用户反馈日志级别
EntryAbility.loadContent终止启动流程错误页或系统级提示error/fatal
Preferences 读取使用明确默认值通常不打扰warn
Preferences 保存保留脏状态、稍后重试关键资料可提示error
DisplaySync切换计时器不提示warn,一次
音效/振动静音或无触感继续设置页可显示不可用warn/metric
头像选择保留旧头像权限或读取失败提示warn
P2P 邀请标记发送失败、允许重试明确提示error
Canvas 单帧绘制跳过异常帧并计数高频时结束会话error,限频

游戏引擎的 render catch 当前会输出错误并继续。这样能防止一次绘制异常直接终止,但如果每帧都报错,日志会被淹没且用户只看到黑屏。建议增加连续失败计数:偶发一次跳帧,连续超过阈值后停止循环并进入可恢复错误界面。

十三、建立一个轻量日志门面

统一日志门面不是为了制造复杂框架,而是把模块名、脱敏、错误归一化和环境策略集中起来:

classAppLogger{ info(event:string, fields: Record<string, Object>):void{ console.info(JSON.stringify({event, ...fields })); } error(event:string, fields: Record<string, Object>):void{ console.error(JSON.stringify({event, ...fields })); } }

真实落地时还应:开发构建允许更多诊断字段,发布构建关闭详细网络与设备日志;相同错误做采样和限频;崩溃前尽可能刷出关键事件;日志保留周期与上传行为写入隐私说明。

十四、验证异常路径,而不是只测成功路径 🧪

建议为以下场景建立故障注入:

  1. DisplaySync.create()抛错,确认备用循环启动且只告警一次;
  2. 模拟 Preferences 读取损坏 JSON,确认使用默认值且不覆盖原数据;
  3. 模拟路由 Promise reject,确认 loading 恢复、Toast 可理解;
  4. 让音效播放失败,确认游戏循环不受影响;
  5. 让 P2P 广播连续失败,确认 UI 不会永远显示“发现中”;
  6. 传入包含敏感字段的授权回调,确认日志只保留结果码;
  7. 连续触发 Canvas render error,确认有限流和终止阈值。

异常测试的验收标准不仅是“不崩溃”,还包括状态不悬挂、用户不被误导、日志能关联、敏感字段不外泄。

十五、总结 ✨

“迷宫坦克派对”已经具备多层错误处理:Ability 生命周期使用hilog,GameLoop 对 DisplaySync 有真实 fallback,Manager 和页面也普遍捕获 Promise/同步异常。但当前仍存在结构不统一、原始异常进入 Toast、完整 URI/IP/授权结果可能被记录,以及部分 P2P、音频异常被静默吞掉等问题。

异常治理的核心不是让每一行都包上try/catch,而是明确失败后的产品行为:能降级就记录一次并切换备用能力;影响操作就告诉用户结果和下一步;不可恢复就停止错误链路;所有日志都使用稳定事件名、关联 ID、错误码与字段白名单。这样,日志才能帮助定位问题,Toast 才不会泄露开发细节,fallback 也不再只是“假装没出错”。🔧


推荐标签:OpenHarmonyHarmonyOSArkTShilog异常处理Promise日志治理降级策略

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/25 4:59:24

2026年AI论文写作工具全流程指南与实战技巧

1. 论文写作的痛点与AI解决方案写论文这件事&#xff0c;从选题到最终定稿&#xff0c;每个环节都让学术人头疼不已。选题阶段找不到创新点&#xff0c;文献综述时被海量资料淹没&#xff0c;写作过程中语言表达不流畅&#xff0c;格式调整更是耗费大量时间。这些痛点我深有体会…

作者头像 李华
网站建设 2026/7/25 4:58:49

AI如何提升学术写作效率:智能工具实战指南

1. 项目背景与核心价值作为一名经历过毕业论文折磨的老学长&#xff0c;我深知学术写作的痛苦。从选题开题到文献综述&#xff0c;从数据收集到格式调整&#xff0c;每个环节都能让研究生脱一层皮。去年帮导师审阅本科生论文时&#xff0c;发现80%的时间都花在格式纠错和语言润…

作者头像 李华
网站建设 2026/7/25 4:56:58

Python静态类型:看似多余,却能拯救你90%的生产级bug

一、写多年&#xff0c;你可能一直在踩同一个坑诸多开发者钟情于它, 源于它身为备受欢迎的编程语言之一所具备的“灵活”特性, 无需声明变量类型, 凭借一行代码便可迅速达成功能, 无论脚本编写还是原型开发, 效率均得以充分展现。然而, 正是这般“灵活”, 暗地里藏匿着致使无数…

作者头像 李华
网站建设 2026/7/25 4:56:19

PSO优化BP神经网络:原理、实现与实战应用

1. 项目背景与核心价值在机器学习领域&#xff0c;BP神经网络因其强大的非线性拟合能力被广泛应用于各类预测和分类任务。但传统BP算法存在两个致命缺陷&#xff1a;一是依赖初始权值和阈值的随机初始化&#xff0c;容易陷入局部最优&#xff1b;二是训练过程中梯度下降法收敛速…

作者头像 李华
网站建设 2026/7/25 4:54:04

YOLOv8集成Triplet Attention:轻量化目标检测性能提升方案

1. 项目背景与核心价值在目标检测领域&#xff0c;YOLO系列算法一直以其实时性和高效性著称。YOLOv8作为该系列的最新版本&#xff0c;在精度和速度之间取得了更好的平衡。然而&#xff0c;随着应用场景的复杂化&#xff0c;如何在保持模型轻量化的同时进一步提升检测精度&…

作者头像 李华
网站建设 2026/7/25 4:53:46

AI实践报告生成工具:百考通AI的技术与应用

1. 项目概述 "百考通AI&#xff1a;实践报告智能生成"是一款面向大学生和职场新人的智能写作辅助工具。作为一名在教育科技领域摸爬滚打多年的从业者&#xff0c;我深知实习报告、实践总结这类文书写作的痛点——既要体现专业性&#xff0c;又要避免千篇一律的模板化…

作者头像 李华