HarmonyOS NEXT 后台代理提醒(闹钟)开发完整教程

前言

本文详细介绍如何在 HarmonyOS NEXT 应用中使用后台代理提醒(Reminder Agent)功能,实现类似闹钟的定时提醒功能。即使应用被关闭,系统也会在指定时间弹出通知提醒用户。

本教程适用场景:

  • 日程提醒
  • 任务提醒
  • 吃药提醒
  • 定时打卡提醒
  • 其他需要定时通知的场景

一、功能介绍

1.1 什么是后台代理提醒

后台代理提醒(Reminder Agent)是 HarmonyOS 提供的系统级定时提醒服务。与普通的定时器不同,它具有以下特点:

  • 系统级服务:由系统管理,不受应用进程影响
  • 应用关闭后仍有效:即使用户关闭应用,提醒依然会在指定时间触发
  • 支持多种提醒类型:日历提醒、闹钟提醒、倒计时提醒
  • 丰富的交互:支持响铃、振动、通知栏显示、按钮操作

1.2 提醒类型

类型 说明 适用场景
REMINDER_TYPE_CALENDAR 日历提醒,指定具体日期时间 日程、预约、纪念日
REMINDER_TYPE_ALARM 闹钟提醒,支持重复 每日闹钟、周期性提醒
REMINDER_TYPE_TIMER 倒计时提醒 计时器、短时间提醒

二、开发准备

2.1 配置权限

module.json5 中添加后台代理提醒权限:

entry/src/main/module.json5

