上个月我在核对账单,发现有一笔 25 块的扣费完全对不上号。翻了半天才想起来,三个月前注册了某个 AI 工具的「7天免费试用」,试完忘了取消,就这么续费了三个月。
说实话有点难受。不是因为 75 块钱多大的事,是那种「被悄悄割了还浑然不觉」的感觉。
作为开发者,我订阅的东西比普通人多得多。GitHub Copilot、Figma、Claude Pro、各种云服务……我认真数了一下,每月订阅支出加起来接近 400 块,比我午饭的钱还多。于是我决定自己做一个 App,专门用来管这件事。
这就是**订阅斩(SubKiller)**的来源。
做了什么,解决了什么问题
说白了这个 App 干三件事:
- 把你所有的订阅录进来,记清楚金额、周期、下次扣费日
-
- 算出你每个月到底在订阅上花了多少钱,给你一个不容回避的数字
-
- 主动帮你找出「高危订阅」——那些快要续费却长期没用的,App 会标红提醒你
交互上我做了一个「斩」的动作:停用一个订阅不叫「删除」,叫「斩断」。斩掉之后进入「已斩」Tab,随时可以恢复。这个设计很小,但用起来有点仪式感——主动止损,而不是被动遗忘。
- 主动帮你找出「高危订阅」——那些快要续费却长期没用的,App 会标红提醒你
技术选型:为什么要出鸿蒙版
App 本体是 iOS 用 SwiftData + SwiftUI 写的。鸿蒙版本是后来决定做的,原因很简单——优快云 上好多开发者在用华为设备,HarmonyOS NEXT 之后纯血鸿蒙生态也在补充各类工具 App,我觉得值得投入。
鸿蒙版用 ArkTS + ArkUI 开发,数据持久化用的是 @ohos.data.relationalStore,整体架构参考了 iOS 端的模型设计,订阅的核心数据结构大致是这样:
// 订阅数据模型(简化版)
interface Subscription {
id: string
name: string
price: number
cycle: 'monthly' | 'quarterly' | 'yearly'
isTrial: boolean
nextBillingDate: Date
status: 'active' | 'killed'
killedDate?: Date
icon: string
category?: string
}
// 按周期折算月均费用
function toMonthlyAmount(price: number, cycle: string): number {
if (cycle === 'quarterly') return price / 3
if (cycle === 'yearly') return price / 12
return price
}
```
这个折算逻辑是整个仪表盘的核心——把季付、年付全部换算成月均,用户看到的「本月预计支出」才是真实的资金压力,而不是一个被年付摊薄的假低数字。
风险检测这块,我在 ArkUI 里用 **@State + 计算属性** 实时跑风险评级,时间维度的判断逻辑是:试用状态 + 距下次扣费不足 7 天 = 高危;距扣费不足 3 天 = 高危;不足 14 天 = 中危。
iOS 端还叠加了活跃度维度——通过 ScreenTime API 读取对应 App 的 weekly screenTime,低于阈值(目前设的是每周不足 2 分钟)就标记为低活跃,再结合扣费时间综合评级。鸿蒙端目前没有等价的 API 可以读第三方 App 使用时长,所以这一维度暂时缺失,只做时间维度的评估。
```arkts
// 风险等级评估(ArkTS 实现,时间维度)
function assessRisk(sub: Subscription): 'high' | 'medium' | 'low' {
const now = new Date()
const daysUntilBilling = Math.ceil(
(sub.nextBillingDate.getTime() - now.getTime()) / 86400000
)
if (sub.isTrial && daysUntilBilling <= 7) {
return 'high'
}
if (daysUntilBilling <= 3) {
return 'high'
}
if (daysUntilBilling <= 14) {
return 'medium'
}
return 'low'
}
```
我自己就被它揪出来两个僵尸订阅。
---
## 踩过的坑,说几个真实的
**多货币换算**这个需求一开始我以为很简单,后来发现不同地区用户的货币符号差异挺大。iOS 端我直接读 `Locale.current.currency?.identifier`,做了一个 switch-case 覆盖主流货币(USD/EUR/CNY/JPY/TWD/HKD 等)。鸿蒙端用 `@ohos.i18n` 的 `getSystemLocale()` 实现了类似逻辑,但部分小语种地区的货币符号需要手动兜底,花了不少时间校对。
**订阅目录预设**这件事我试了三个方案最后全推倒重来。最初想做在线拉取,服务器维护成本太高;然后想做本地 JSON,结果模糊搜索的匹配质量很差用户体验烂;最终选择在代码里维护一个 `SubscriptionCatalog`,内置 Netflix、YouTube Premium、Disney+、Spotify、Apple TV+ 等几十个常见服务的预设数据(含图标、参考价格、推荐周期),用别名匹配做模糊搜索,召回率明显好多了。
**SwiftData 在 iOS 端的 schema 迁移**是另一个坑。AppSettings 里后来新增了 iCloud 同步开关、OCR 使用计数等字段,老数据的 migration 处理花了一整个下午。鸿蒙端因为是后做的,我直接把这些坑填好了,省了不少事。
---
## 目前的状态
App 在 **App Store 已上架**(搜「订阅斩」或 ID 6761400615),上架约 3 周,目前累计下载 200 次出头,还在早期。产品方向我觉得是对的——开发者群体是「订阅重度用户」里感知最强的一批,你们自己肯定也有过「这个月到底花了多少钱在订阅上」的疑惑。
鸿蒙版本正在适配 HarmonyOS NEXT,争取近期上架华为应用市场。
对了,App 里有一个「工时换算」功能,可以设置自己的月薪和工时,然后每个订阅下面会显示「相当于你工作 X 小时」——这个数字出来的时候挺扎心的。某些订阅一看,两个小时的劳动换来的,然后我发现自己上个月完全没用过它。
---
## 关于鸿蒙开发的一些感受
ArkUI 的声明式写法和 SwiftUI 在思路上很像,上手成本比我预期低。ArkTS 的类型系统比纯 JS 严格很多,写起来有安全感。
有一个地方比较顺手:**@Link 双向绑定**处理多组件状态同步时逻辑挺清晰,订阅斩的仪表盘数据要在主页、列表页、设置页三个地方同步刷新,用这套写下来比我预期干净。
不过 **@Watch 装饰器**踩了个坑——页面销毁时如果不手动解绑,内存泄漏不会立刻崩,但跑久了内存涨得很明显,查了半天才定位到。这个在官方文档里提得不够显眼,写鸿蒙 App 的朋友注意一下。
你现在每月订阅支出大概是多少?有没有被某个试用坑过——评论区说说,我挺好奇开发者们的「订阅账单」长什么样。如果你也在做鸿蒙工具类 App,本地数据库选型和华为云替代 iCloud 同步这块也欢迎聊聊,这两个问题我还在研究。
575



被折叠的 条评论
为什么被折叠?



