HarmonyOS 应用开发《掌上英语》第44篇:错误边界与异常处理——ArkTS 的 try-catch 实践 错误边界与异常处理——ArkTS 的 try-catch 实践一、为什么需要显式异常处理ArkTS 是静态类型语言编译期可以捕获大部分类型错误。但运行时错误网络 IO、文件访问、数据解析异常等是编译期无法预见的。在这些场景下不处理异常会导致应用直接崩溃。在本项目中异常处理遵循一个黄金法则所有可能出错的系统调用都要包裹在 try-catch 中。这个法则体现在 PreferenceUtil、AudioPlayer、各个 Manager 以及页面组件中。二、PreferenceUtil 中的异常防御PreferenceUtil 是所有数据持久化的基础设施它的稳定性至关重要privateconstructor(context:Context,fileName:string){try{preferences.removePreferencesFromCacheSync(context,fileName)this.dataPreferencespreferences.getPreferencesSync(context,{name:fileName})Logger.info(PreferenceUtil:${fileName}init:${this.dataPreferences!null})}catch(e){Logger.error(PreferenceUtil error :${JSON.stringify(e)})}}publicput(key:string,value:preferences.ValueType){if(!this.dataPreferences){Logger.info(PreferenceUtil: dataPreferences is null)return;// 防御性返回}try{this.dataPreferences?.putSync(key,value)this.dataPreferences?.flush()}catch(e){Logger.info(PreferenceUtil: put error:${JSON.stringify(e)})}}双重防御设计在方法入口处判断dataPreferences是否为空为空则提前返回避免调用空指针上的方法核心操作包裹 try-catch即使 preferences 操作抛出异常应用也不会崩溃这种设计在get、delete、clear、hasSync、getAllSync等方法中一致贯彻。三、AudioPlayer 中的异常处理音频播放涉及媒体框架调用是另一个高风险的 IO 操作publicstaticgetInstance():AudioPlayer{if(!AudioPlayer.instance){AudioPlayer.instancenewAudioPlayer();AudioPlayer.instance.initPlayer();// initPlayer 内部有 try-catch}returnAudioPlayer.instance;}privateasyncinitPlayer():Promisevoid{try{this.avPlayerawaitmedia.createAVPlayer();this.initPlayerEvents();}catch(e){Logger.error(AudioPlayer,初始化播放器失败:${JSON.stringify(e)});}}每个 public 方法都有独立的 try-catchpublicplay(url:string):void{if(!url||url){Logger.warn(AudioPlayer,音频URL为空);return;// 参数校验防御性返回}try{if(!this.avPlayer){return;// 空指针防御}// ... 播放逻辑}catch(e){Logger.error(AudioPlayer,播放失败:${JSON.stringify(e)});}}这里展示了三重防御参数校验空 URL 直接返回空值判断avPlayer 未初始化则跳过try-catch 兜底所有未知异常被捕获四、Manager 业务层的异常处理4.1 NewWordManagerpublicaddNewWord(topicItem:TopicItemType,wordPackage:string默认词汇包):boolean{try{letwords:NewWordItem[]this.getAllNewWords();// ... 业务逻辑this.saveNewWords(words);returntrue;}catch(e){Logger.error(NewWordManager,添加生词失败:${JSON.stringify(e)});returnfalse;// 失败时返回 false调用方可以感知}}关键设计失败时返回false。调用方可以根据返回值判断操作是否成功从而给予用户反馈。4.2 StatisticsManagerpubliccalculateStatistics(ques:TopicItemType[],duration:number):PracticeStatistics{try{// ... 复杂的计算逻辑returnstatistics;}catch(e){Logger.error(StatisticsManager,计算统计数据失败:${JSON.stringify(e)});returnthis.getEmptyStatistics();// 返回空数据不阻塞流程}}当统计计算失败时返回空统计数据而不是抛异常。这保证了答题流程的连续性——用户就算看不到统计也能继续学习。4.3 LearningPlanManagerpublicgetLearningPlan():LearningPlan{try{letplanPreferenceUtil.getInstance().get(this.LEARNING_PLAN_KEY,undefined)asLearningPlan;if(!plan){planthis.cloneLearningPlan(DEFAULT_LEARNING_PLAN);this.saveLearningPlan(plan);}returnplan;}catch(e){Logger.error(LearningPlanManager,获取学习计划失败:${JSON.stringify(e)});returnthis.cloneLearningPlan(DEFAULT_LEARNING_PLAN);// 降级到默认值}}降级策略当无法从存储中读取计划时直接返回默认计划。用户可能丢失之前的设置但应用不会崩溃可以继续使用。五、页面组件中的异常处理在 AnswerQuestionsPage 中记录学习数据使用了 try-catchrecordStudyData(){try{lettopicItemTypeListthis.toTopicItemTypeList(this.ques);StatisticsManager.getInstance().calculateStatistics(topicItemTypeList,this.practiceDuration);letplanManagerLearningPlanManager.getInstance();letansweredCountthis.ques.filter(itemitem.isAnswer).length;planManager.recordWordStudy(answeredCount);}catch(e){Logger.error(AnswerQuestionsPage,记录学习数据失败:${JSON.stringify(e)});}}这里将所有 Manager 调用合并到一个 try-catch 块中。原因是不管哪个 Manager 出错结果都是学习记录失败且都不应该影响用户跳转到报告页面。六、异常处理模式总结6.1 四种降级策略策略示例适用场景返回空值return []、return null获取列表数据时返回默认值return DEFAULT_LEARNING_PLAN获取配置数据时返回空对象return this.getEmptyStatistics()获取统计摘要时返回 falsereturn false增删改操作时6.2 分层处理原则UI 层 → 可选 try-catch关注用户体验 ↓ Manager 层 → 必须 try-catch使用降级策略 ↓ 基础设施层 → 必须 try-catch确保自身稳定 ↓ 系统 API → 可能抛出 BusinessError基础设施层PreferenceUtil、AudioPlayer捕获所有异常确保自身状态一致Manager 层捕获异常后返回降级值向上层提供稳定接口UI 层可以省略 try-catch如果 Manager 已经处理了或额外包裹以记录页面级日志6.3 异常日志要求所有 catch 块中必须记录日志且使用统一的格式Logger.error(ClassName,方法名失败:${JSON.stringify(e)});这确保了异常的可追溯性。在 Hilog 中ERROR 级别的日志有独立的缓冲区即使 DEBUG 和 INFO 日志被清空ERROR 日志也会保留。七、总结从 PreferenceUtil 到 AnswerQuestionsPage本项目的异常处理构建了一道严密的防线。每一个 try-catch 块都搭配了降级策略确保任一模块的临时故障都不会波及整个应用的正常运行。这体现了防御性编程的思想——不信任外部输入不假定系统 API 永远成功始终保持应用的可用性。