我把iOS存钱App搬到鸿蒙,删掉了哪些东西
先说为什么做这个
上个月我翻了一下余额宝,26号发薪后只剩83块。月初我设了整整5个储蓄计划,旅行基金、换手机、应急备用金……每一个都像模像样,每一个都在第三天安静地死掉了。
不是没有记账App,是记账这件事本身太「会算账」——它把你消费了多少记得清清楚楚,然后让你在图表面前感到愧疚。我想要的不是这个。我想要的是:往罐子里存进去一笔钱的那一刻,能有点爽感,让我明天还想再打开一次。
于是做了聚沙攒钱。iOS版本跑通了这个想法之后,决定移植到HarmonyOS。这篇文章主要聊鸿蒙版开发过程中真正删掉了什么、留下了什么。
App的核心逻辑
产品就两个模式:
愿望模式:设一个目标金额,比如「索尼耳机,2000元」,每次存钱看进度条涨。目标感强,适合短期消费储蓄。
聚沙模式:偏DCA定投逻辑。告诉App每天/每周存多少,内置复利计算器算出到达目标的时间线。适合攒应急备用金、首付这类长期计划。
两个模式背后有一套成就徽章系统。连续存款7天、总存款破10000、早上7点前存钱……条件判断用一个BadgeDefinition结构维护,每条徽章对应一个(StatsSummary) -> Bool的判断函数,iOS端这块逻辑后来直接翻译到了鸿蒙端,几乎没改。
鸿蒙移植:删掉了什么
物理引擎动画——砍掉了
iOS版最有差异感的功能是硬币掉落动画,用SpriteKit做的物理模拟,每次存款硬币哗哗往罐子里落,有真实重力感。
移植到鸿蒙的时候,我调研了一圈,HarmonyOS没有直接对应SpriteKit的物理引擎API。@ohos.animator可以做关键帧动画,但模拟多个硬币独立物理轨迹的复杂度太高,预计要额外花两周。权衡了一下,鸿蒙版改成了animateTo驱动的简化下落效果——单枚硬币从顶部滑入,带缓动曲线,物理感差了一截,但核心的「存进去有反馈」这件事还在。
这是我觉得最可惜的一个删减。
数据层:全换成关系型数据库
iOS用CoreData,鸿蒙版我一开始想全部放Preferences,省事。结果压力测试直接打脸:构造了1200个Goal对象,每个关联10条计划记录和10条交易记录,序列化成JSON字符串大概跑出来将近8MB文本,冷启动读取耗时在1.1秒左右,在中端机上肉眼可见的卡顿。
切换到@ohos.data.relationalStore关系型数据库之后,同样的数据集冷启动读取降到200ms以内。代价是多写了一套建表、查询、迁移的模板代码,大概多了400行左右。换得值。
数据备份做了本地JSON导出,schema版本号、来源标识、App版本都写进了文件头。备份文本上限设了8,000,000字符,这个数字是反复测试极端情况后定的。
ArkUI进度可视化
存钱罐的进度展示,我想比普通进度条有意思一点。鸿蒙端用ArkUI的Canvas组件手绘了罐子轮廓,填充高度随currentAmount / targetAmount比值变化。
这里有个坑要说清楚:CanvasRenderingContext2D对象必须在组件顶部显式初始化,不能直接在onReady里用未声明的context,否则编译能过但运行时直接白屏。
@Component
struct SavingsJarView {
private context: CanvasRenderingContext2D =
new CanvasRenderingContext2D(new RenderingContextSettings(true))
@Prop currentAmount: number
@Prop targetAmount: number
build() {
Canvas(this.context)
.width('100%')
.height(200)
.onReady(() => {
const ratio = this.targetAmount > 0
? Math.min(this.currentAmount / this.targetAmount, 1.0)
: 0
const fillHeight = 160 * ratio
this.context.strokeStyle = '#C8A96E'
this.context.lineWidth = 3
this.context.strokeRect(30, 20, 140, 160)
this.context.fillStyle = '#F0D080'
this.context.fillRect(31, 20 + (160 - fillHeight), 138, fillHeight)
})
}
}
```
在这段代码之前我试了三个方案:用`Progress`组件竖排、用`Stack`叠层控制高度、自定义`Shape`。最后全删了,Canvas画最直接。
### 每日激励语录
iOS版有个`QuoteLibrary`,用主谓数组组合生成存钱激励语,足够全年轮换不重样。鸿蒙版用ArkTS重写了同样的逻辑:
```arkts
// 注意:两个数组必须等长(当前均为18条),才能保证主谓全覆盖324种组合
// 若长度不等,实际组合数 = subjects.length × predicates.length,但会出现非预期配对
function getDailyQuote(subjects: string[], predicates: string[]): string {
const today = new Date()
const dayOfYear = Math.floor(
(today.getTime() - new Date(today.getFullYear(), 0, 0).getTime()) / 86_400_000
)
const total = subjects.length * predicates.length
const index = dayOfYear % total
const s = subjects[Math.floor(index / predicates.length)]
const p = predicates[index % predicates.length]
return s + p
}
```
用`dayOfYear % total`做统一索引,两个数组不等长也能遍历完所有组合,不会出现错位循环的问题。每天根据日期取一条,不需要联网。
---
## 上架情况
鸿蒙版已上架华为AppGallery,版本1.8,App ID 6758853486。
上架三周,有12个用户留了反馈。其中3条是问「能不能加多账户支持,家庭共用」,2条是问能不能导出到Excel。多账户这个需求我在iOS端也收到过,排进了下一个版本的计划里。导出Excel……暂时没有优先级,鸿蒙端的文件读写权限申请流程我还没完全摸清楚。
下载量整体不大,储蓄工具这个品类在鸿蒙AppGallery比iOS冷清不少,加上我几乎没做推广,基本靠搜索自然流量。
---
## 给也在做鸿蒙的开发者
跨平台移植代码翻译工作量不大,麻烦的是系统API行为差异。几个我实际踩过的:
- **通知提醒**:鸿蒙用`@ohos.notificationManager`,需要在使用前主动`requestEnableNotification`。实测有相当比例用户第一次打开直接拒掉,之后想开就得去设置里找。打算在onboarding加一句引导文案,但还没做。
- - **深色模式**:`@ohos.app.ability.Configuration`里能拿到当前颜色模式,App启动时读一次,用`AppStorage`全局共享,这块还算顺。
- - **Canvas的`onReady`时序**:这是我到现在还没完全搞清楚的一个问题。在某些真机上,`onReady`回调触发的时候`context`的宽高还没有正确设置,导致绘制偏移。我的临时方案是在回调里加了50ms的`setTimeout`延迟,但这明显是个workaround而不是根因解决。
如果你在鸿蒙真机上用Canvas也遇到过`onReady`触发时序不稳定的问题,或者知道正确的初始化方式,评论区告诉我一声——我不确定这是DevEco的bug还是我写法本身有问题,目前没找到官方文档里对这个边界情况的明确说明。
更多推荐



所有评论(0)