news 2026/9/4 23:53:24

鸿蒙开发避坑:倒计时器为何用时间戳而非setInterval?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸿蒙开发避坑:倒计时器为何用时间戳而非setInterval?

你有没有过这种经历:手机上自带的倒计时只能设一个时间,想改成“工作45分钟、休息10分钟”的循环,要么没有这个功能,要么藏在二级菜单里。下载一个专注类App,模式又多得离谱,免费版还插广告。一位搞鸿蒙开发的朋友跟我吐槽过这件事,他的解决办法很直接:既然受不了传统计时器,那就自己写一个鸿蒙原生App。

听起来是个小工具,好像一两个晚上就能搞完。结果这个项目让他失败了两次,第三次才真正跑通。他把整个复盘讲给我听之后,我觉得里面最有价值的并不是他最终写的那个 UI,而是他踩过的两个坑:一个是新手很容易犯的setInterval误用,另一个是对鸿蒙后台任务和常驻通知的过度期待。这两个问题,几乎每一个从 Web 前端转鸿蒙开发的人都会遇到。

这篇文章是这位开发者的访谈实录整理,同时也是面向鸿蒙原生开发的一篇技术复盘。我会把他的两次失败原因、第三次成功的设计思路,以及完整可参考的 ArkTS 示例代码都拆开讲清楚。如果你正准备入门鸿蒙开发,或者想在鸿蒙上写一个带定时功能的原生 App,这篇文章应该能帮你省掉不少弯路。

1. 这次开发要解决的问题:为什么“系统计时器”撑不住

先说说他最开始的需求。听起来特别朴素:他要一个能够支持“多组预设时长”“循环提醒”和“自定义文案提示”的计时器。

手机自带的倒计时功能,满足不了的点其实不少。比如做饭时经常要“先煮15分钟,再焖5分钟”,系统计时器只能设一个倒计时,到点之后要手动再设一次。又比如健身间歇训练,需要“每组45秒,休息15秒,循环8组”,这种结构化的计时需求,用系统自带工具做起来非常痛苦。买一个实体计时器当然也可以,但实体计时器不能改名、不能记录历史、到点提醒声音也不能灵活配置。于是他就想:自己做一个小工具,装在鸿蒙手机上,顺便还能练习一下鸿蒙原生开发。

这个想法刚提出的时候,他的预期是“半天搞定 UI,半天搞定逻辑”。因为他以前写过 Vue 和 React Native,觉得 ArkTS 与 ArkUI 这种声明式写法应该很快能上手。实际动手之后才发现,什么页面布局、组件拆分、状态管理,问题都不大。真正让他两次返工的地方是同一个词:生命周期

移动操作系统里的“计时器”,和你在网页里理解的“计时器”不是一回事。网页里setInterval只要页面不关,就能一直跑,最多是后台标签页被浏览器降频。但在手机操作系统里,应用一旦退到后台,系统会随时冻结它、挂起它,甚至回收它,目的是省电、省内存。你写在页面里的定时器,退后台几秒钟后可能就不走了,或者被系统延迟到用户再次打开才补执行。如果你设计的核心逻辑是“每秒计数一次”,那这种不确定性会让计时结果直接失真。

换句话说,他遇到的并非 ArkTS 语法问题,而是对“鸿蒙如何管理应用后台生命周期”理解不足。很多从 Web、小程序方向转鸿蒙的开发者,第一个项目都会在类似的地方翻车。下面我们具体看他第一次是怎么失败的。

2. 第一次失败:把 setInterval 当成了真正的计时器

他的第一版实现,几乎是照着 Web 开发习惯写的。在 ArkUI 页面组件里放一个@State变量,表示剩余秒数,然后启动一个setInterval,每隔一秒减一。界面显示没问题,倒计时在 App 停留前台时也很正常。

问题出在两个场景上。

第一个场景是“应用退到后台”。他锁屏之后去倒水,回来点亮屏幕一看,发现剩余时间明显比真实时间慢了很多。比如设了 10 分钟倒计时,中间锁屏了 3 分钟,回来之后界面上可能只少了几十秒。原因是系统对后台应用的定时器做了约束,setInterval不会像前台那样稳定触发。

