HarmonyOS 应用开发《掌上英语》第42篇:首选项工具链——从 PreferenceUtil 到业务 Manager
2026/7/24 23:58:42 网站建设 项目流程

首选项工具链——从 PreferenceUtil 到业务 Manager

一、三层架构概览

在本项目中,数据持久化被设计为清晰的三层架构:

基础层:PreferenceUtil → 封装 @kit.ArkData preferences 的 CRUD 常量层:PreferConstant → 统一定义存储 Key 业务层:NewWordManager / LearningPlanManager / StatisticsManager / DashboardManager → 封装业务语义

这种分层带来了几个关键好处:

  • 关注点分离:Manager 层无需关心数据怎么存,只关心数据的业务含义
  • 复用性:所有 Manager 共享同一个 PreferenceUtil 实例化逻辑
  • 可维护性:Key 集中管理,避免字符串散落在各处

二、PreferenceUtil 基础层

2.1 单实例与多文件管理

exportclassPreferenceUtil{privatestaticpreferenceRecord:Map<string,PreferenceUtil>=newMap()privatedataPreferences:preferences.Preferences|null=nullprivateconstructor(context:Context,fileName:string){try{preferences.removePreferencesFromCacheSync(context,fileName)this.dataPreferences=preferences.getPreferencesSync(context,{name:fileName})}catch(e){Logger.error(`PreferenceUtil error :${JSON.stringify(e)}`)}}publicstaticgetInstance(fileName:string='default'){if(PreferenceUtil.preferenceRecord.has(fileName)){returnPreferenceUtil.preferenceRecord.get(fileName)!!}letpreferenceUtil:PreferenceUtil=newPreferenceUtil(GlobalContextUtils.globalContext,fileName);PreferenceUtil.preferenceRecord.set(fileName,preferenceUtil);returnpreferenceUtil}// ...}

多文件策略getInstance(fileName)允许以不同的文件名创建独立的 Preferences 实例。默认使用'default'作为文件名,对于像笔记这种需要隔离的数据(PreferConstant.TOPIC_NOTES),可以创建独立的文件实例。

缓存机制preferenceRecord是一个 Map,相同的 fileName 不会重复创建实例,避免了多次打开同一个 Preferences 文件的开销。

2.2 CRUD 方法

PreferenceUtil 提供了完整的增删改查接口:

// 写入publicput(key:string,value:preferences.ValueType){this.dataPreferences?.putSync(key,value);this.dataPreferences?.flush();// 立即写入磁盘}// 读取publicget(key:string,defaultValue?:preferences.ValueType){returnthis.dataPreferences?.getSync(key,defaultValue);}// 检查存在publichasSync(key:string):boolean{returnthis.dataPreferences?.hasSync(key);}// 删除publicdelete(key:string){this.dataPreferences?.deleteSync(key);this.dataPreferences?.flush();}// 清空publicclear(){this.dataPreferences.clearSync();this.dataPreferences?.flush();}

关键设计点:

  • 同步 API:使用getSync/putSync等同步方法,简化调用链;同步操作在数据量小时性能足够
  • flush 策略:每次 put/delete 后立即 flush,确保数据不丢失
  • ValueType 泛化preferences.ValueType支持 string、number、boolean、Array、Object 等类型,足够覆盖所有业务场景

三、PreferConstant 常量层

所有存储 Key 集中在PreferConstant类中:

exportclassPreferConstant{staticreadonlyTOPIC_NOTES:string='TOPIC_NOTES';staticreadonlyERROR_RECORDS:string='ERROR_RECORDS';staticreadonlyEXAM_PREFER_COLLECT:string='EXAM_PREFER_COLLECT';staticreadonlyFEEDBACK_RECORD:string='FEEDBACK_RECORD';staticreadonlyFIRST_LAUNCH:string='FirstLaunch';staticreadonlyCOLOR_MODE:string='Color_Mode';staticreadonlyDAILY_CHALLENGE_DATA:string='DAILY_CHALLENGE_DATA';}

这样做的好处:

  • 避免魔法字符串:所有 Key 有明确的语义名称
  • 集中管理:修改 Key 只需要改一个地方
  • 可发现性:新开发者可以通过 PreferConstant 快速了解应用存储了哪些数据

四、业务 Manager 层

4.1 NewWordManager

NewWordManager 使用 Preferences 存储生词列表:

exportclassNewWordManager{privateNEW_WORD_KEY='NEW_WORDS';publicgetAllNewWords():NewWordItem[]{try{letwords=PreferenceUtil.getInstance().get(this.NEW_WORD_KEY,[])asNewWordItem[];returnwords||[];}catch(e){Logger.error('NewWordManager',`获取生词列表失败:${JSON.stringify(e)}`);return[];}}publicaddNewWord(topicItem:TopicItemType,wordPackage:string='默认词汇包'):boolean{letwords:NewWordItem[]=this.getAllNewWords();letexistIndex=words.findIndex(item=>item.keyID===topicItem.keyID);if(existIndex>=0){words[existIndex].addTime=Date.now();this.saveNewWords(words);returnfalse;}letnewWord:NewWordItem={keyID:topicItem.keyID,title:topicItem.title,// ... 其他字段addTime:Date.now(),masteryLevel:0,wordPackage:wordPackage};words.push(newWord);this.saveNewWords(words);returntrue;}privatesaveNewWords(words:NewWordItem[]):void{PreferenceUtil.getInstance().put(this.NEW_WORD_KEY,words);}}

NewWordManager 的职责是"生词本"的业务语义——去重、掌握程度更新、按词汇包筛选。它把整个生词列表作为一个数组存储在 Preferences 中。

4.2 StatisticsManager

StatisticsManager 存储聚合的统计数据:

publiccalculateStatistics(ques:TopicItemType[],duration:number):PracticeStatistics{// 计算各个指标// ...this.saveStatistics(statistics);returnstatistics;}privatesaveStatistics(statistics:PracticeStatistics):void{lethistoryStats=this.getHistoryStatistics();letmergedStats=this.mergeStatistics(historyStats,statistics);PreferenceUtil.getInstance().put(this.STATISTICS_KEY,mergedStats);}

与 NewWordManager 不同的是,StatisticsManager 保存的是经过聚合计算的统计对象,而不是原始数据列表。它体现了"写时计算"的思路:在每次完成练习后即时计算并累加统计数据。

4.3 LearningPlanManager

LearningPlanManager 存储的是结构化的学习计划对象:

publicgetLearningPlan():LearningPlan{try{letplan=PreferenceUtil.getInstance().get(this.LEARNING_PLAN_KEY,undefined)asLearningPlan;if(!plan){plan=this.cloneLearningPlan(DEFAULT_LEARNING_PLAN);this.saveLearningPlan(plan);}returnplan;}catch(e){Logger.error('LearningPlanManager',`获取学习计划失败:${JSON.stringify(e)}`);returnthis.cloneLearningPlan(DEFAULT_LEARNING_PLAN);}}

注意它的"懒初始化 + 默认值"模式:如果 Preferences 中还没有数据(首次使用),就使用DEFAULT_LEARNING_PLAN创建一份默认计划。

4.4 各层调用关系

页面组件 (CourseHomePage) ↓ 业务语义层 (NewWordManager.getInstance().addNewWord(...)) ↓ 基础存储层 (PreferenceUtil.getInstance().put('NEW_WORDS', data)) ↓ 系统 API (preferences.getPreferencesSync / putSync / flush)

五、架构优势总结

  1. 替换成本低:如果将来需要从 Preferences 迁移到数据库,只需要改 PreferenceUtil 的实现,Manager 层无需改动
  2. 测试友好:可以用 Mock 的 Preferences 替换真实实例进行单元测试
  3. 统一错误处理:PreferenceUtil 中统一 try-catch,Manager 层不再需要关注底层存储异常
  4. 线程安全:Preferences 是线程安全的,多 Manager 并发写入不会冲突

从 PreferenceUtil 到业务 Manager 的工具链设计,体现了"高内聚低耦合"的经典原则。每一层各司其职,下层不知道上层的业务含义,上层不关心下层的实现细节,是值得借鉴的分层架构范式。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询