HarmonyOS 鸿蒙Next后台定时器

HarmonyOS 鸿蒙Next后台定时器

有一个页面设置了一个定时器,倒计时结束后页面的元素会发生变化

当倒计时还在进行中时,销毁页面,如何在下次创建该页面时正确的获取并展示剩余时间(要计算页面销毁期间的时间),如果还有剩余时间就继续进行倒计时

7 回复

【背景知识】

用户首选项为应用提供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)

实例如下:

cke_11175.gif

鸿蒙Next的后台定时器主要通过@BackgroundTask装饰器和backgroundTaskManager模块实现。使用backgroundTaskManager.startBackgroundTask()可创建后台任务,支持延迟和周期性执行。定时器任务需在onCreate()中注册,通过backgroundTaskManager.stopBackgroundTask()停止。注意任务最长持续运行时间为3分钟,超时自动终止。系统会限制高频任务(间隔<5秒)的触发。任务触发时应用需处于后台或灭屏状态,前台时定时器可能不生效。

在HarmonyOS Next中实现后台定时器持久化,可以使用以下方案:

  1. 使用持久化存储保存关键数据:
  • 在页面销毁时(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();
}
  1. 页面初始化时恢复定时器:
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();
    }
  }
}
  1. 定时器实现:
let timer: number | null = null;

function startTimer(duration: number) {
  timer = setInterval(() => {
    duration -= 1000;
    if (duration <= 0) {
      clearInterval(timer!);
      updateUI();
    }
  }, 1000);
}
  1. 页面生命周期处理:
onDestroy() {
  if (timer) {
    clearInterval(timer);
    const endTime = new Date().getTime() + remainingTime;
    saveEndTime(endTime);
  }
}

注意事项:

  1. 使用Preferences存储简单可靠,适合这种场景
  2. 时间计算使用系统时间戳,确保准确性
  3. 记得在定时器结束时清理存储的数据
  4. 考虑应用进程被杀死的情况,可以在AppStorage中做备份

这种方案能正确处理页面销毁和重新创建时的定时器状态恢复。

回到顶部