HarmonyOS开发:定时任务——设备定时控制

举报
Jack20 发表于 2026/06/26 15:56:59 2026/06/26
【摘要】 HarmonyOS开发:定时任务——设备定时控制📌 核心要点:定时任务是智能家居的基础能力,cron表达式定义执行规则,后台任务保活确保定时触发不丢,冲突检测和优先级机制避免定时打架。 背景与动机你每天早上7点起床,7点05分开灯,7点10分开空调,7点30分关空调关灯出门。每天重复,从不间断——直到有一天你忘了关空调,电费账单让你肉疼。这就是定时任务的价值。让设备按时间表自动执行,不依...

HarmonyOS开发:定时任务——设备定时控制

📌 核心要点:定时任务是智能家居的基础能力,cron表达式定义执行规则,后台任务保活确保定时触发不丢,冲突检测和优先级机制避免定时打架。

背景与动机

你每天早上7点起床,7点05分开灯,7点10分开空调,7点30分关空调关灯出门。每天重复,从不间断——直到有一天你忘了关空调,电费账单让你肉疼。

这就是定时任务的价值。让设备按时间表自动执行,不依赖人的记忆。

定时任务看起来简单——设个时间,到了就执行。但做起来有几个关键问题:App被杀了定时还能触发吗?两个定时任务冲突了怎么办?定时任务执行时设备离线了怎么办?用户改了时区定时还准吗?

这些问题不解决,定时任务就是摆设——用户设了定时,结果定时没触发,设备没控制,还不如手动操作。

核心原理

定时任务调度架构

定时任务的调度分三层:规则层 → 调度层 → 执行层。

flowchart TD
    A[用户创建定时任务] --> B[规则层:解析cron表达式]
    B --> C[计算下次执行时间]
    C --> D[调度层:注册到系统调度器]
    
    D --> E{触发方式}
    E -->|前台| F[App内定时器触发]
    E -->|后台| G[系统后台任务唤醒]
    E -->|云端| H[服务端推送触发]
    
    F --> I[执行层:获取任务详情]
    G --> I
    H --> I
    
    I --> J{设备是否在线?}
    J -->|在线| K[下发控制指令]
    J -->|离线| L[记录失败,等待重试]
    
    K --> M{指令是否成功?}
    M -->|成功| N[更新任务状态]
    M -->|失败| O{重试次数<3?}
    
    O -->|| P[延迟重试]
    O -->|| Q[标记失败,通知用户]
    
    L --> R{是否在重试窗口内?}
    R -->|| S[设备上线后补执行]
    R -->|| T[跳过本次,等下次定时]
    
    classDef rule fill:#1565C0,color:#fff,stroke:#0D47A1
    classDef schedule fill:#2E7D32,color:#fff,stroke:#1B5E20
    classDef execute fill:#E65100,color:#fff,stroke:#BF360C
    classDef result fill:#6A1B9A,color:#fff,stroke:#4A148C
    classDef error fill:#C62828,color:#fff,stroke:#B71C1C

    class A,B,C,rule
    class D,E,F,G,H,schedule
    class I,J,K,execute
    class N,S,result
    class L,O,P,Q,R,T,error

Cron表达式

Cron表达式是定时任务的灵魂。它用5个字段定义执行时间:

┌───── 分钟 (0-59)
│ ┌───── 小时 (0-23)
│ │ ┌─────  (1-31)
│ │ │ ┌─────  (1-12)
│ │ │ │ ┌───── 星期 (0-6, 0=周日)
│ │ │ │ │
* * * * *

常见场景的cron表达式:

场景 Cron表达式 说明
每天7:00 0 7 * * * 每天早上7点
工作日8:30 30 8 * * 1-5 周一到周五8:30
每小时整点 0 * * * * 每小时执行一次
每30分钟 */30 * * * * 每30分钟执行一次
每天晚上11点 0 23 * * * 每天晚上11点