第二个场景是“页面销毁”。他手动把 App 从多任务卡片里划掉,再重新打开,发现倒计时不仅没有继续走,甚至因为组件重建,已经回到了初始值。这说明他第一版的定时器任务宿主在页面组件上,页面销毁,定时器自然也就没了。

他当时的第一反应是:是不是应该把定时器放到一个更全局的地方?比如放到 UIAbility 里,或者单独抽一个“计时引擎”类,这样组件销毁了,定时器还能继续存活。这个思路本身有一定道理,但问题在于:就算把定时器放到 App 进程层面,应用进程被系统回收之后,里面的定时器同样会被销毁。也就是说,只要还是一个普通的前台应用,开发者就无法保证自己的 JS 定时器在所有场景下都可靠运行。

这里要特别说清楚一个概念:setInterval只是“周期性回调”,它并不负责计算真实时间。它只负责“每隔一段时间去执行一次函数”。如果你在每个回调里执行“剩余值减一”,那么回调被系统延迟、冻结、丢失,最终剩余值就会偏离真实时间。

例如这段代码,看起来逻辑通顺,实际上非常脆弱:

// 错误示例:把定时器回调次数当成时间流逝 @State remainingSeconds: number = 1500; private timerId: number = -1; start() { this.timerId = setInterval(() => { if (this.remainingSeconds > 0) { this.remainingSeconds--; } }, 1000); }

如果定时器每隔 10 秒才被系统唤醒一次,那么 10 秒钟真实时间过去后,remainingSeconds只会减少 1,而不是减少 10。问题并不出在定时器写错了,而是出在“开发环境的朴素假设”与“移动操作系统真实调度策略”之间存在的落差。

所以第一个失败带给他的教训是:不要用定时器回调次数来计量真实时间。定时器只适合做界面刷新和周期检查,真实时间必须以Date.now()这类系统时间戳为准。至于定时器被延迟、被挂起之后,如何让剩余时间依然准确,答案在于下一次刷新时重新计算,而不是在回调里累计减一。

3. 第二次失败:以为常驻通知能把 App “保护”在后台

第一次失败后,他看了不少鸿蒙开发资料,学到了一个词:常驻通知。很多文档和教程会说,给应用加一个常驻通知,可以让应用在后台更不容易被系统清理,同时用户也能实时看到当前状态。

他当时的理解是:只要通知栏里一直显示倒计时数字,系统就会认为这个应用正在被用户使用,从而放慢冻结或者清理它的速度。于是他给应用加上了通知权限,并且在开始计时后发送一条持续存在的通知,内容显示“剩余 04:32”。

这版确实解决了一部分场景。有常驻通知的情况下,应用退到后台后,被系统立即挂起的概率确实低一些,至少短时间内状态能保持住。但他很快就遇到了新的边界:用户手动从多任务卡片划掉应用时,通知和应用进程都会一起消失;有些手机省电策略比较激进,即使有常驻通知,也仍然会选择在某种条件下冻结后台进程。

真正让他放弃这条路的原因,是更现实的合规约束。鸿蒙应用如果要长期在后台运行,有明确的能力边界。开发者必须根据业务类型申请对应的“长时任务”权限,并且声明具体的任务类型。比如音乐播放、导航、运动记录、通话等场景,系统提供了相应的长时任务接口,让应用在特定场景下可以继续在后台运行。

但一个“番茄钟”或者“倒计时器”,在系统能力分类里并不是典型的长时任务类型。如果强行申请长时任务权限,应用市场审核时会很难解释:为什么一个倒计时工具需要持续占用后台资源?用户设备也会因此增加耗电,这显然不是最好的产品设计。

他第二次尝试的实质问题在于:试图通过“让应用活着”来解决所有计时问题,方向就不对。手机不是服务器,应用也不能把自己包装成永远不死的系统进程。真正专业的移动端设计,是接受系统随时可能回收后台资源这一事实,然后想清楚:应用被挂起后,用户再回来时,如何给出正确结果。与其追求进程永生,不如建立一个可靠的“时间账本”。

