HarmonyOS开发:定时任务——设备定时控制
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进后台一段时间后,系统可能回收资源,定时器就停了。
三层保活策略:
- 前台保活:App在前台时,用
setInterval定时检查 - 后台保活:App在后台时,用
reminderAgentManager注册系统级提醒 - 云端保活:定时规则上传服务端,服务端到时间推送通知给App
三层保活不是都需要的。如果定时精度要求不高(±1分钟),后台保活就够了;如果要求精确到秒,必须用前台保活或云端保活。
定时任务冲突
两个定时任务在同一时间操作同一设备——任务A在7:00开灯,任务B也在7:00关灯——听谁的?
冲突解决规则:
- 优先级高的优先:每个定时任务有优先级,优先级高的覆盖低的
- 后创建的优先:优先级相同时,后创建的任务覆盖先创建的
- 用户选择:冲突时弹窗让用户选择执行哪个
实际项目中,优先级用得最多。用户创建定时任务时可以设置优先级,默认为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对定时任务做了几项增强:
-
精确闹钟API:新增
@ohos.reminderAgentManager的精确闹钟能力,支持秒级精度的定时触发,不再只有分钟级。定时任务可以精确到秒执行。 -
后台任务类型扩展:
backgroundTaskManager新增DATA_TRANSFER类型的后台长时任务,专门用于IoT设备的数据传输和定时控制,系统不会轻易回收。 -
定时任务模板:智能家居Kit新增定时任务模板API,预置"早安模式"“睡眠模式”"定时关灯"等常见模板,用户选择模板后只需调整时间。
-
智能调度:系统根据用户习惯自动推荐定时任务。比如用户连续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解析和后台保活有一定复杂度,冲突检测需要仔细设计 |
| 使用频率 | ⭐⭐⭐⭐ 定时任务是智能家居的高频功能,用户几乎每天依赖 |
| 重要程度 | ⭐⭐⭐⭐⭐ 定时不准=不可靠,不可靠的定时不如没有 |
一句话:定时任务的核心不是"到时间执行",而是"到时间一定能执行"。 后者才是真正的难点。
- 点赞
- 收藏
- 关注作者
评论(0)