后台任务保活

定时任务最大的敌人是系统杀后台。HarmonyOS对后台App的管控很严格——App进后台一段时间后,系统可能回收资源,定时器就停了。

三层保活策略:

  1. 前台保活:App在前台时,用setInterval定时检查
  2. 后台保活:App在后台时,用reminderAgentManager注册系统级提醒
  3. 云端保活:定时规则上传服务端,服务端到时间推送通知给App

三层保活不是都需要的。如果定时精度要求不高(±1分钟),后台保活就够了;如果要求精确到秒,必须用前台保活或云端保活。

定时任务冲突

两个定时任务在同一时间操作同一设备——任务A在7:00开灯,任务B也在7:00关灯——听谁的?

冲突解决规则:

  1. 优先级高的优先:每个定时任务有优先级,优先级高的覆盖低的
  2. 后创建的优先:优先级相同时,后创建的任务覆盖先创建的
  3. 用户选择:冲突时弹窗让用户选择执行哪个

实际项目中,优先级用得最多。用户创建定时任务时可以设置优先级,默认为0(最低)。

代码实战

基础用法:定时任务调度引擎

实现定时任务的创建、调度、执行、冲突检测。

// services/TimerTaskEngine.ets
// 定时任务调度引擎 - cron解析、任务调度、冲突检测

// 定时任务定义
interface TimerTask {
  taskId: string
  name: string             // 任务名称
  enabled: boolean         // 是否启用
  cronExpression: string   // cron表达式
  priority: number         // 优先级(0-10,10最高)
  actions: Array<{         // 执行动作
    deviceId: string
    property: string
    value: Object
  }>
  lastExecutedAt?: number  // 上次执行时间
  nextExecuteAt?: number   // 下次执行时间
  retryCount: number       // 当前重试次数
  maxRetry: number         // 最大重试次数
  createdAt: number
}

// 任务执行结果
interface TaskResult {
  taskId: string
  success: boolean
  executedAt: number
  failedActions: string[]  // 失败的设备ID
  conflicts: string[]      // 冲突的任务ID
}

class TimerTaskEngine {
  // 已注册的任务
  private tasks: Map<string, TimerTask> = new Map()
  // 定时器映射
  private timers: Map<string, number> = new Map()
  // 动作执行器
  private actionExecutor?: (deviceId: string, property: string, value: Object) => Promise<boolean>
  // 主调度定时器
  private schedulerTimer?: number
  // 调度间隔(秒)
  private readonly SCHEDULE_INTERVAL = 30

  // 设置动作执行器
  setActionExecutor(executor: (deviceId: string, property: string, value: Object) => Promise<boolean>): void {
    this.actionExecutor = executor
  }

  // 启动调度器
  startScheduler(): void {
    // 每30秒检查一次是否有任务需要执行
    this.schedulerTimer = setInterval(() => {
      this.checkAndExecute()
    }, this.SCHEDULE_INTERVAL * 1000)

    // 立即检查一次
    this.checkAndExecute()
  }

  // 停止调度器
  stopScheduler(): void {
    if (this.schedulerTimer) {
      clearInterval(this.schedulerTimer)
      this.schedulerTimer = undefined
    }
  }

  // 添加定时任务
  addTask(task: TimerTask): void {
    task.nextExecuteAt = this.calculateNextExecution(task.cronExpression)
    this.tasks.set(task.taskId, task)
  }

  // 移除定时任务
  removeTask(taskId: string): void {
    this.tasks.delete(taskId)
    const timer = this.timers.get(taskId)
    if (timer) {
      clearInterval(timer)
      this.timers.delete(taskId)
    }
  }

  // 启用/禁用任务
  toggleTask(taskId: string, enabled: boolean): void {
    const task = this.tasks.get(taskId)
    if (task) {
      task.enabled = enabled
      if (enabled) {
        task.nextExecuteAt = this.calculateNextExecution(task.cronExpression)
      }
    }
  }