这也引出了第三次成功实现的核心策略:把“每秒都在跑”换成“关键时间点被记录”,把“定时器驱动”换成“时间戳驱动”。当用户回到应用时,不需要知道刚才中间那几分钟定时器是否执行了,只需要用当前系统时间减掉之前保存的开始时间或结束时间,就能算出一个准确无误的剩余时间。

4. 第三次才想清楚:定时器只是刷新器,时间戳才是真相

第三次设计在逻辑上非常简单,但和第一版有本质区别。核心原则只有一句话:计时状态只用系统时间戳来保存,不依赖 setInterval 的累计次数。

具体做法可以拆成三步。

第一步,用户点击“开始”时,根据用户希望坚持的时长,计算出“预计结束时间戳”。比如用户设置了 25 分钟倒计时,点击开始的那一刻拿到now = Date.now(),那么结束时间戳就是now + 25 * 60 * 1000。代码把结束时间戳保存在内存变量里,同时写入本地持久化存储。

第二步,启动一个setInterval,但它不再承担“倒计时计数”职责。它每隔 200 毫秒或 500 毫秒醒来一次,做的事情只有一个:读取当前Date.now(),计算与结束时间戳的差值,然后刷新界面。

第三步,应用从后台回到前台时,界面不需要重新从 25 分钟开始计时。因为结束时间戳还在,重新计算一次“结束时间戳 - 当前系统时间”就能得到准确的剩余时间。即使应用进程被系统杀掉,只要本地存储里保存过结束时间戳,再次启动时也可以选择“继续计时”或者“标记超时”。

这个方案的优点在于:它完全不依赖定时器能否稳定执行,也不在乎应用在后台是否被冻结。定时器被延迟了,界面刷新慢一点,但下次刷新时计算结果依然是正确的。定时器被销毁了,只要结束时间戳存在,等用户再次打开应用,结果也依然正确。

此时他再回头看第一次的失败,就看得非常明白:第一版的setInterval同时承担了“计量时间”和“刷新界面”两项任务,而它真正适合承担的只有后面那项。计量时间的活,应该交给操作系统时钟。

这个思路与你在 Web 开发中经常看到的“服务器时间校准”“动画帧时间差”类似,本质上是一种对“异步调度不可靠”的防御性设计。搬到鸿蒙原生开发里,它让倒计时功能变得稳定、可恢复、易于持久化。

5. 鸿蒙原生开发环境与基础概念(ArkTS / ArkUI / Stage 模型)

在进入完整代码之前,先帮还没接触过鸿蒙原生开发的读者补充几个基础概念。因为这位开发者失败两次的过程,也反映出很多初学者会把精力集中在 UI 语法上,却忽略了底层应用模型。

鸿蒙原生应用开发,当前主流技术栈是 ArkTS 和 ArkUI。ArkTS 是鸿蒙的声明式 UI 开发语言,语法上接近 TypeScript,但有更严格的静态类型限制。ArkUI 是一套声明式 UI 框架,用组件树和状态装饰器来描述界面。简单理解:@Component声明一个组件,@State声明一个可观察的状态变量。当状态变量变化时,绑定了它的 UI 会自动更新。这个写法和 Vue 的响应式数据、Flutter 的 setState 都有相似之处,所以前端开发者上手并不算太难。

与 UI 框架相关的应用模型是 Stage 模型,这是 HarmonyOS 的应用开发框架模型。一个应用可以包含若干个 UIAbility,UIAbility 是具备界面入口的能力单元,通常一个页面入口对应一个 UIAbility 或页面路由。除此之外,应用还需要理解组件的生命周期,比如aboutToAppearaboutToDisappear,以及页面级回调onPageShowonPageHide等。

鸿蒙系统对后台应用的管理策略,与 Android、iOS 类似,目的是平衡用户体验和设备资源。应用退到后台,系统可能冻结其 UI 线程和相关任务;应用被用户手动清理或长时间未使用,系统可能终止其进程。开发者不能把“网页里跑定时器”的思维直接搬到移动端,而应该主动设计应用在“不可见”“被挂起”“被恢复”时应该如何保存和恢复状态。