{
  "module": {
    "name": "entry",
    "type": "entry",
    "requestPermissions": [
      {
        "name": "ohos.permission.PUBLISH_AGENT_REMINDER",
        "reason": "$string:reminder_permission_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      }
    ]
  }
}

2.2 添加权限说明字符串

entry/src/main/resources/base/element/string.json

{
  "string": [
    {
      "name": "reminder_permission_reason",
      "value": "用于设置定时提醒通知"
    }
  ]
}

2.3 导入模块

import { reminderAgentManager } from '@kit.BackgroundTasksKit';
import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';

三、设置日历提醒

日历提醒是最常用的提醒类型,可以指定具体的年月日时分触发。

3.1 创建提醒请求

/**
 * 设置日历提醒
 * @param reminderId 提醒的唯一标识(用于后续取消)
 * @param title 提醒标题
 * @param content 提醒内容
 * @param triggerTime 触发时间(毫秒时间戳)
 */
async function setCalendarReminder(
  reminderId: number,
  title: string,
  content: string,
  triggerTime: number
): Promise<number | null> {
  
  // 1. 将时间戳转换为日期对象
  const triggerDate = new Date(triggerTime);
  
  console.info(`设置提醒: ${title}, 触发时间: ${triggerDate.toString()}`);
  
  // 2. 检查时间是否在未来
  const now = new Date();
  if (triggerDate.getTime() <= now.getTime()) {
    console.error('提醒时间必须在未来');
    return null;
  }
  
  // 3. 创建日历提醒请求
  const reminderRequest: reminderAgentManager.ReminderRequestCalendar = {
    // 提醒类型:日历
    reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_CALENDAR,
    
    // 触发时间
    dateTime: {
      year: triggerDate.getFullYear(),
      month: triggerDate.getMonth() + 1,  // 月份从1开始
      day: triggerDate.getDate(),
      hour: triggerDate.getHours(),
      minute: triggerDate.getMinutes(),
      second: 0
    },
    
    // 通知内容
    title: title,
    content: content,
    expiredContent: '提醒已过期',
    snoozeContent: '点击稍后提醒',
    
    // 通知配置
    notificationId: reminderId,
    slotType: notificationManager.SlotType.SOCIAL_COMMUNICATION,
    
    // 延迟提醒配置
    snoozeTimes: 2,           // 最多延迟2次
    timeInterval: 5 * 60,     // 延迟间隔5分钟(单位:秒)
    
    // 操作按钮
    actionButton: [
      {
        title: '关闭',
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE
      },
      {
        title: '稍后提醒',
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_SNOOZE
      }
    ],
    
    // 点击通知后打开的页面
    wantAgent: {
      pkgName: 'com.example.myapp',      // 替换为你的包名
      abilityName: 'EntryAbility'
    },
    
    // 响铃时长(秒)
    ringDuration: 10
  };
  
  // 4. 发布提醒
  try {
    const publishedId = await reminderAgentManager.publishReminder(reminderRequest);
    console.info(`提醒发布成功,系统分配ID: ${publishedId}`);
    return publishedId;
  } catch (err) {
    const error = err as BusinessError;
    console.error(`发布提醒失败: ${error.code} - ${error.message}`);
    return null;
  }
}

3.2 使用示例

// 设置一个10分钟后的提醒
const triggerTime = Date.now() + 10 * 60 * 1000;  // 10分钟后

const systemId = await setCalendarReminder(
  1001,                    // 自定义ID
  '会议提醒',              // 标题
  '10分钟后有一个重要会议', // 内容
  triggerTime              // 触发时间
);

if (systemId !== null) {
  console.info(`提醒设置成功,系统ID: ${systemId}`);
  // 保存 systemId,用于后续取消提醒
}

四、设置闹钟提醒(支持重复)

闹钟提醒适合需要周期性重复的场景,如每日闹钟、工作日提醒等。

4.1 创建闹钟提醒

/**
 * 设置闹钟提醒(支持重复)
 * @param reminderId 提醒ID
 * @param title 标题
 * @param content 内容
 * @param hour 小时(0-23)
 * @param minute 分钟(0-59)
 * @param daysOfWeek 重复的星期几(1-7,1代表周一)
 */
async function setAlarmReminder(
  reminderId: number,
  title: string,
  content: string,
  hour: number,
  minute: number,
  daysOfWeek: number[]
): Promise<number | null> {
  
  console.info(`设置闹钟: ${title}, 时间: ${hour}:${minute}, 重复: ${daysOfWeek}`);
  
  // 创建闹钟提醒请求
  const reminderRequest: reminderAgentManager.ReminderRequestAlarm = {
    // 提醒类型:闹钟
    reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_ALARM,
    
    // 触发时间(每天的几点几分)
    hour: hour,
    minute: minute,
    
    // 重复的星期几(1-7,1代表周一,7代表周日)
    // 空数组表示只响一次
    daysOfWeek: daysOfWeek,
    
    // 通知内容
    title: title,
    content: content,
    
    // 通知配置
    notificationId: reminderId,
    slotType: notificationManager.SlotType.SOCIAL_COMMUNICATION,
    
    // 操作按钮
    actionButton: [
      {
        title: '关闭',
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE
      },
      {
        title: '稍后提醒',
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_SNOOZE
      }
    ],
    
    // 延迟配置
    snoozeTimes: 3,
    timeInterval: 5 * 60,
    
    // 点击动作
    wantAgent: {
      pkgName: 'com.example.myapp',
      abilityName: 'EntryAbility'
    },
    
    ringDuration: 30  // 响铃30秒
  };
  
  try {
    const publishedId = await reminderAgentManager.publishReminder(reminderRequest);
    console.info(`闹钟设置成功,系统ID: ${publishedId}`);
    return publishedId;
  } catch (err) {
    const error = err as BusinessError;
    console.error(`设置闹钟失败: ${error.code} - ${error.message}`);
    return null;
  }
}

4.2 使用示例

// 设置工作日早上8点的闹钟
const systemId = await setAlarmReminder(
  2001,                      // 自定义ID
  '起床闹钟',                // 标题
  '该起床上班了!',          // 内容
  8,                         // 8点
  0,                         // 0分
  [1, 2, 3, 4, 5]           // 周一到周五
);

// 设置每天晚上10点的提醒
const systemId2 = await setAlarmReminder(
  2002,
  '睡眠提醒',
  '该休息了,早睡早起身体好',
  22,
  0,
  [1, 2, 3, 4, 5, 6, 7]     // 每天
);

// 设置只响一次的闹钟(不重复)
const systemId3 = await setAlarmReminder(
  2003,
  '一次性提醒',
  '这是一个只响一次的提醒',
  14,
  30,
  []                         // 空数组表示不重复
);

五、设置倒计时提醒

倒计时提醒适合短时间的计时场景,如煮饭计时、运动计时等。

5.1 创建倒计时提醒

/**
 * 设置倒计时提醒
 * @param reminderId 提醒ID
 * @param title 标题
 * @param content 内容
 * @param countdownSeconds 倒计时秒数
 */
async function setTimerReminder(
  reminderId: number,
  title: string,
  content: string,
  countdownSeconds: number
): Promise<number | null> {
  
  console.info(`设置倒计时: ${title}, ${countdownSeconds}秒后提醒`);
  
  // 创建倒计时提醒请求
  const reminderRequest: reminderAgentManager.ReminderRequestTimer = {
    // 提醒类型:倒计时
    reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_TIMER,
    
    // 倒计时时长(秒)
    triggerTimeInSeconds: countdownSeconds,
    
    // 通知内容
    title: title,
    content: content,
    
    // 通知配置
    notificationId: reminderId,
    slotType: notificationManager.SlotType.SOCIAL_COMMUNICATION,
    
    // 操作按钮
    actionButton: [
      {
        title: '关闭',
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE
      }
    ],
    
    // 点击动作
    wantAgent: {
      pkgName: 'com.example.myapp',
      abilityName: 'EntryAbility'
    },
    
    ringDuration: 15
  };
  
  try {
    const publishedId = await reminderAgentManager.publishReminder(reminderRequest);
    console.info(`倒计时设置成功,系统ID: ${publishedId}`);
    return publishedId;
  } catch (err) {
    const error = err as BusinessError;
    console.error(`设置倒计时失败: ${error.code} - ${error.message}`);
    return null;
  }
}

5.2 使用示例

// 设置5分钟倒计时
const systemId = await setTimerReminder(
  3001,
  '计时结束',
  '5分钟倒计时已结束',
  5 * 60  // 300秒
);

// 设置30秒倒计时
const systemId2 = await setTimerReminder(
  3002,
  '泡面好了',
  '泡面时间到,可以开吃了!',
  30
);

六、管理提醒

6.1 取消单个提醒

/**
 * 取消指定的提醒
 * @param reminderId 系统分配的提醒ID(publishReminder返回的ID)
 */
async function cancelReminder(reminderId: number): Promise<boolean> {
  console.info(`取消提醒: ${reminderId}`);
  
  try {
    await reminderAgentManager.cancelReminder(reminderId);
    console.info('取消提醒成功');
    return true;
  } catch (err) {
    const error = err as BusinessError;
    console.error(`取消提醒失败: ${error.code} - ${error.message}`);
    return false;
  }
}

6.2 取消所有提醒

/**
 * 取消当前应用的所有提醒
 */
async function cancelAllReminders(): Promise<boolean> {
  console.info('取消所有提醒');
  
  try {
    await reminderAgentManager.cancelAllReminders();
    console.info('取消所有提醒成功');
    return true;
  } catch (err) {
    const error = err as BusinessError;
    console.error(`取消所有提醒失败: ${error.code} - ${error.message}`);
    return false;
  }
}

6.3 获取所有有效提醒

/**
 * 获取当前应用的所有有效提醒
 */
async function getAllReminders(): Promise<reminderAgentManager.ReminderRequest[]> {
  console.info('获取所有提醒');
  
  try {
    const reminders = await reminderAgentManager.getValidReminders();
    console.info(`当前有 ${reminders.length} 个有效提醒`);
    
    // 遍历打印提醒信息
    reminders.forEach((reminder, index) => {
      console.info(`提醒${index + 1}: 类型=${reminder.reminderType}, 标题=${reminder.title}`);
    });
    
    return reminders;
  } catch (err) {
    const error = err as BusinessError;
    console.error(`获取提醒失败: ${error.code} - ${error.message}`);
    return [];
  }
}

七、请求通知权限

在设置提醒之前,建议先检查并请求通知权限,否则提醒可能无法正常显示。

7.1 检查并请求权限

import { notificationManager } from '@kit.NotificationKit';
import { common } from '@kit.AbilityKit';

/**
 * 检查并请求通知权限
 * @param context 上下文
 */
async function requestNotificationPermission(context: common.UIAbilityContext): Promise<boolean> {
  try {
    // 1. 检查通知是否已启用
    const isEnabled = await notificationManager.isNotificationEnabled();
    console.info(`通知权限状态: ${isEnabled}`);
    
    if (isEnabled) {
      return true;
    }
    
    // 2. 请求开启通知权限
    console.info('请求通知权限...');
    await notificationManager.requestEnableNotification(context);
    console.info('用户已授权通知权限');
    return true;
    
  } catch (err) {
    const error = err as BusinessError;
    console.error(`请求通知权限失败: ${error.code} - ${error.message}`);
    // 用户拒绝了权限请求
    return false;
  }
}

7.2 在设置提醒前调用

// 在 Ability 或页面中使用
async function setupReminder(context: common.UIAbilityContext) {
  // 先请求权限
  const hasPermission = await requestNotificationPermission(context);
  
  if (!hasPermission) {
    console.warn('用户未授权通知权限,提醒可能无法正常显示');
    // 可以提示用户去设置中开启
  }
  
  // 设置提醒
  const triggerTime = Date.now() + 60 * 1000;  // 1分钟后
  await setCalendarReminder(1001, '测试提醒', '这是一条测试提醒', triggerTime);
}

八、完整示例:提醒管理类

将上述功能封装成一个完整的提醒管理类:

import { reminderAgentManager } from '@kit.BackgroundTasksKit';
import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';

/**
 * 提醒管理器
 */
export class ReminderManager {
  private context: common.UIAbilityContext;
  private bundleName: string;
  
  constructor(context: common.UIAbilityContext, bundleName: string) {
    this.context = context;
    this.bundleName = bundleName;
  }
  
  /**
   * 请求通知权限
   */
  async requestPermission(): Promise<boolean> {
    try {
      const isEnabled = await notificationManager.isNotificationEnabled();
      if (isEnabled) {
        return true;
      }
      
      await notificationManager.requestEnableNotification(this.context);
      return true;
    } catch (err) {
      console.error('请求通知权限失败:', err);
      return false;
    }
  }
  
  /**
   * 设置日历提醒
   */
  async setCalendarReminder(
    id: number,
    title: string,
    content: string,
    triggerTime: number
  ): Promise<number | null> {
    const triggerDate = new Date(triggerTime);
    
    // 检查时间
    if (triggerDate.getTime() <= Date.now()) {
      console.error('提醒时间必须在未来');
      return null;
    }
    
    const request: reminderAgentManager.ReminderRequestCalendar = {
      reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_CALENDAR,
      dateTime: {
        year: triggerDate.getFullYear(),
        month: triggerDate.getMonth() + 1,
        day: triggerDate.getDate(),
        hour: triggerDate.getHours(),
        minute: triggerDate.getMinutes(),
        second: 0
      },
      title: title,
      content: content,
      notificationId: id,
      slotType: notificationManager.SlotType.SOCIAL_COMMUNICATION,
      snoozeTimes: 2,
      timeInterval: 5 * 60,
      actionButton: [
        { title: '关闭', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE },
        { title: '稍后提醒', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_SNOOZE }
      ],
      wantAgent: {
        pkgName: this.bundleName,
        abilityName: 'EntryAbility'
      },
      ringDuration: 10
    };
    
    try {
      const publishedId = await reminderAgentManager.publishReminder(request);
      console.info(`提醒设置成功: ${publishedId}`);
      return publishedId;
    } catch (err) {
      console.error('设置提醒失败:', err);
      return null;
    }
  }
  
  /**
   * 设置闹钟提醒
   */
  async setAlarmReminder(
    id: number,
    title: string,
    content: string,
    hour: number,
    minute: number,
    daysOfWeek: number[] = []
  ): Promise<number | null> {
    const request: reminderAgentManager.ReminderRequestAlarm = {
      reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_ALARM,
      hour: hour,
      minute: minute,
      daysOfWeek: daysOfWeek,
      title: title,
      content: content,
      notificationId: id,
      slotType: notificationManager.SlotType.SOCIAL_COMMUNICATION,
      snoozeTimes: 3,
      timeInterval: 5 * 60,
      actionButton: [
        { title: '关闭', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE },
        { title: '稍后提醒', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_SNOOZE }
      ],
      wantAgent: {
        pkgName: this.bundleName,
        abilityName: 'EntryAbility'
      },
      ringDuration: 30
    };
    
    try {
      const publishedId = await reminderAgentManager.publishReminder(request);
      console.info(`闹钟设置成功: ${publishedId}`);
      return publishedId;
    } catch (err) {
      console.error('设置闹钟失败:', err);
      return null;
    }
  }
  
  /**
   * 取消提醒
   */
  async cancelReminder(reminderId: number): Promise<boolean> {
    try {
      await reminderAgentManager.cancelReminder(reminderId);
      console.info(`取消提醒成功: ${reminderId}`);
      return true;
    } catch (err) {
      console.error('取消提醒失败:', err);
      return false;
    }
  }
  
  /**
   * 取消所有提醒
   */
  async cancelAllReminders(): Promise<boolean> {
    try {
      await reminderAgentManager.cancelAllReminders();
      console.info('取消所有提醒成功');
      return true;
    } catch (err) {
      console.error('取消所有提醒失败:', err);
      return false;
    }
  }
  
  /**
   * 获取所有有效提醒
   */
  async getAllReminders(): Promise<reminderAgentManager.ReminderRequest[]> {
    try {
      return await reminderAgentManager.getValidReminders();
    } catch (err) {
      console.error('获取提醒失败:', err);
      return [];
    }
  }
}

8.1 使用提醒管理类

// 在 EntryAbility 中初始化
export default class EntryAbility extends UIAbility {
  private reminderManager: ReminderManager | null = null;
  
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 初始化提醒管理器
    this.reminderManager = new ReminderManager(
      this.context,
      this.context.abilityInfo.bundleName
    );
  }
  
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 请求通知权限
    this.reminderManager?.requestPermission();
    
    // ... 加载页面
  }
}