  // 检查并执行到期任务
  private async checkAndExecute(): Promise<void> {
    const now = Date.now()

    this.tasks.forEach(async (task) => {
      if (!task.enabled || !task.nextExecuteAt) return

      // 检查是否到达执行时间(允许30秒误差)
      if (now >= task.nextExecuteAt - this.SCHEDULE_INTERVAL * 1000) {
        await this.executeTask(task.taskId)
        // 计算下次执行时间
        task.nextExecuteAt = this.calculateNextExecution(task.cronExpression)
      }
    })
  }

  // 执行任务
  async executeTask(taskId: string): Promise<TaskResult> {
    const task = this.tasks.get(taskId)
    if (!task || !task.enabled) {
      return { taskId, success: false, executedAt: Date.now(), failedActions: [], conflicts: [] }
    }

    // 冲突检测
    const conflicts = this.detectConflicts(task)

    // 执行动作
    const failedActions: string[] = []

    for (const action of task.actions) {
      // 检查是否与冲突任务的动作冲突
      const isConflicting = this.isActionConflicting(task, action, conflicts)
      if (isConflicting) {
        // 优先级低的跳过
        if (conflicts.some(cId => {
          const conflictTask = this.tasks.get(cId)
          return conflictTask && conflictTask.priority >= task.priority
        })) {
          failedActions.push(action.deviceId)
          continue
        }
      }

      // 执行动作
      if (this.actionExecutor) {
        const success = await this.actionExecutor(action.deviceId, action.property, action.value)
        if (!success) {
          failedActions.push(action.deviceId)
        }
      }
    }

    task.lastExecutedAt = Date.now()
    task.retryCount = 0

    return {
      taskId,
      success: failedActions.length === 0,
      executedAt: Date.now(),
      failedActions,
      conflicts
    }
  }

  // 冲突检测:检查同一时间是否有其他任务操作相同设备
  private detectConflicts(task: TimerTask): string[] {
    const conflicts: string[] = []
    const now = Date.now()

    this.tasks.forEach((otherTask, otherTaskId) => {
      if (otherTaskId === task.taskId || !otherTask.enabled) return

      // 检查执行时间是否重叠(5分钟内视为同时)
      if (otherTask.nextExecuteAt &&
          Math.abs(otherTask.nextExecuteAt - (task.nextExecuteAt || now)) < 5 * 60 * 1000) {
        // 检查是否有动作操作同一设备
        const hasOverlap = task.actions.some(action =>
          otherTask.actions.some(otherAction =>
            otherAction.deviceId === action.deviceId &&
            otherAction.property === action.property &&
            otherAction.value !== action.value
          )
        )
        if (hasOverlap) {
          conflicts.push(otherTaskId)
        }
      }
    })

    return conflicts
  }

  // 检查动作是否与冲突任务冲突
  private isActionConflicting(
    task: TimerTask,
    action: { deviceId: string, property: string, value: Object },
    conflicts: string[]
  ): boolean {
    return conflicts.some(conflictId => {
      const conflictTask = this.tasks.get(conflictId)
      return conflictTask?.actions.some(a =>
        a.deviceId === action.deviceId &&
        a.property === action.property &&
        a.value !== action.value
      )
    })
  }

  // 计算下次执行时间(简化版cron解析)
  private calculateNextExecution(cronExpression: string): number {
    const parts = cronExpression.split(' ')
    if (parts.length !== 5) return Date.now() + 24 * 60 * 60 * 1000

    const now = new Date()
    const minute = parts[0] === '*' ? now.getMinutes() : parseInt(parts[0])
    const hour = parts[1] === '*' ? now.getHours() : parseInt(parts[1])

    // 简化处理:只处理固定时间和每天重复
    const next = new Date()
    next.setHours(hour, minute, 0, 0)

    // 如果今天的时间已过,设为明天
    if (next.getTime() <= now.getTime()) {
      next.setDate(next.getDate() + 1)
    }

    // 处理星期字段
    if (parts[4] !== '*') {
      const weekDays = this.parseWeekDays(parts[4])
      while (!weekDays.includes(next.getDay())) {
        next.setDate(next.getDate() + 1)
      }
    }

    return next.getTime()
  }