开发工具方面,一般使用 DevEco Studio。这个工具基于 IntelliJ IDEA,提供鸿蒙工程创建、代码编辑、模拟器、真机调试、日志查看等功能。具体安装版本和 SDK 版本更新较快,这里不写死具体版本号。实际开发时,你去 DevEco Studio 创建工程,选择 Empty Ability 模板,就能得到一个 Hello World 级别的鸿蒙原生工程。

真机调试通常需要打开开发者模式,并在 DevEco Studio 中完成签名配置。模拟器调试更适合快速验证 UI 和基础逻辑。需要注意的是,后台行为、通知、权限等能力,真机上的表现往往更接近最终用户环境,所以建议在开发中后期以真机测试为主。

6. 用 ArkTS 实现“时间戳余量法”倒计时页面

下面给出一个可以运行的简化示例。演示代码不追求完整产品功能,而是把“时间戳余量法”这个核心思想变成最小可运行代码,帮助你理解它的工作方式。

示例页面提供一个 25 分钟倒计时,支持开始、暂停、重置。暂停时,不会清除结束时间戳,而是把剩余时间保存下来,并在下次开始时重新计算结束时间戳。这样即使中间发生任何后台冻结,恢复后时间也不会出错。

// 文件路径:entry/src/main/ets/pages/TimerPage.ets @Entry @Component struct TimerPage { @State endTime: number = 0; // 结束时间戳,0 表示未开始 @State remainingMs: number = 25 * 60 * 1000; // 剩余毫秒 @State isRunning: boolean = false; // 是否正在倒计时 private tickId: number = -1; // 定时器 ID aboutToDisappear(): void { this.stopTick(); } start(): void { if (this.isRunning) { return; } // 核心:把剩余时间换算成结束时间戳 this.endTime = Date.now() + this.remainingMs; this.isRunning = true; this.startTick(); } pause(): void { if (!this.isRunning) { return; } // 暂停时回写剩余时间,然后停止刷新 this.remainingMs = Math.max(0, this.endTime - Date.now()); this.isRunning = false; this.stopTick(); } reset(): void { this.pause(); this.remainingMs = 25 * 60 * 1000; } private startTick(): void { this.stopTick(); // 定时器只负责刷新 UI,不负责累计时间 this.tickId = setInterval(() => { const diff = this.endTime - Date.now(); if (diff <= 0) { this.remainingMs = 0; this.isRunning = false; this.stopTick(); // 这里可以接本地通知,提示倒计时结束 } else { this.remainingMs = diff; } }, 200); } private stopTick(): void { if (this.tickId !== -1) { clearInterval(this.tickId); this.tickId = -1; } } private formatTime(ms: number): string { const totalSeconds = Math.ceil(ms / 1000); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; const mm = minutes < 10 ? `0${minutes}` : `${minutes}`; const ss = seconds < 10 ? `0${seconds}` : `${seconds}`; return `${mm}:${ss}`; } build() { Column({ space: 20 }) { Text(this.formatTime(this.remainingMs)) .fontSize(64) .fontWeight(FontWeight.Bold) Row({ space: 16 }) { Button(this.isRunning ? '暂停' : '开始') .onClick(() => { if (this.isRunning) { this.pause(); } else { this.start(); } }) Button('重置') .onClick(() => { this.reset(); }) } } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } }

代码里有几个值得强调的设计点。

remainingMs不再在定时器回调里手动减一,而是每次通过endTime - Date.now()重新计算。这种情况下,即使定时器本次回调比预期晚了 3 秒,界面显示的数字也依然正确,因为它读取的是真实系统时间。

setInterval的间隔设为 200 毫秒,而不是 1000 毫秒,不是为了计数更准确,而是为了让倒计时最后一分钟的 UI 显示更平滑。取Math.ceil(ms / 1000)可以避免显示 00:00 时实际还差几百毫秒的挫败感。

暂停按钮的作用是“冻结当前剩余时间”。它计算一次差值并保存到remainingMs,然后停掉刷新定时器。重新开始时,会用新的剩余时间重新计算结束时间戳,所以暂停多久都不会影响准确性。

这个版本已经能解决他最初遇到的主要问题:后台冻结、锁屏、页面重建。但如果应用进程被系统杀掉,内存里的endTimeremainingMs都会丢失。要处理这个场景,就需要把状态写入本地存储。

