鸿蒙 PC Markdown 编辑器设置持久化模型:Preferences、类型收敛与失败降级

发布时间:2026/7/23 16:45:52

鸿蒙 PC Markdown 编辑器设置持久化模型:Preferences、类型收敛与失败降级 鸿蒙 PC Markdown 编辑器设置持久化模型Preferences、类型收敛与失败降级编辑器设置的价值不在于面板上能点而在于选择能够跨会话稳定恢复又不会因为旧值、损坏值或写入失败破坏文档工作流。OhMarkdown 当前持久化自动保存策略、图片资源目录、图片拖放语义、界面语言策略和侧栏宽度。这五类数据形态不同却共享同一个小型 Preferences 文件和同一套设计原则稳定键、有限枚举、读取解析、显式 flush、UI 先行与失败可见。实现位于公开仓库 https://gitcode.com/VON-/codex_md_oh。自动保存与图片设置来自 G3 提交57aea97、0a02ce3、89a5e57语言设置提交为ed13ee0侧栏宽度提交为358eb3f当前布局验证基线为0d8d38b。本文只讨论已经落地的 Preferences不把搜索选项、主题同步或云配置写成现有持久化能力。先区分设置、文档与恢复数据Preferences 适合少量、低频、可独立回退的用户偏好。自动保存策略是一个枚举图片目录和拖放模式是枚举语言是策略值侧栏宽度是有限数字。它们即使丢失也只恢复默认不应造成文档内容损坏。Markdown 正文、撤销历史、未保存恢复记录和保存前备份不属于设置。正文通过文件系统和安全保存事务处理恢复记录写到应用私有文件并有代际控制保存前备份保留原内容与编码格式。把这些数据塞入 Preferences 会遇到容量、事务、序列化和恢复一致性问题。这种分离建立了风险等级设置写失败可以继续当前会话并提示文档保存失败必须回滚或保留备份。统一存储 API不代表统一错误语义。一个文件与五个稳定键SettingsService.ets统一使用ohmarkdown-settings每项拥有不会随界面翻译变化的 kebab-case 键。键是升级契约不使用中文标签也不使用组件位置。constSETTINGS_FILE_NAME:stringohmarkdown-settings;constAUTO_SAVE_POLICY_KEY:stringauto-save-policy;constASSET_DIRECTORY_RULE_KEY:stringasset-directory-rule;constASSET_DROP_MODE_KEY:stringasset-drop-mode;constAPPLICATION_LANGUAGE_KEY:stringapplication-language;constSIDEBAR_WIDTH_KEY:stringsidebar-width;集中定义避免不同 Builder 写错键也方便审查已有配置。使用一个文件减少 Preferences 实例数量五项彼此独立每次put只改变单键。当前没有 schema version因为所有值都有解析回退且尚无需要跨键原子迁移的结构。未来若设置关系变复杂例如导出模板包含多字段或快捷键映射需要版本化应单独设计 schema而不是继续把任意 JSON 字符串堆进同一文件。枚举值是持久化协议自动保存使用off、after-delay、on-focus-loss图片拖放使用 copy、move、reference语言使用 default、zh-Hans-CN、en-US。界面显示可以是“关闭”“延迟保存”“失焦保存”但写入值永远不随语言变化。exportenumAutoSavePolicy{OFFoff,AFTER_DELAYafter-delay,ON_FOCUS_LOSSon-focus-loss}exportfunctionparseAutoSavePolicy(value:preferences.ValueType):AutoSavePolicy{if(valueAutoSavePolicy.AFTER_DELAY){returnAutoSavePolicy.AFTER_DELAY;}if(valueAutoSavePolicy.ON_FOCUS_LOSS){returnAutoSavePolicy.ON_FOCUS_LOSS;}returnAutoSavePolicy.OFF;}解析采用白名单而不是类型断言。未知字符串、数字或旧版本值都回到 OFF。自动保存默认关闭是保守选择设置损坏时不应突然开始写用户文件。图片拖放默认 COPY同样比 MOVE 更安全语言默认跟随系统侧栏默认 264 vp。枚举一旦写入用户配置就成为兼容协议。重命名界面文字无影响重命名枚举值则需要迁移。开发者不能把内部变量名变化直接传播到存储值。不同设置有不同的安全默认值parseAssetDirectoryRule只在值明确等于SHARED_ASSETS时选择共享目录否则回到文档专属 assets。这样未知值不会让图片跨文档共享。parseAssetDropMode只接受 MOVE 和 REFERENCE其他回到 COPY避免意外删除源文件或引用越界。exportfunctionparseAssetDropMode(value:preferences.ValueType):AssetDropMode{if(valueAssetDropMode.MOVE){returnAssetDropMode.MOVE;}if(valueAssetDropMode.REFERENCE){returnAssetDropMode.REFERENCE;}returnAssetDropMode.COPY;}默认值不是统一的“第一个选项”而是按失败后果选择。语言未知值回跟随系统不锁死在某个错误地区侧栏非法值回默认不使用 0自动保存未知值回关闭资源目录回文档隔离拖放回复制。这体现了“设置失败不能扩大破坏面”。解析函数纯粹、同步便于 UnitTestBuild 覆盖。I/O 函数只负责 get/put领域安全由解析层承担职责清晰。数字设置必须拒绝 NaN 与 Infinity侧栏宽度是唯一数字设置。只判断typeof value number不够因为 NaN 和 Infinity 也属于 number会污染 ArkUI 布局。解析先用Number.isFinite再钳制 220 至 480。exportconstDEFAULT_SIDEBAR_WIDTH:number264;exportconstMIN_SIDEBAR_WIDTH:number220;exportconstMAX_SIDEBAR_WIDTH:number480;exportfunctionparseSidebarWidth(value:preferences.ValueType):number{if(typeofvalue!number||!Number.isFinite(value)){returnDEFAULT_SIDEBAR_WIDTH;}returnMath.max(MIN_SIDEBAR_WIDTH,Math.min(MAX_SIDEBAR_WIDTH,value));}存储层范围还不是最终有效宽度。WorkspaceShell 加载后按当前窗口预算再次 clamp保证编辑器至少保留 520 vp。静态解析保护数据动态 clamp 保护当前布局两者不能互相替代。写入也调用parseSidebarWidth。即使未来某个调用点忘记 clamp持久层仍不会保存越界值。双重验证对边界设置是合理冗余。语言保存的是策略而非解析结果语言是最容易错误持久化的设置。用户选择“跟随系统”时当前界面可能解析为中文但 Preferences 必须写default。若写zh-Hans-CN重启后无法区分跟随与固定中文系统改成英文也不会响应。exportasyncfunctionloadApplicationLanguage(context:Context):PromiseApplicationLanguage{constsettingsawaitpreferences.getPreferences(context,SETTINGS_FILE_NAME);constvalueawaitsettings.get(APPLICATION_LANGUAGE_KEY,ApplicationLanguage.SYSTEM);returnparseApplicationLanguage(String(value));}解析兼容zh...与en...未知值回 SYSTEM。设置面板选中态绑定持久策略applicationLanguage当前资源语言绑定 Ability 提供的language。这两个状态分离才可能在中文系统下正确显示“跟随系统 selectedtrue”。模拟器选择跟随系统后强制停止并重启辅助功能树仍返回该项选中这是 Preferences 语义正确的直接证据。每次写入都显式 flush保存函数模式一致获取同一 Preferencesput 单键然后 flush。flush 的意义是在方法 resolve 前请求持久落盘而不是只更新内存缓存。用户可能切换设置后立刻关闭或强制停止应用语言与侧栏测试正是这样验证。exportasyncfunctionsaveSidebarWidth(context:Context,width:number):Promisevoid{constsettingsawaitpreferences.getPreferences(context,SETTINGS_FILE_NAME);awaitsettings.put(SIDEBAR_WIDTH_KEY,parseSidebarWidth(width));awaitsettings.flush();}写入不是跨五个键的事务。每次用户动作只改一个设置单键 flush 足够。若未来提供“应用/取消”式设置对话框需要考虑一次提交多项、失败回滚和 schema 版本不能继续假设独立即时保存。拖动侧栏只在 gesture end flush避免每帧 I/O方向键每次 16 vp 是独立完成动作立即 flush。自动保存、资源模式和语言点击频率低直接保存即可。一次性异步初始化WorkspaceShell 在编辑器 Ready 后调用initializeDocumentReliability用settingsInitialized防止重复加载。五项并行式发起各自 Promise成功更新对应 State失败设置各自默认。if(!this.settingsInitialized){this.settingsInitializedtrue;constcontextthis.getHostContext();if(context){loadApplicationLanguage(context).then((language){this.applicationLanguagelanguage;}).catch((){this.applicationLanguageApplicationLanguage.SYSTEM;});loadSidebarWidth(context).then((width){this.sidebarWidththis.clampSidebarWidth(width);}).catch((){this.sidebarWidththis.clampSidebarWidth(DEFAULT_SIDEBAR_WIDTH);});}}单项失败不阻止其他设置恢复也不阻止文档可靠性定时器启动。语言策略读失败不会关闭自动保存读取侧栏失败不会影响图片模式。这样的故障隔离比Promise.all一处 reject 后全部回默认更稳健。初始状态在代码中已经是安全默认所以异步读取期间界面可用。读取完成可能产生一次从 264 到保存宽度的布局调整当前设备可接受。若未来追求完全无跳动可在首屏前读取但会增加启动阻塞需要测量权衡。UI 先更新持久化失败单独报告设置点击通常先更新内存状态让界面即时反馈再异步保存。保存失败不强制回滚视觉选择因为平台资源或拖放策略可能已经作用于当前会话回滚还可能触发第二次失败。状态栏会说明无法保存重启后可能恢复旧值。语言更新先调用平台 API再修改选中态和 WebPreferences 失败显示“无法保存界面语言设置”。侧栏拖动结束后保存失败显示“无法保存侧栏宽度”。图片模式保存成功时显示下一次拖放语义失败显示 Unable to save。privatepersistSidebarWidth():void{constcontextthis.getHostContext();if(!context)return;saveSidebarWidth(context,this.sidebarWidth).catch((){this.operationStatusresolveEditorLanguage(this.language)zh-CN?无法保存侧栏宽度:Unable to save sidebar width;});}这是一致性中的“会话优先”模型当前状态立即生效持久状态尽力跟上并对失败可见。文档保存不能采用同样轻量策略因为内容落盘失败必须有备份与恢复设置风险较低模型可以更简单。自动保存策略影响文件时序虽然设置值很小自动保存策略的作用很大。恢复 AFTER_DELAY 后需要立即根据当前 dirty、URI、冲突和 operation 状态决定是否安排定时器不能只更新按钮。初始化成功回调因此调用scheduleDelayedAutoSave()。策略更新时先取消旧定时器再为 AFTER_DELAY 安排新任务ON_FOCUS_LOSS 由失焦路径触发OFF 不产生自动写。保存策略值本身不代表自动保存一定执行文档状态机仍检查未命名文档、外部冲突、Mixed EOL 和进行中的操作。Preferences 只提供“用户希望什么”文件可靠性层决定“当前是否安全执行”。这种边界防止一个设置值绕过冲突保护。设置文章必须说明其业务效应而不能只展示 get/put。图片设置必须服从安全边界图片目录规则选择文档专属.assets或共享assets拖放模式选择复制、移动、仅引用。Preferences 恢复这些选择但AssetService仍验证路径、MIME、大小、符号链接和授权目录。即使设置为 REFERENCE拖入外部图片也不会绕过边界服务会拒绝“不在受管理资源目录”。MOVE 先复制成功再删除源删除失败会删除目标回滚。设置只是请求语义不是安全授权。这说明持久化枚举不能被服务层盲信。用户选择和系统事实是两回事选择可以跨重启文件路径每次都必须重新验证。默认 COPY 进一步降低未知设置导致源文件删除的风险。真实应用截图与持久化证据下面截图来自 MateBook Pro 2in1 模拟器设置面板同时展示界面语言、自动保存、图片资源目录和拖放模式。它证明多个设置在同一原生侧栏中有清晰互斥状态而不是散落在不同页面。语言选择 English 后强制停止并重启英文仍选中最终切回跟随系统并重启辅助功能树返回selectedtrue。侧栏拖到 392 vp 后强制停止并重启辅助功能 description 再次为392 vp。两条设备路径分别覆盖枚举策略和有限数字。自动保存已有“延迟保存”设置与完成状态截图图片拖放已有复制、移动、仅引用设备证据。不同设置的业务验证分散在对应功能测试中不能用一个设置面板截图替代文件行为验证。单元测试与构建门禁ArkTSUnitTestBuild覆盖自动保存策略解析、语言解析、语言标签映射、侧栏合法值、非法回退和上下限。图片模式的服务测试验证导入语义。Playwright30/30保护 Web 功能Debug HAP 与 ohosTest HAP 构建通过MateBook Pro 2in1 模拟器 ohosTest7/7。最终 Debug HAP 为 1,520,352 字节SHA-256367ab8650479aa1fa8fe73bd1ebadd9a53f46659c850c2e388fc799d5cb88e5bohosTest HAP 为 2,360,824 字节SHA-256b7230037b51044fe16168d2c835fb891e1c70f675941a1046165bc895217592c。两份未签名仅用于测试追踪。Preferences 测试需要分层纯解析不依赖设备I/O 与强制停止恢复需要模拟器业务效应由自动保存、图片、语言、布局各自用例覆盖。只测put成功不能证明设置真的被消费。并发、时序与最后写入当前设置操作频率低每个键由单一 UI 控件写入不存在复杂并发编辑。侧栏拖动只在结束写语言按钮可能快速连续点击多个异步 flush 的完成顺序理论上可能与点击顺序不同。Preferences put 在调用时更新同一实例通常最后调用值生效但当前没有显式写入序号。如果用户极快地中英来回点击并立即杀进程极端时序值得专门压力测试。后续可以为设置保存建立串行队列或每键 generation保证最后选择最终 flush。当前真实设备正常交互和重启路径已通过但文章不把未测并发写入宣称完全解决。初始化读取也可能与用户早期点击竞争。工作台在 Editor Ready 后很快读取实际窗口很短更严谨架构可在设置加载完成前禁用对应控件或用 generation 防止旧读取覆盖新点击。当前没有观察到该问题仍是明确演进点。隐私、安全与同步边界Preferences 不保存文档内容、最近搜索词、文件路径列表或用户账户。当前设置都在应用本地不上传、不跨设备同步也没有新增网络权限。语言default只表示跟随系统不记录系统语言历史。键和值由应用控制读取后仍白名单解析。它们不能直接进入 JavaScript 代码、文件路径或 shell语言先映射再 JSON 序列化给 Web侧栏只作为有限数字图片枚举仍受 AssetService 验证。未来云同步需要逐项产品决策。侧栏宽度在不同屏幕上可能不适合同步跟随系统必须保留策略图片 MOVE 是操作偏好但可能带来跨设备风险。不能因为“设置都在一个文件”就默认全部同步。已知限制与后续架构当前没有设置 schema version、迁移日志、统一保存队列、批量应用/取消、导入导出或恢复默认按钮。五项简单设置依靠解析回退已经足够随着快捷键、字体、主题和导出模板增加应在结构复杂前引入版本与分类。错误状态目前通过状态栏字符串展示没有持久诊断页面。设置写失败后不自动重试。搜索选项故意不持久化主题仍跟随系统不应在文档中误列为已保存。新增设置时至少要回答安全默认是什么、旧值如何解析、是否需要立即 flush、应用时机、写失败是否回滚、是否影响文件、是否值得跨设备同步、怎样做强制停止验证。只有字段和 UI 不足以完成设置功能。结论OhMarkdown 的设置层用一个 Preferences 文件承载五类有限偏好以稳定键和领域枚举隔离界面翻译用解析函数处理未知值用安全默认降低后果用显式 flush 支持强制停止恢复再由各业务服务保留自己的安全判断。语言和 392 vp 侧栏都已在真实模拟器重启后恢复。这种模型保持了适合当前阶段的克制不引入数据库不把文档塞进设置不让配置绕过文件安全也不把写入失败伪装成成功。对鸿蒙 PC Markdown 编辑器而言可预测的本地偏好是长期产品体验的基础而它的可靠性来自边界和降级不只是 Preferences API 本身。

相关新闻