  // 解析星期字段
  private parseWeekDays(weekStr: string): number[] {
    if (weekStr === '*') return [0, 1, 2, 3, 4, 5, 6]

    const days: number[] = []
    weekStr.split(',').forEach(part => {
      if (part.includes('-')) {
        const [start, end] = part.split('-').map(Number)
        for (let i = start; i <= end; i++) {
          days.push(i)
        }
      } else {
        days.push(parseInt(part))
      }
    })
    return days
  }

  // 获取所有任务
  getAllTasks(): TimerTask[] {
    return Array.from(this.tasks.values())
  }

  // 获取任务
  getTask(taskId: string): TimerTask | undefined {
    return this.tasks.get(taskId)
  }
}

export default new TimerTaskEngine()
export type { TimerTask, TaskResult }

进阶用法:后台任务保活

用HarmonyOS的后台任务管理器保活定时调度,确保App在后台时定时任务也能触发。

// services/BackgroundTaskKeeper.ets
// 后台任务保活服务

import { reminderAgentManager } from '@kit.ReminderAgentKit'
import { backgroundTaskManager } from '@kit.ResourcescheduleServiceKit'
import { notificationManager } from '@kit.NotificationKit'
import TimerTaskEngine, { TimerTask } from './TimerTaskEngine'

class BackgroundTaskKeeper {
  private reminderIds: number[] = []

  // 注册后台提醒
  async registerReminders(tasks: TimerTask[]): Promise<void> {
    // 先清除旧的提醒
    await this.unregisterAllReminders()

    for (const task of tasks) {
      if (!task.enabled || !task.nextExecuteAt) continue

      try {
        // 计算距离下次执行的分钟数
        const minutesToNext = Math.ceil((task.nextExecuteAt - Date.now()) / 60000)
        if (minutesToNext <= 0) continue

        // 创建提醒请求
        const reminderRequest: reminderAgentManager.ReminderRequestTimer = {
          reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_TIMER,
          triggerTimeInSeconds: minutesToNext * 60,
          actionButton: [
            {
              title: '执行',
              type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CUSTOM
            }
          ],
          wantAgent: undefined,
          maxScreenDuration: 30,
          title: '智能家居定时任务',
          content: `即将执行:${task.name}`
        }

        const reminderId = await reminderAgentManager.publishReminder(reminderRequest)
        this.reminderIds.push(reminderId)

      } catch (err) {
        console.error(`注册提醒失败: ${JSON.stringify(err)}`)
      }
    }
  }

  // 申请长时后台任务
  async requestContinuousTask(): Promise<void> {
    try {
      // 申请DATA_TRANSFER类型的后台长时任务
      backgroundTaskManager.requestSuspendDelay('定时任务保活', () => {
        // 后台任务即将超时的回调
        console.warn('后台任务即将超时')
      })
    } catch (err) {
      console.error(`申请后台任务失败: ${JSON.stringify(err)}`)
    }
  }

  // 取消后台长时任务
  async cancelContinuousTask(): Promise<void> {
    try {
      backgroundTaskManager.cancelSuspendDelay()
    } catch (err) {
      console.error(`取消后台任务失败: ${JSON.stringify(err)}`)
    }
  }

  // 清除所有提醒
  async unregisterAllReminders(): Promise<void> {
    for (const id of this.reminderIds) {
      try {
        await reminderAgentManager.cancelReminder(id)
      } catch (err) {
        // 忽略取消失败的提醒
      }
    }
    this.reminderIds = []
  }