// 在页面中使用
@Entry
@Component
struct ReminderPage {
  private reminderManager: ReminderManager = new ReminderManager(
    getContext(this) as common.UIAbilityContext,
    'com.example.myapp'
  );
  
  build() {
    Column() {
      Button('设置10分钟后提醒')
        .onClick(async () => {
          const triggerTime = Date.now() + 10 * 60 * 1000;
          const id = await this.reminderManager.setCalendarReminder(
            1001,
            '测试提醒',
            '10分钟到了',
            triggerTime
          );
          if (id) {
            console.info(`提醒已设置,ID: ${id}`);
          }
        })
      
      Button('设置每天8点闹钟')
        .onClick(async () => {
          const id = await this.reminderManager.setAlarmReminder(
            2001,
            '起床闹钟',
            '该起床了',
            8,
            0,
            [1, 2, 3, 4, 5, 6, 7]
          );
          if (id) {
            console.info(`闹钟已设置,ID: ${id}`);
          }
        })
      
      Button('取消所有提醒')
        .onClick(async () => {
          await this.reminderManager.cancelAllReminders();
        })
    }
  }
}

九、常见问题

问题 1:提醒没有触发

可能原因:

  • 未配置 ohos.permission.PUBLISH_AGENT_REMINDER 权限
  • 通知权限未开启
  • 设置的时间已经过去

