news 2026/4/29 3:55:38

我把 iOS 存钱 App 移植到鸿蒙:number 精度丢失坑了我两天

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
我把 iOS 存钱 App 移植到鸿蒙:number 精度丢失坑了我两天

做了个什么东西

我有一个独立开发的存钱 App 叫「聚沙攒钱」,iOS 版上线快两年了。核心功能就是设一个储蓄目标,比如攒钱买耳机或者攒旅行基金,每次存钱会有硬币掉落动画,配合成就徽章和连续打卡,让存钱这件事不那么无聊。

去年陆续有用户问鸿蒙能不能用,我看了看 HarmonyOS NEXT 的应用数量,存钱工具类几乎是空白,就动手做了。目前鸿蒙版已经上架华为应用市场(应用 ID:6758853486),迭代到 1.9 版本。

这篇文章主要聊从 Swift + SwiftUI 到 ArkTS + ArkUI 适配过程中的技术细节和踩坑记录。

持久化方案:从 Preferences 到 relationalStore

iOS 版我用 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%。

排查过程说实话挺折腾的。一开始我怀疑是relationalStoreresultSet.getDouble()返回值有精度截断,花了大半天在数据库读写层打日志,确认存进去的数和读出来的数是一致的。然后又怀疑是 ArkUI 的Progress组件渲染有 bug,用Text直接展示百分比值才发现——数据库里的数是对的,但在 TS 层做多笔存款累加后值就漂了。经典的 IEEE 754 浮点问题,说出来谁都懂,但定位的时候真的很难第一时间想到,因为 Swift 端从来没出过这个事。

解决方案很朴素:所有金额统一用"分"为单位存整数,显示的时候除以 100 格式化。数据库 schema 里全部用INTEGER,ArkTS 层做一次(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 推送。这个时间点是我自己用下来觉得合适的——太早还在上班,太晚已经准备睡了。用户可以在设置里自定义reminderHourreminderMinute

备份稳定性测试

iOS 上线后出过一次备份导入失败的问题——某个用户的 Goal 日期字段为 nil,JSON 序列化直接挂了。吃过这个亏,鸿蒙版从一开始就写了稳定性测试脚本,核心思路是模拟大量数据验证序列化完整性:

配置了 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 物理模拟,或者有好用的引擎方案推荐,评论区聊聊——我真的需要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/29 3:54:22

长视频理解技术:实体图构建与应用实践

1. 项目概述:长视频理解的技术痛点与突破方向在视频内容爆炸式增长的当下,传统短视频分析技术已无法满足超过10分钟的长视频理解需求。我曾在多个实际项目中深刻体会到,当视频时长超过15分钟时,单纯依靠帧级特征提取或时序建模的方…

作者头像 李华
网站建设 2026/4/29 3:50:22

部署与可视化系统:源码级剖析:ONNX算子导出底层原理与YOLO模型中Grid Sample、Gather等复杂算子的修改适配

前言:当模型部署卡在“算子”这道坎上 2025年11月,一个名为“ultralytics”的PyPI包被曝出遭受供应链投毒攻击,其8.3.41和8.3.42两个版本被植入加密货币矿工程序,全球数万开发者的GPU在不知不觉中沦为攻击者的“矿卡”。根据安全研究机构Digital Watch Observatory的确认,…

作者头像 李华
网站建设 2026/4/29 3:50:21

深入解析nococli:基于Node.js的零配置CLI工具设计与实现

1. 项目概述:一个命令行工具,为何值得深挖?最近在GitHub上看到一个项目,叫doanbactam/nococli。乍一看,这只是一个命令行工具(CLI),名字里还带着“noco”,很容易让人联想…

作者头像 李华
网站建设 2026/4/29 3:47:26

MIL-STD-1553B军用数据总线协议详解与应用实践

1. MIL-STD-1553B协议深度解析1.1 军用数据总线的技术演进在20世纪50-60年代,航空电子系统采用简单的独立模拟系统架构,各子系统通过点对点布线连接。这种架构导致飞机内部布线复杂,重量增加,后期系统集成困难。随着数字技术的兴起…

作者头像 李华
网站建设 2026/4/29 3:43:25

Podinfo:云原生微服务样板间,从部署到集成的完整实践指南

1. 项目概述:为什么我们需要一个“样板间”微服务?在云原生和微服务架构成为主流的今天,无论是初创团队还是大型企业,启动一个新服务时都面临一个共同问题:如何快速搭建一个符合生产环境标准的“样板间”?这…

作者头像 李华
网站建设 2026/4/29 3:40:23

AI团队协作神器:用Git和IM让后端开发效率飙升10倍

文章探讨了如何利用Git作为信息中枢,结合IM实时通知,实现多个AI Agent(智能助手)像人类团队一样高效协作,解决传统后端开发中信息孤岛、需求传递慢、接口不同步、跨服务依赖等问题。通过构建共享知识库、Agent业务层和…

作者头像 李华