  // 发送本地通知(任务执行结果)
  async notifyTaskResult(taskName: string, success: boolean): Promise<void> {
    try {
      // 请求通知权限
      await notificationManager.requestEnableNotification()

      const request: notificationManager.NotificationRequest = {
        id: Date.now(),
        content: {
          notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
          normal: {
            title: '定时任务执行完成',
            text: `${taskName}${success ? '执行成功' : '部分设备执行失败'}`
          }
        }
      }

      await notificationManager.publish(request)
    } catch (err) {
      console.error(`发送通知失败: ${JSON.stringify(err)}`)
    }
  }
}

export default new BackgroundTaskKeeper()

完整示例:定时任务管理页面

定时任务的创建、列表展示、启用禁用、冲突提示。

// pages/TimerTaskPage.ets
// 定时任务管理页面

import TimerTaskEngine, { TimerTask, TaskResult } from '../services/TimerTaskEngine'
import BackgroundTaskKeeper from '../services/BackgroundTaskKeeper'
import DeviceControlManager from '../services/DeviceControlManager'
import { promptAction } from '@kit.ArkUI'

@Entry
@Component
struct TimerTaskPage {
  @State tasks: TimerTask[] = []
  @State showCreateDialog: boolean = false
  @State newTaskName: string = ''
  @State newTaskCron: string = '0 7 * * *'
  @State newTaskDeviceId: string = 'light-001'
  @State newTaskProperty: string = 'power'
  @State newTaskValue: string = 'true'
  @State newTaskPriority: number = 5

  aboutToAppear() {
    // 注入动作执行器
    TimerTaskEngine.setActionExecutor(async (deviceId, property, value) => {
      DeviceControlManager.controlDevice(deviceId, property, value)
      return true
    })

    // 启动调度器
    TimerTaskEngine.startScheduler()

    // 申请后台保活
    BackgroundTaskKeeper.requestContinuousTask()

    this.loadTasks()
  }

  aboutToDisappear() {
    // 注册后台提醒(App进后台后靠提醒触发)
    BackgroundTaskKeeper.registerReminders(this.tasks)
  }

  loadTasks() {
    // 加载预设任务
    if (TimerTaskEngine.getAllTasks().length === 0) {
      const presets: TimerTask[] = [
        {
          taskId: 'timer-morning',
          name: '早安模式',
          enabled: true,
          cronExpression: '0 7 * * 1-5',
          priority: 5,
          actions: [
            { deviceId: 'light-001', property: 'power', value: true },
            { deviceId: 'ac-001', property: 'power', value: true },
            { deviceId: 'curtain-001', property: 'position', value: 100 }
          ],
          retryCount: 0, maxRetry: 3, createdAt: Date.now()
        },
        {
          taskId: 'timer-sleep',
          name: '睡眠模式',
          enabled: true,
          cronExpression: '0 23 * * *',
          priority: 8,
          actions: [
            { deviceId: 'light-001', property: 'power', value: false },
            { deviceId: 'ac-001', property: 'temperature', value: 27 },
            { deviceId: 'curtain-001', property: 'position', value: 0 }
          ],
          retryCount: 0, maxRetry: 3, createdAt: Date.now()
        },
        {
          taskId: 'timer-ac-off',
          name: '空调定时关闭',
          enabled: true,
          cronExpression: '30 8 * * 1-5',
          priority: 3,
          actions: [
            { deviceId: 'ac-001', property: 'power', value: false }
          ],
          retryCount: 0, maxRetry: 3, createdAt: Date.now()
        }
      ]

      presets.forEach(task => TimerTaskEngine.addTask(task))
    }

    this.tasks = TimerTaskEngine.getAllTasks()
  }