7. 状态持久化与“到点提醒”的边界处理

为了应对进程被杀后的恢复,需要引入本地持久化。HarmonyOS 提供 Preferences 轻量级键值存储,适合保存这类简单的应用状态。

下面这段代码示意如何把结束时间戳写入持久化存储:

// 示意代码:保存状态到 Preferences import dataPreferences from '@ohos.data.preferences'; import { common } from '@kit.AbilityKit'; async function saveTimerState(context: common.UIAbilityContext, endTime: number) { const preferences = await dataPreferences.getPreferences(context, 'timer_store'); await preferences.put('endTime', endTime); await preferences.flush(); }

读取时也类似,拿到endTime后,用endTime - Date.now()判断剩余时间。如果差值大于 0,说明计时未结束,可以让用户选择“继续计时”或“放弃”。如果差值小于等于 0,说明这个倒计时任务已经超时,可以提示用户上一次倒计时已经结束。

但这里必须老老实实说清楚一个边界:如果应用进程已经被系统杀掉,而你想在“应用完全没有运行”的时刻弹出提醒,普通的前台定时器做不到。这属于系统级提醒能力边界。要做到“应用被清理后仍然能响铃”,通常需要系统闹钟接口或后台任务能力,而且这类能力有着明确的申请条件。对于个人开发的倒计时工具来说,更稳妥的设计是妥协:应用在前台或最近任务中存在时,时间计算保持准确;应用被彻底清理后,等用户再次打开时给出“上次任务已结束”的最终状态,并让用户自己决定是否补记。

很多初学鸿蒙开发的朋友会在这个阶段死磕“如何让应用被杀后还能通知”,其实不应该。倒计时工具不是系统闹钟,强行申请后台常驻反而会增加审核和耗电风险。把用户预期管理好,比在技术上欺骗系统更重要。一位合格的应用开发者,应该学会在系统能力边界内做产品,而不是把资源浪费在与系统调度策略对抗上。

另外,如果你想在倒计时结束时给用户一个通知,需要申请通知权限,并使用鸿蒙的通知接口发送一条本地通知。这不会延长应用后台存活时间,但至少用户回到通知栏时能看到结果。本地通知的接口在不同 SDK 版本中也有差异,建议以你使用的 DevEco Studio 和 SDK 版本对应的官方 API 为准。

8. 验证测试与常见问题排查

这个倒计时页面写完以后,建议不只点击“开始”看它走不走,还要针对后台场景做几个专项测试。

第一项测试:点击开始,倒计时正常显示;按 Home 键回到桌面,等待 1 到 2 分钟;重新进入 App,观察剩余时间是否等于“进入后台前的时间减去真实经过的时间”。如果使用前面写的“时间戳余量法”,重新进入时差值会立刻被计算出来,即使界面在后台没有刷新,也应该显示正确。

第二项测试:开始倒计时后,把 App 加入多任务列表,从多任务卡片中划掉 App;稍等几秒后重新打开。如果做好了持久化恢复,应该能拿到上次保存的结束时间戳。如果没做持久化,那么你会看到倒计时归零或者回到初始值,这就说明进程被杀导致状态丢失了。

第三项测试:锁屏后等待一段时间再解锁。重点观察解锁瞬间,剩余时间是否还是正确的。这一步能暴露旧方案中“后台定时器被挂起”的问题。

如果测试过程中发现状态错乱,可以按照下面的排查顺序来看,而不是直接怀疑定时器写错:

问题现象可能原因排查方式解决方案
退后台后再进入,时间少走页面不可见时刷新定时器被系统降频打印恢复时刻的endTime - Date.now()确认使用时间戳差值,而不是依赖 setInterval 回调减一
从多任务卡片划掉后,重新打开时间复原未持久化结束时间戳查看进程被杀后是否从 Preferences 恢复状态在开始计时和暂停时写入 Preferences
显示 00:00 但声音/通知没触发没有申请通知权限或通知发送失败查看权限是否已授予,检查发送接口的错误日志申请通知权限,确认本地通知 API 调用成功
暂停后重新开始,时间比上次少很多暂停逻辑中多次减去了同一段时间检查暂停时是否重复调用remainingMs = endTime - Date.now()暂停和停止逻辑保证只计算一次
多任务卡片里看不到 App 或进程被杀系统资源回收策略使用真机复现,观察是否所有应用都会被清理接受系统行为,通过状态恢复解决用户感知问题

