先说为什么做这个

上个月我翻了一下余额宝,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还是我写法本身有问题,目前没找到官方文档里对这个边界情况的明确说明。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