  // 创建新任务
  createTask() {
    if (!this.newTaskName.trim()) {
      promptAction.showToast({ message: '请输入任务名称' })
      return
    }

    const task: TimerTask = {
      taskId: `timer_${Date.now()}`,
      name: this.newTaskName,
      enabled: true,
      cronExpression: this.newTaskCron,
      priority: this.newTaskPriority,
      actions: [{
        deviceId: this.newTaskDeviceId,
        property: this.newTaskProperty,
        value: this.newTaskValue === 'true'
      }],
      retryCount: 0,
      maxRetry: 3,
      createdAt: Date.now()
    }

    // 冲突检测
    const existingTask = TimerTaskEngine.getTask(task.taskId)
    const conflicts = this.checkConflicts(task)

    if (conflicts.length > 0) {
      promptAction.showToast({
        message: `${conflicts.length}个任务存在冲突,请注意优先级设置`
      })
    }

    TimerTaskEngine.addTask(task)
    this.tasks = TimerTaskEngine.getAllTasks()
    this.showCreateDialog = false
    this.resetForm()
  }

  // 检查冲突
  checkConflicts(task: TimerTask): string[] {
    const conflicts: string[] = []
    this.tasks.forEach(existing => {
      if (!existing.enabled) return
      // 简化:cron相同视为可能冲突
      if (existing.cronExpression === task.cronExpression) {
        const hasOverlap = task.actions.some(a =>
          existing.actions.some(ea =>
            ea.deviceId === a.deviceId && ea.property === a.property
          )
        )
        if (hasOverlap) {
          conflicts.push(existing.name)
        }
      }
    })
    return conflicts
  }

  // 重置表单
  resetForm() {
    this.newTaskName = ''
    this.newTaskCron = '0 7 * * *'
    this.newTaskDeviceId = 'light-001'
    this.newTaskProperty = 'power'
    this.newTaskValue = 'true'
    this.newTaskPriority = 5
  }

  // 获取cron描述
  getCronDesc(cron: string): string {
    const parts = cron.split(' ')
    if (parts.length !== 5) return cron

    const minute = parts[0]
    const hour = parts[1]
    const week = parts[4]

    let timeDesc = `${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`
    let weekDesc = '每天'

    if (week === '1-5') {
      weekDesc = '工作日'
    } else if (week === '0,6') {
      weekDesc = '周末'
    } else if (week !== '*') {
      const weekNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
      weekDesc = week.split(',').map(w => weekNames[parseInt(w)] || w).join('、')
    }

    return `${weekDesc} ${timeDesc}`
  }