出现问题时,第一步不要看界面,先看“状态数据”。在关键节点打印日志,比如开始时的endTime、暂停时的remainingMs、恢复时的endTime - Date.now()。只要这几个数值是对的,UI 表现最终一定是对的。如果这几个数值已经错了,再去看 UI 也没有意义。

9. 给鸿蒙原生开发者的工程建议

经过这次“两次失败、第三次成功”的完整经历,这位开发者总结出几条不仅适用于计时器,也适用于其他鸿蒙原生 App 的开发经验。

第一,不要试图对抗系统的后台限制。把更多精力放在“可靠恢复”而非“永不销毁”上。普通工具类应用不需要后台永生。用户回到应用时看到正确结果,通常比应用在后台默默运行显得更可信。对于确实需要后台运行的业务,比如音乐播放、导航,务必按照鸿蒙官方要求申请对应能力,不要用隐藏手段绕过系统限制。

第二,状态与 UI 分离。哪怕是一个小型计时器,也应该把“倒计时逻辑”和“界面刷新”分开设计。逻辑层负责记录endTimeremainingMs,UI 层只负责展示和转发用户操作。这样即使界面组件销毁重建,逻辑状态不容易丢失,代码也更容易测试。

第三,善用本地持久化。@State变量只活在内存里,App 进程一结束就归零。对于有状态的产品,应该在合适的时机把关键状态保存到 Preferences 或数据库中。保存时机可以选在开始计时、暂停计时、倒计时结束这几个关键节点。过度频繁地写存储反而没有必要,因为移动存储写入也有性能和寿命成本。

第四,把系统时间戳当成唯一权威。无论是倒计时、每日签到、限时活动,还是节流和调度,只要涉及“真实经过了多少时间”,都应该基于Date.now()或系统单调时钟,而不是基于某些定时器的回调次数。定时器只适合做延迟触发和周期刷新,不适合作为手表。

第五,权限申请要克制。很多新手开发者在做工具时会顺手申请通知、后台运行、闹钟等权限。实际上,权限越多,应用市场的审核风险越高,用户安装时的心理负担也越大。一个倒计时工具,真正需要的权限可能只有“通知”这一项,甚至如果只在 App 内提示,连通知权限都可以不申请。只申请与核心功能直接相关的权限,是移动应用开发的基本礼仪。

第六,开发过程中要区分“功能可用”和“用户可用”。第一版代码在模拟器里点击开始,数字每秒递减,看起来是“功能可用”。但用户不会一直停留在前台盯着界面。用户通常在计时开始后就去干别的事情了,锁屏、切后台、杀进程都会发生。所以你的测试用例不能只在模拟器前台点按钮,而要模拟真实用户的使用路径。每完成一个功能,至少跑一遍“进入后台、等待一段时间、重新进入”的流程,这比多写几个按钮更接近真实质量。

第七,如果想在鸿蒙生态里长期做开发,要关注官方能力和 API 的版本变化。鸿蒙系统迭代速度较快,部分接口在历史版本和新版本中的命名有调整,比如通知、后台任务、数据存储等能力都逐渐从@ohos.*迁移到@kit.*。建议不要死记某一种写法,而是学会在 DevEco Studio 中查看 SDK 接口说明,或者搜索官方文档中最新的使用示例。技术文章里的代码有一定时效性,但“时间戳驱动、状态持久化、合理权限”这套方法论不会过时。

10. 总结与后续方向

回顾这位开发者的整个经历,最值得记住的是三个转变。

第一,他对“计时器”的理解变了。以前认为定时器就是计时器,后来才明白定时器只是刷新器,真正的计时基准来自系统时间戳。这个转变解决了锁屏和后台冻结带来的时间漂移问题。

第二,他对“后台保活”的理解变了。以前以为只要不断给应用叠加常驻通知,应用就能一直运行,后来发现这既不符合系统调度策略,也不符合工具类应用的合规要求。真正的解法是做好状态恢复,让应用可以在任何情况下给用户一个正确结果。