解决方案:

  • 检查 module.json5 中的权限配置
  • 调用 requestEnableNotification 请求通知权限
  • 确保触发时间在未来

问题 2:取消提醒失败

可能原因:

  • 使用了错误的提醒 ID
  • 提醒已经触发并被系统清除

解决方案:

  • 使用 publishReminder 返回的系统 ID,而不是自定义 ID
  • 保存系统返回的 ID 用于后续操作

问题 3:应用重启后提醒丢失

说明:
后台代理提醒是系统级服务,应用重启后提醒仍然有效。如果发现提醒丢失,可能是:

  • 用户手动清除了通知
  • 系统重启导致提醒被清除(部分设备)

建议:

  • 将提醒信息保存到本地数据库
  • 应用启动时检查并重新设置必要的提醒

问题 4:提醒时间不准确

可能原因:

  • 系统省电模式影响
  • 时区问题

解决方案:

  • 使用 Date 对象处理时间,避免手动计算
  • 注意月份从 0 开始(getMonth() 返回 0-11)

十、总结

本文介绍了 HarmonyOS NEXT 后台代理提醒的完整使用方法:

  1. 日历提醒:指定具体日期时间触发,适合日程、预约等场景
  2. 闹钟提醒:支持每天/每周重复,适合起床闹钟、定时任务
  3. 倒计时提醒:指定秒数后触发,适合计时器场景

关键点:

  • 必须配置 PUBLISH_AGENT_REMINDER 权限
  • 建议先请求通知权限再设置提醒
  • 使用系统返回的 ID 来取消提醒
  • 提醒是系统级服务,应用关闭后仍然有效

希望本文对你有所帮助!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