  build() {
    Navigation() {
      Column() {
        // 任务列表
        List({ space: 12 }) {
          ForEach(this.tasks, (task: TimerTask) => {
            ListItem() {
              Row() {
                // 任务信息
                Column() {
                  Text(task.name)
                    .fontSize(16)
                    .fontWeight(FontWeight.Bold)
                    .fontColor('#333333')

                  Text(this.getCronDesc(task.cronExpression))
                    .fontSize(13)
                    .fontColor('#007DFF')
                    .margin({ top: 4 })

                  // 动作描述
                  Text(task.actions.map(a => `${a.deviceId}.${a.property}=${a.value}`).join(', '))
                    .fontSize(12)
                    .fontColor('#999999')
                    .margin({ top: 2 })
                    .maxLines(1)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)

                // 优先级标签
                Text(`P${task.priority}`)
                  .fontSize(11)
                  .fontColor(task.priority >= 7 ? '#F44336' : '#999999')
                  .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                  .backgroundColor(task.priority >= 7 ? '#FFEBEE' : '#F5F5F5')
                  .borderRadius(8)
                  .margin({ right: 8 })

                // 启用开关
                Toggle({ type: ToggleType.Switch, isOn: task.enabled })
                  .selectedColor('#007DFF')
                  .width(48)
                  .height(28)
                  .onChange((isOn: boolean) => {
                    TimerTaskEngine.toggleTask(task.taskId, isOn)
                    this.tasks = TimerTaskEngine.getAllTasks()
                  })
              }
              .width('100%')
              .padding(16)
              .backgroundColor('#FFFFFF')
              .borderRadius(12)
            }
            .swipeAction({ end: this.DeleteButton(task.taskId) })
          })
        }
        .width('100%')
        .layoutWeight(1)
        .padding({ left: 16, right: 16, top: 12 })

        // 添加按钮
        Button('+ 创建定时任务')
          .width('90%')
          .height(48)
          .backgroundColor('#007DFF')
          .fontColor('#FFFFFF')
          .borderRadius(24)
          .margin({ top: 12, bottom: 24 })
          .onClick(() => { this.showCreateDialog = true })
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#F8F8F8')
    }
    .title('定时任务')
    .titleMode(NavigationTitleMode.Mini)
    .bindContentCover(this.showCreateDialog, this.CreateTaskDialog(), {
      modalTransition: ModalTransition.DEFAULT
    })
  }

  // 滑动删除按钮
  @Builder DeleteButton(taskId: string) {
    Button('删除')
      .height('100%')
      .width(72)
      .fontSize(14)
      .fontColor('#FFFFFF')
      .backgroundColor('#F44336')
      .borderRadius(0)
      .onClick(() => {
        TimerTaskEngine.removeTask(taskId)
        this.tasks = TimerTaskEngine.getAllTasks()
      })
  }

  // 创建任务对话框
  @Builder CreateTaskDialog() {
    Scroll() {
      Column() {
        Text('创建定时任务')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .margin({ bottom: 20 })

        // 任务名称
        Text('任务名称').fontSize(14).fontColor('#666666').margin({ bottom: 4 })
        TextInput({ placeholder: '如:早安模式', text: this.newTaskName })
          .fontSize(16)
          .onChange((value: string) => { this.newTaskName = value })
          .margin({ bottom: 16 })

        // 执行时间
        Text('执行时间(Cron表达式)').fontSize(14).fontColor('#666666').margin({ bottom: 4 })
        TextInput({ text: this.newTaskCron })
          .fontSize(16)
          .onChange((value: string) => { this.newTaskCron = value })
          .margin({ bottom: 4 })
        Text(`预览:${this.getCronDesc(this.newTaskCron)}`)
          .fontSize(12).fontColor('#007DFF').margin({ bottom: 16 })

        // 优先级
        Text('优先级').fontSize(14).fontColor('#666666').margin({ bottom: 4 })
        Row() {
          ForEach([1, 3, 5, 7, 10], (p: number) => {
            Text(`P${p}`)
              .fontSize(13)
              .fontColor(this.newTaskPriority === p ? '#FFFFFF' : '#333333')
              .backgroundColor(this.newTaskPriority === p ? '#007DFF' : '#F5F5F5')
              .borderRadius(12)
              .padding({ left: 12, right: 12, top: 6, bottom: 6 })
              .margin({ right: 8 })
              .onClick(() => { this.newTaskPriority = p })
          })
        }
        .margin({ bottom: 24 })

        // 按钮
        Row() {
          Button('取消')
            .width('45%').height(44)
            .backgroundColor('#F5F5F5').fontColor('#333333').borderRadius(22)
            .onClick(() => { this.showCreateDialog = false })
          Button('创建')
            .width('45%').height(44)
            .backgroundColor('#007DFF').fontColor('#FFFFFF').borderRadius(22)
            .onClick(() => this.createTask())
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
      }
      .padding(24)
    }
    .width('100%')
    .backgroundColor('#FFFFFF')
    .borderRadius({ topLeft: 24, topRight: 24 })
  }
}

踩坑与注意事项

1. App被杀后台后定时失效

这是定时任务最大的坑。HarmonyOS杀后台很积极,App进后台5-10分钟就可能被杀。setInterval直接失效,定时任务完全无法触发。

解法: 三层保活——前台用setInterval,后台用reminderAgentManager注册系统提醒,同时把定时规则上传服务端。服务端到时间推送通知,App收到通知后执行任务。最可靠的是云端保活,不依赖App是否在运行。

2. 时区变更

用户从北京飞到纽约,时区从UTC+8变成UTC-5。原来7:00的定时任务,是北京时间7:00还是纽约时间7:00?

解法: cron表达式关联时区。创建定时任务时记录时区,执行时按创建时区计算。或者提供选项让用户选择"按设备时区"还是"按固定时区"。

3. 定时任务的幂等性

定时任务每分钟执行一次,但上一分钟的执行还没完成(设备响应慢),下一分钟又触发了——同一个动作被执行两次。

解法: 加执行锁。任务执行时标记为"执行中",执行完成后才清除标记。执行中的任务不会被重复触发。或者用lastExecutedAt字段,两次执行间隔必须大于任务的最小执行间隔。

4. 夏令时

有些国家有夏令时,时钟会往前或往后拨一小时。cron表达式0 7 * * *在夏令时切换的那天可能执行两次或零次。

解法: 使用UTC时间计算执行时间,不受夏令时影响。显示给用户时转换为本地时间。或者干脆不管——夏令时切换的那一天,定时任务差一次或多次,用户基本不会注意到。

5. Cron表达式的用户友好性

让用户输入0 7 * * 1-5?大多数用户不知道cron是什么。你让他们输入这个,他们直接放弃。

解法: 提供可视化的时间选择器,用户选"工作日早上7:00",App自动生成cron表达式。cron表达式只在高级模式显示,普通用户不需要看到。

HarmonyOS 6适配说明

HarmonyOS 6对定时任务做了几项增强:

  1. 精确闹钟API:新增@ohos.reminderAgentManager的精确闹钟能力,支持秒级精度的定时触发,不再只有分钟级。定时任务可以精确到秒执行。

  2. 后台任务类型扩展backgroundTaskManager新增DATA_TRANSFER类型的后台长时任务,专门用于IoT设备的数据传输和定时控制,系统不会轻易回收。

  3. 定时任务模板:智能家居Kit新增定时任务模板API,预置"早安模式"“睡眠模式”"定时关灯"等常见模板,用户选择模板后只需调整时间。

  4. 智能调度:系统根据用户习惯自动推荐定时任务。比如用户连续5天在7:00手动开灯,系统建议创建7:00的定时任务。

适配代码示例:

// HarmonyOS 6 精确闹钟
import { reminderAgentManager } from '@kit.ReminderAgentKit'

// 精确到秒的定时提醒
const reminder: reminderAgentManager.ReminderRequestAlarm = {
  reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_ALARM,
  hour: 7,
  minute: 0,
  // HarmonyOS 6新增:秒级精度
  second: 0,
  repeatMonths: [],     // 每月重复(空=不限制)
  repeatDays: [1, 2, 3, 4, 5],  // 周一到周五
  title: '早安模式',
  content: '即将执行早安模式定时任务',
  actionButton: [
    { title: '执行', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CUSTOM }
  ]
}

const reminderId = await reminderAgentManager.publishReminder(reminder)

总结

定时任务是智能家居的"闹钟"。没有定时任务,用户每天手动操作;有了定时任务,设备按时间表自动运行。

但定时任务最大的敌人是系统杀后台。三层保活(前台+后台+云端)是必须的,否则定时任务就是摆设。cron表达式是定时任务的灵魂,但不要让用户直接输入——提供可视化选择器。

维度 评价
学习难度 ⭐⭐⭐⭐ cron解析和后台保活有一定复杂度,冲突检测需要仔细设计
使用频率 ⭐⭐⭐⭐ 定时任务是智能家居的高频功能,用户几乎每天依赖
重要程度 ⭐⭐⭐⭐⭐ 定时不准=不可靠,不可靠的定时不如没有

一句话:定时任务的核心不是"到时间执行",而是"到时间一定能执行"。 后者才是真正的难点。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。