第三,他对鸿蒙原生开发的认识变了。ArkTS 和 ArkUI 的语法并不难,真正影响应用质量的是开发者对 Stage 模型、生命周期、系统后台策略和数据持久化的理解程度。这个底层认知不建立,换到其他移动平台开发,大概率还会踩类似深浅不一的坑。

如果你也想做一个属于自己的鸿蒙原生小工具,建议从这次的时间戳余量法入手。先写一个只有开始、暂停、重置的倒计时页面,再把结束时间戳写入 Preferences,测试一下划掉进程再打开是否还能恢复。这个最小 Demo 跑通之后,再逐步加入多组预设、提醒文案、历史记录等功能。每一步都能验证,出问题时也能按“状态数据是否正确”这条主线快速定位。

计时器这个需求本身很小,但它真的能暴露很多移动开发的核心问题。不要因为“只是一个小工具”就忽略生命周期设计。相反,恰恰是这种小工具,最适合用来理解鸿蒙系统的运行机制。希望这篇访谈式的技术复盘,能帮你少走一次弯路。

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

Unity冰纹理折射效果实现:Shader Graph与手写Shader详解

做冰纹理折射这个效果&#xff0c;最早是因为在做一个冰雪主题的场景&#xff0c;需要冰面既能透出底下的物体&#xff0c;又要有那种扭曲变形的视觉感受。一开始想直接用普通的玻璃Shader换贴图&#xff0c;但效果怎么都不对——要么太平、太死板&#xff0c;要么就是完全的模…

作者头像 李华
网站建设 2026/9/4 23:50:16

Unity冰材质折射实现:GrabPass屏幕扭曲Shader全解析

前阵项目里要加一块被冰冻住的场景物件&#xff0c;甲方开口就三个字&#xff1a;“要像冰。”我当时第一反应是冷笑——像冰&#xff0c;这三个字背后是半透明、折射、扭曲、高光、菲涅尔、次表面散射一整套东西&#xff0c;真要写实够喝一壶的。但需求落到画面上&#xff0c;…

作者头像 李华
网站建设 2026/9/4 23:49:28

SSM框架实战:从零构建家乡特产电商系统

简介&#xff1a;这是一套面向高校计算机专业本科生的Java毕业设计实战资源&#xff0c;聚焦家乡特产电商场景&#xff0c;基于SSM&#xff08;SpringSpringMVCMyBatis&#xff09;后端框架与Vue.js前端技术构建B/S架构商城系统&#xff0c;适用于课程设计、毕设开题与中期开发…

作者头像 李华
网站建设 2026/9/4 23:49:18

日本企业AI落地为何慢?数字基础、合规与日语模型适配成瓶颈

日本企业的AI步子慢&#xff0c;不是最近才被讨论的问题。当美国企业已经把大模型接入办公套件、客服系统和代码开发流程时&#xff0c;日本许多企业还在用传真、印章和Excel推进内部流程。生成式AI出来之后&#xff0c;这种差距变得更加明显。今天我们抛开“日本企业保守”这种…

作者头像 李华
网站建设 2026/9/4 23:49:16

大模型开源的真相:开放权重与本地部署、微调的边界

这个话题的关键在于“开源”这个词被过度使用了。DeepSeek、Kimi以及不少被当作“AI模型开源”代表的案例&#xff0c;其实和程序员熟悉的“开源代码”不是一回事。我们更准确地讲&#xff0c;这些模型通常是“开放权重”或“有条件开源”&#xff1a;公开的是模型文件、推理代…

作者头像 李华
网站建设 2026/9/4 23:45:35

搭建AI水印移除验证工具:元数据、像素残留与可量化评估

AI 水印移除工具要证明自己有效&#xff0c;最常见的方式是放一张“前后对比图”&#xff1a;左边是带水印的原图&#xff0c;右边是声称已清理干净的图片。问题在于&#xff0c;整个过程缺少一个独立的验证层&#xff0c;结论完全由移除工具自己给出。更麻烦的是&#xff0c;很…

作者头像 李华