HarmonyOS 鸿蒙Next后台定时器
HarmonyOS 鸿蒙Next后台定时器
有一个页面设置了一个定时器,倒计时结束后页面的元素会发生变化
当倒计时还在进行中时,销毁页面,如何在下次创建该页面时正确的获取并展示剩余时间(要计算页面销毁期间的时间),如果还有剩余时间就继续进行倒计时
【背景知识】
用户首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。数据存储形式为键值对,键的类型为字符串型,值的存储数据类型包括数字型、字符型、布尔型以及这3种类型的数组类型。详情请参考用户首选项使用指南及相关API。
【解决方案】
主要思路是数据持久化,可以将计时器计时的每一秒钟保存到用户首选项中,可以保证数据永远不会丢失,即时进程被杀掉也不影响。 通过setInterval设定一个定时器,定时器内部控制倒计时逻辑,配合状态管理,实现倒计时效果。 示例demo如下:
import { preferences } from '@kit.ArkData';
@Component
struct Index {
// 使用@State装饰器管理状态,记录动态更新的数值
@State message: number = 60
// 用户首选项
dataPreferences = preferences.getPreferencesSync(this.getUIContext().getHostContext(), { name: 'myStore' });
private timerId: number = 0;
aboutToAppear(): void {
this.countdown(this.message);
if (this.timerId === 0 && this.message > 0) {
this.timerId = setInterval(() => {
if (this.message > 0) {
this.message--;
} else {
clearInterval(this.timerId);
this.timerId = 0;
}
}, 1000);
}
}
countdown(message: number) {
if (this.dataPreferences.hasSync('startTime')) {
console.info("The key 'startTime' is contained:" + this.dataPreferences.getSync('startTime', 600));
} else {
// 此处以此键值对不存在时写入数据为例
this.dataPreferences.putSync('startTime', Date.now());
let endtime: number = this.dataPreferences.getSync('startTime', Date.now()) as number + message * 1000;
this.dataPreferences.putSync('endTime', endtime);
this.dataPreferences.flushSync()
}
}
onPageShow(): void {
if (this.dataPreferences.hasSync('endTime')) {
let endtime: number = this.dataPreferences.getSync('endTime', Date.now()) as number;
let remainingTime: number =
Math.floor((endtime - Date.now()) / 1000) > 0 ? Math.floor((endtime - Date.now()) / 1000) : 0;
this.dataPreferences.putSync('remainingTime', remainingTime);
this.dataPreferences.flushSync()
this.message = remainingTime
}
}
onPageHide(): void {
this.dataPreferences.putSync('remainingTime', this.message);
this.dataPreferences.flushSync()
}
build() {
Column() {
Text(this.message.toString())
.fontSize(50)
.fontWeight(FontWeight.Bold)
.margin({
bottom: 5,
top: 5,
right: 5,
left: 5
}).backgroundColor('#FFF0F0F0')
if (this.message == 0) {
Text("倒计时结束UI")
}
}.width('100%')
}
}
【常见FAQ】
Q:使用putSync()方法保存数据后,退出app重新进入时为什么无法获取到此前保存的数据? A:使用putSync()方法只是保存数据到缓存的Preferences实例中,并未将Preferences实例的数据存储到持久化文件,导致无法读取数据。需要执行flushSync()方法。
更多关于HarmonyOS 鸿蒙Next后台定时器的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
楼主可以参考一下这个实现思路来实现:
aboutToAppear(): void {
//页面进入时检查
// 1.获取首选项的值
// 2.this.currentTime是否倒计时完成
//没完成继续处理倒计时逻辑
}
aboutToDisappear(): void {
if (this.currentTime < 10) {
//使用首选项存储this.currentTime剩下的时间
}
}
Button('开始倒计时:' + (this.currentTime > 0 ? this.currentTime.toString() : ''))
.onClick((event: ClickEvent) => {
let timeId = setInterval(() => {
if (this.currentTime < 10) {
this.currentTime++
} else {
clearTimeout(timeId)
}
}, 1000)
})
主要思路是“数据持久化”
可以将计时器计时的每一秒钟保存到用户首选项中,可以保证数据永远不会丢失,即时进程被杀掉也不影响。
大致思路------ 使用 AppStorage 保存剩余时间、倒计时开始时间戳;页面销毁前记录当前剩余时间和触发倒计时的初始时间点。
AppStorage.SetOrCreate<number>('remainingTime', 60);
AppStorage.SetOrCreate<number>('startTimestamp', new Date().getTime());
页面创建时检查存储中是否存在未完成的倒计时;根据存储的起始时间和当前时间差计算实际剩余时间。
aboutToAppear() {
const savedTime = AppStorage.Get<number>('remainingTime');
const startTime = AppStorage.Get<number>('startTimestamp');
if (savedTime > 0) {
const elapsed = Math.floor((Date.now() - startTime) / 1000);
this.remainingTime = Math.max(savedTime - elapsed, 0);
}
}
主要代码:
@Entry
@Component
struct CountdownPage {
@StorageLink('remainingTime') remainingTime: number = 60;
@StorageLink('startTimestamp') startTimestamp: number = 0;
private timerId: number = 0;
// 启动/恢复倒计时
startTimer() {
if (this.timerId === 0 && this.remainingTime > 0) {
this.timerId = setInterval(() => {
if (this.remainingTime > 0) {
this.remainingTime--;
// 更新界面元素
} else {
clearInterval(this.timerId);
this.timerId = 0;
}
}, 1000);
}
}
// 页面显示时触发
onPageShow() {
if (this.remainingTime > 0) {
this.startTimer();
}
}
// 页面销毁前保存状态
aboutToDisappear() {
AppStorage.SetOrCreate('remainingTime', this.remainingTime);
AppStorage.SetOrCreate('startTimestamp', Date.now());
if (this.timerId !== 0) {
clearInterval(this.timerId);
this.timerId = 0;
}
}
build() {
// 界面代码
}
}
背景知识:
需要重启后还需要获取到之前的值,那一定是退出app时将倒计时时间进行缓存了。用到缓存就需要将数据写入文件进行持久化操作。我们这里用到的时 用户首选项 持久化。提供Key-Value键值型的数据处理能力,Key键为string类型,要求非空且长度不超过1024个字节。Value值为string类型,请使用UTF-8编码格式,可以为空,不为空时长度不超过16MB。支持: number | string | boolean | Array<number> | Array<string> | Array<boolean> | Uint8Array | object | bigint;数据类型。
问题处理:
首先我们需要在页面进行退出时(aboutToDisappear())将倒计时数据进行缓存起来。
aboutToDisappear(): void {
PreferencesUtils.put("saveTime",this.time)
if(this.timer!=0){
clearInterval(this.timer)
}
console.log("aboutToDisappear "+PreferencesUtils.getNumber("saveTime"))
}
然后再页面首次加载时方法去除缓存的值:
aboutToAppear(): void {
// this.getUIContext().getSharedLocalStorage()
let second = PreferencesUtils.getNumber("saveTime")
console.log("aboutToAppear "+second)
if(second>0){
this.time = second
this.countdown()
}
}
倒计时方法:
countdown(){
this.timer = setInterval(() => {
this.time--;
if (this.time <= 0) {
clearInterval(this.timer)
this.timer=0;
}
}, 1000)
}
PreferencesUtils 工具类:
import { preferences, ValueType } from "@kit.ArkData";
export class PreferencesUtils{
private static dataPreferences:preferences.Preferences | null = null;
static init(context: Context){
let options: preferences.Options = { name: 'myStore' };
PreferencesUtils.dataPreferences = preferences.getPreferencesSync(context, options);
}
static has(key:string):boolean{
if(PreferencesUtils.dataPreferences==null){
return false
}
return PreferencesUtils.dataPreferences?.hasSync(key) ?? false
}
static put(key:string,data:ValueType){
if(PreferencesUtils.dataPreferences==null){
return
}
PreferencesUtils.dataPreferences.putSync(key,data)
PreferencesUtils.dataPreferences.flushSync()
}
static getString(key:string):string{
if(PreferencesUtils.dataPreferences==null){
return ""
}
return PreferencesUtils.dataPreferences?.getSync(key,"") as string
}
static getNumber(key:string):number{
if(PreferencesUtils.dataPreferences==null){
return 0
}
return PreferencesUtils.dataPreferences?.getSync(key,0) as number
}
}
工具类需要在 EntryAbility->onCreate()进行初始化:
PreferencesUtils.init(this.context)
实例如下:
鸿蒙Next的后台定时器主要通过@BackgroundTask
装饰器和backgroundTaskManager
模块实现。使用backgroundTaskManager.startBackgroundTask()
可创建后台任务,支持延迟和周期性执行。定时器任务需在onCreate()
中注册,通过backgroundTaskManager.stopBackgroundTask()
停止。注意任务最长持续运行时间为3分钟,超时自动终止。系统会限制高频任务(间隔<5秒)的触发。任务触发时应用需处于后台或灭屏状态,前台时定时器可能不生效。
在HarmonyOS Next中实现后台定时器持久化,可以使用以下方案:
- 使用持久化存储保存关键数据:
- 在页面销毁时(onDestroy),将定时器的结束时间戳存入Preferences:
import preferences from '@ohos.data.preferences';
// 保存结束时间
async function saveEndTime(endTime: number) {
let prefs = await preferences.getPreferences(context, 'timerData');
await prefs.put('endTime', endTime);
await prefs.flush();
}
- 页面初始化时恢复定时器:
async function restoreTimer() {
let prefs = await preferences.getPreferences(context, 'timerData');
let endTime = await prefs.get('endTime', 0);
if (endTime > 0) {
const now = new Date().getTime();
const remaining = endTime - now;
if (remaining > 0) {
// 重新创建定时器
startTimer(remaining);
} else {
// 倒计时已结束
updateUI();
}
}
}
- 定时器实现:
let timer: number | null = null;
function startTimer(duration: number) {
timer = setInterval(() => {
duration -= 1000;
if (duration <= 0) {
clearInterval(timer!);
updateUI();
}
}, 1000);
}
- 页面生命周期处理:
onDestroy() {
if (timer) {
clearInterval(timer);
const endTime = new Date().getTime() + remainingTime;
saveEndTime(endTime);
}
}
注意事项:
- 使用Preferences存储简单可靠,适合这种场景
- 时间计算使用系统时间戳,确保准确性
- 记得在定时器结束时清理存储的数据
- 考虑应用进程被杀死的情况,可以在AppStorage中做备份
这种方案能正确处理页面销毁和重新创建时的定时器状态恢复。