做了个什么东西我有一个独立开发的存钱 App 叫「聚沙攒钱」iOS 版上线快两年了。核心功能就是设一个储蓄目标比如攒钱买耳机或者攒旅行基金每次存钱会有硬币掉落动画配合成就徽章和连续打卡让存钱这件事不那么无聊。去年陆续有用户问鸿蒙能不能用我看了看 HarmonyOS NEXT 的应用数量存钱工具类几乎是空白就动手做了。目前鸿蒙版已经上架华为应用市场应用 ID6758853486迭代到 1.9 版本。这篇文章主要聊从 Swift SwiftUI 到 ArkTS ArkUI 适配过程中的技术细节和踩坑记录。持久化方案从 Preferences 到 relationalStoreiOS 版我用 SwiftData 做持久化Goal 和 Transaction 是一对多关系Query配合FetchDescriptor查询很方便。到鸿蒙这边一开始我试了ohos.data.preferences想着 KV 存储足够轻量。结果当我造了 50 个 Goal、每个 Goal 挂 30 条 Transaction 的测试数据后读取一次全量数据要将近 800ms而且按状态筛选、按时间排序这种需求得全部读进内存再手动过滤写起来又丑又慢。果断切到了ohos.data.relationalStore建关系型表。核心建表逻辑大概是这样const SQL_CREATE_GOALS CREATE TABLE IF NOT EXISTS goals ( id TEXT PRIMARY KEY, name TEXT NOT NULL, icon TEXT DEFAULT S, mode TEXT CHECK(mode IN (wish,free)) DEFAULT wish, status TEXT CHECK(status IN (active,archived)) DEFAULT active, strategy TEXT DEFAULT weekly, target_amount INTEGER DEFAULT 0, current_amount INTEGER DEFAULT 0, plan_amount INTEGER DEFAULT 0, total_periods INTEGER DEFAULT 0, created_at INTEGER NOT NULL, next_due_date INTEGER ); 所有金额字段都是 INTEGER单位是分。这就引出了移植过程中最蠢的一个 bug。 ## number 精度丢失标题里说的两天就花在这儿 iOS 版里 targetAmount 用的 Int64整数运算没有精度问题。移植到 ArkTS 的时候我图省事直接用了 number 类型存元 arkts // 错误写法 —— 别学我 let currentAmount: number 1999.99 let deposit: number 0.01 currentAmount deposit // 期望 2000.00实际可能是 1999.9999999999998这个问题在小金额测试的时候完全看不出来。我是用稍大的数做集成测试时才发现进度条百分比算出来不对——比如目标 2000 元、已存 2000 元进度条显示 99.99%。排查过程说实话挺折腾的。一开始我怀疑是relationalStore的resultSet.getDouble()返回值有精度截断花了大半天在数据库读写层打日志确认存进去的数和读出来的数是一致的。然后又怀疑是 ArkUI 的Progress组件渲染有 bug用Text直接展示百分比值才发现——数据库里的数是对的但在 TS 层做多笔存款累加后值就漂了。经典的 IEEE 754 浮点问题说出来谁都懂但定位的时候真的很难第一时间想到因为 Swift 端从来没出过这个事。解决方案很朴素所有金额统一用分为单位存整数显示的时候除以 100 格式化。数据库 schema 里全部用INTEGERArkTS 层做一次(amount / 100).toFixed(2)。改完之后再也没出现过精度问题。成就徽章系统currentStreak 的计算iOS 版有一套我挺喜欢的成就系统。BadgeLibrary里定义了十几个徽章比如streak_7连续打卡 7 天、night_owl夜间存款 10 次、collector同时维护 5 个目标。每个徽章的解锁条件是一个闭包拿StatsSummary做判断。移植到 ArkTS 问题不大箭头函数替代闭包就行。真正麻烦的是StatsSummary的计算——iOS 端用 SwiftData 的查询能力配合内存计算很顺畅鸿蒙这边得自己写 SQL 查出原始数据再在 TypeScript 层做二次处理。其中最复杂的是currentStreak当前连续打卡天数。我的做法是用 SQL 按天去重查出所有有存款记录的日期然后在 TS 里从今天往回倒推SQL 部分用created_at / 86400000做整数除法实现按天去重比在 TS 层遍历所有记录再 groupBy 高效得多。这个 streak 值算好之后塞进StatsSummary徽章解锁判断就很直白了。每日提醒notificationManager 的平台差异iOS 用UNUserNotificationCenter注册本地通知流程大家都很熟。鸿蒙这边用ohos.notificationManager有几个关键差异权限申请。鸿蒙的通知权限默认是关闭的需要用notificationManager.requestEnableNotification()触发系统弹窗。但这个 API 在部分设备上行为不太一致——我在 Mate 60 上测试正常在某款平板上弹窗死活不出来。最后加了 try-catch如果requestEnableNotification抛异常或者超时就弹一个自定义对话框引导用户手动跳转系统设置页。不算优雅但至少不会卡死流程。通知渠道是必须的。iOS 可以直接发本地通知鸿蒙需要先创建 NotificationSlot不创建就静默失败连报错都不给你排查了一会儿才意识到。我的提醒设置默认是每天 20:15 推送。这个时间点是我自己用下来觉得合适的——太早还在上班太晚已经准备睡了。用户可以在设置里自定义reminderHour和reminderMinute。备份稳定性测试iOS 上线后出过一次备份导入失败的问题——某个用户的 Goal 日期字段为 nilJSON 序列化直接挂了。吃过这个亏鸿蒙版从一开始就写了稳定性测试脚本核心思路是模拟大量数据验证序列化完整性配置了 1200 个 Goal每个带 10 条 PlanItem 和 10 条 Transaction共 20 条子记录总共两万多条数据跑一遍序列化 / 反序列化。脚本里用了自定义的assert工具函数ArkTS 没有原生 assert API自己封装的条件不满足直接 throw Error检查两件事序列化后 JSON 长度不超过 8MB、反序列化后 Goal 数量一致。备份数据结构里有个BACKUP_SCHEMA_VERSION字段当前值是 1以后加字段可以通过版本号做迁移而不破坏旧备份。真实数据和商业化实话说鸿蒙版上线后数据很冷。最近一周下载量个位数付费为零。说一下付费模式跟 iOS 版一样是订阅制免费版可以创建 3 个储蓄目标Pro 订阅解锁无限目标、主题切换和成就徽章系统。订阅走的华为 IAP。我分析下载冷的原因ASO 还没认真做截图和关键词都很粗糙鸿蒙纯血应用的存量用户还在增长期工具类 App 的自然流量本来就不大。但我翻了华为应用市场存钱储蓄关键词下的搜索结果原生鸿蒙应用确实很少大部分还是安卓套壳。我觉得只要产品做到位后面增长应该能起来。没做的事硬币掉落动画得坦白一下。iOS 版有个用 SpriteKit 做的硬币物理掉落动画硬币之间会碰撞弹跳堆叠配合 Haptic Feedback 触感反馈存钱那一刻特别爽。鸿蒙版暂时没做。HarmonyOS 目前没有 SpriteKit 这种现成的 2D 物理引擎框架如果要做得用 Canvas 组件手写碰撞检测和物理模拟。我评估了一下大概要额外两三周效果还不一定好。先上线核心功能动画后面再补。有点可惜但我觉得这个取舍是对的。下一步近期计划适配鸿蒙桌面卡片Widget显示今日存钱进度、补上深色模式主题、用 Canvas 实现一版简化的硬币物理动画。Canvas 物理引擎这块我打算单独写一篇会包括碰撞检测算法、帧率控制和 ArkUI Canvas 的性能调优。如果方案跑通了碰撞检测模块会开源出来。对这个方向感兴趣的可以先关注下篇发出来第一时间能看到。另外如果你在鸿蒙上做过 Canvas 2D 物理模拟或者有好用的引擎方案推荐评论区聊聊——我真的需要。