HarmonyOS 鸿蒙Next中本地通知与行程提醒功能实现

HarmonyOS 鸿蒙Next中本地通知与行程提醒功能实现 在开发旅行计划应用时,需要实现行程提醒功能:

  1. 用户设置行程后,在指定时间前(如提前30分钟)发送通知提醒
  2. 通知需要显示行程标题和开始时间
  3. 支持创建、更新、取消提醒
  4. 应用在后台时也能正常发送通知
3 回复

1. 配置通知权限

module.json5 中无需额外配置权限,但需要在运行时请求通知权限。

2. 创建提醒服务(单例模式)

// services/ReminderService.ets
import { notificationManager } from '@kit.NotificationKit'
import { BusinessError } from '@kit.BasicServicesKit'

/**
 * 提醒数据接口
 */
interface ReminderData {
  itemId: string           // 行程项ID(唯一标识)
  title: string            // 行程标题
  scheduledDate: string    // 行程日期 (YYYY-MM-DD)
  startTime: string        // 开始时间 (HH:mm)
  minutesBefore: number    // 提前多少分钟提醒
  message?: string         // 自定义提醒消息
}

/**
 * 定时器信息接口
 */
interface TimerInfo {
  timerId: number
  reminderTime: number
}

/**
 * 提醒服务类(单例模式)
 */
class ReminderServiceClass {
  private static instance: ReminderServiceClass
  private timerMappings: Map<string, TimerInfo> = new Map()
  private isInitialized: boolean = false

  private constructor() {}

  public static getInstance(): ReminderServiceClass {
    if (!ReminderServiceClass.instance) {
      ReminderServiceClass.instance = new ReminderServiceClass()
    }
    return ReminderServiceClass.instance
  }
}

3. 初始化并请求通知权限

/**
 * 初始化服务
 */
public async initialize(): Promise<void> {
  if (this.isInitialized) {
    return
  }

  try {
    // 请求通知权限
    const hasPermission = await this.requestNotificationPermission()
    if (!hasPermission) {
      console.warn('[ReminderService] 没有通知权限')
    }

    this.isInitialized = true
    console.info('[ReminderService] ✅ 初始化成功')
  } catch (error) {
    console.error('[ReminderService] 初始化失败:', error)
  }
}

/**
 * 请求通知权限
 */
private async requestNotificationPermission(): Promise<boolean> {
  try {
    // 检查通知是否已开启
    const isEnabled = await notificationManager.isNotificationEnabled()
    if (isEnabled) {
      console.info('[ReminderService] 通知权限已开启')
      return true
    }

    // 请求用户开启通知
    await notificationManager.requestEnableNotification()
    console.info('[ReminderService] 已请求开启通知')
    return true
  } catch (error) {
    const err = error as BusinessError
    if (err.code === 1600004) {
      // 用户拒绝
      console.warn('[ReminderService] 用户拒绝开启通知')
    } else {
      console.error('[ReminderService] 请求通知权限失败:', err.code, err.message)
    }
    return false
  }
}

4. 创建行程提醒

/**
 * 创建行程提醒
 * @param data 提醒数据
 * @returns 1=成功, -1=失败
 */
public async createReminder(data: ReminderData): Promise<number> {
  if (!this.isInitialized) {
    await this.initialize()
  }

  try {
    // 计算提醒时间
    const reminderTime = this.calculateReminderTime(
      data.scheduledDate,
      data.startTime,
      data.minutesBefore
    )

    if (!reminderTime) {
      console.warn('[ReminderService] 提醒时间计算失败')
      return -1
    }

    const now = Date.now()
    const delay = reminderTime.getTime() - now

    // 检查提醒时间是否在未来
    if (delay <= 0) {
      console.warn('[ReminderService] 提醒时间已过,不创建提醒')
      return -1
    }

    // 取消已有的同ID提醒
    this.cancelReminder(data.itemId)

    // 构建提醒内容
    const content = data.message || `${data.minutesBefore}分钟后开始:${data.title}`

    // 设置定时器
    const timerId: number = setTimeout(() => {
      this.sendNotification(data.itemId, '行程提醒', content)
      this.timerMappings.delete(data.itemId)
    }, delay) as number

    // 保存定时器映射
    this.timerMappings.set(data.itemId, {
      timerId: timerId,
      reminderTime: reminderTime.getTime()
    })

    console.info(`[ReminderService] ✅ 创建提醒成功`)
    console.info(`  行程: ${data.title}`)
    console.info(`  提醒时间: ${reminderTime.toLocaleString()}`)
    console.info(`  延迟: ${Math.round(delay / 1000 / 60)}分钟后`)

    return 1
  } catch (error) {
    console.error('[ReminderService] 创建提醒失败:', error)
    return -1
  }
}

/**
 * 计算提醒时间
 */
private calculateReminderTime(
  scheduledDate: string,
  startTime: string,
  minutesBefore: number
): Date | null {
  try {
    // 解析日期和时间
    const dateParts = scheduledDate.split('-')
    const timeParts = startTime.split(':')

    if (dateParts.length < 3 || timeParts.length < 2) {
      return null
    }

    const year = parseInt(dateParts[0])
    const month = parseInt(dateParts[1]) - 1  // 月份从0开始
    const day = parseInt(dateParts[2])
    const hour = parseInt(timeParts[0])
    const minute = parseInt(timeParts[1])

    // 创建行程开始时间
    const startDateTime = new Date(year, month, day, hour, minute, 0)

    // 减去提前提醒的分钟数
    const reminderTime = new Date(startDateTime.getTime() - minutesBefore * 60 * 1000)

    return reminderTime
  } catch (error) {
    console.error('[ReminderService] 计算提醒时间失败:', error)
    return null
  }
}

5. 发送本地通知

/**
 * 发送通知
 */
private async sendNotification(itemId: string, title: string, content: string): Promise<void> {
  try {
    // 生成唯一的通知ID
    const notifyId = this.generateNotificationId(itemId)

    // 创建通知请求
    const notificationRequest: notificationManager.NotificationRequest = {
      id: notifyId,
      content: {
        // 通知类型:基础文本
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: title,
          text: content
        }
      },
      // 通知类型:社交通信(会显示在通知栏顶部)
      notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION
    }

    // 发布通知
    await notificationManager.publish(notificationRequest)
    console.info(`[ReminderService] ✅ 通知已发送: ${title} - ${content}`)
  } catch (error) {
    const err = error as BusinessError
    console.error(`[ReminderService] 发送通知失败: ${err.code} - ${err.message}`)
  }
}

/**
 * 生成通知ID(基于字符串哈希)
 */
private generateNotificationId(itemId: string): number {
  let hash = 0
  for (let i = 0; i < itemId.length; i++) {
    const char = itemId.charCodeAt(i)
    hash = ((hash << 5) - hash) + char
    hash = hash & hash  // Convert to 32bit integer
  }
  return Math.abs(hash) % 2147483647  // 确保是正整数
}

6. 取消和更新提醒

/**
 * 取消行程提醒
 * @param itemId 行程项ID
 */
public async cancelReminder(itemId: string): Promise<boolean> {
  try {
    const timerInfo = this.timerMappings.get(itemId)
    if (timerInfo) {
      clearTimeout(timerInfo.timerId)
      this.timerMappings.delete(itemId)
      console.info(`[ReminderService] ✅ 取消提醒成功: ${itemId}`)
    }
    return true
  } catch (error) {
    console.error('[ReminderService] 取消提醒失败:', error)
    return false
  }
}

/**
 * 更新行程提醒
 */
public async updateReminder(data: ReminderData): Promise<number> {
  // 先取消旧提醒
  await this.cancelReminder(data.itemId)
  // 创建新提醒
  return await this.createReminder(data)
}

/**
 * 取消所有提醒
 */
public async cancelAllReminders(): Promise<void> {
  this.timerMappings.forEach((timerInfo, itemId) => {
    clearTimeout(timerInfo.timerId)
  })
  this.timerMappings.clear()
  console.info('[ReminderService] ✅ 已取消所有提醒')
}

/**
 * 发送测试通知
 */
public async sendTestNotification(): Promise<void> {
  await this.sendNotification(
    'test',
    '测试提醒',
    '这是一条测试通知,说明提醒功能正常工作!'
  )
}

7. 导出单例

// 导出单例实例
export const reminderService = ReminderServiceClass.getInstance()

8. 在页面中使用

// pages/ScheduleDetailPage.ets
import { reminderService } from '../services/ReminderService'

@Entry
@Component
struct ScheduleDetailPage {
  @State scheduleTitle: string = '参观故宫'
  @State scheduleDate: string = '2024-12-25'
  @State scheduleTime: string = '09:00'
  @State reminderMinutes: number = 30
  @State reminderSet: boolean = false
  
  aboutToAppear(): void {
    // 初始化提醒服务
    reminderService.initialize()
  }
  
  build() {
    Column({ space: 16 }) {
      Text('行程详情')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      
      // 行程信息
      Column({ space: 8 }) {
        Text(`行程:${this.scheduleTitle}`)
        Text(`日期:${this.scheduleDate}`)
        Text(`时间:${this.scheduleTime}`)
      }
      .alignItems(HorizontalAlign.Start)
      .width('100%')
      
      // 提醒设置
      Row() {
        Text('提前提醒')
        Select([
          { value: '15分钟' },
          { value: '30分钟' },
          { value: '1小时' },
          { value: '2小时' }
        ])
        .selected(1)
        .value('30分钟')
        .onSelect((index: number) => {
          const minutes = [15, 30, 60, 120]
          this.reminderMinutes = minutes[index]
        })
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
      
      // 设置提醒按钮
      Button(this.reminderSet ? '取消提醒' : '设置提醒')
        .width('100%')
        .height(48)
        .backgroundColor(this.reminderSet ? '#FF4444' : '#A9846A')
        .onClick(async () => {
          if (this.reminderSet) {
            // 取消提醒
            await reminderService.cancelReminder('schedule_001')
            this.reminderSet = false
          } else {
            // 创建提醒
            const result = await reminderService.createReminder({
              itemId: 'schedule_001',
              title: this.scheduleTitle,
              scheduledDate: this.scheduleDate,
              startTime: this.scheduleTime,
              minutesBefore: this.reminderMinutes
            })
            this.reminderSet = result === 1
          }
        })
      
      // 测试按钮
      Button('发送测试通知')
        .width('100%')
        .height(48)
        .backgroundColor('#666666')
        .onClick(() => {
          reminderService.sendTestNotification()
        })
    }
    .width('100%')
    .padding(20)
  }
}

效果

控制台日志:

[ReminderService] 通知权限已开启
[ReminderService] ✅ 初始化成功
[ReminderService] ✅ 创建提醒成功
  行程: 参观故宫
  提醒时间: 2024/12/25 08:30:00
  延迟: 1440分钟后
[ReminderService] ✅ 通知已发送: 行程提醒 - 30分钟后开始:参观故宫

通知效果:

场景 表现
应用前台 状态栏显示通知,可下拉查看
应用后台 同上,通知正常发送
点击通知 可配置跳转到应用指定页面

通知类型说明

SlotType 说明 适用场景
SOCIAL_COMMUNICATION 社交通信 即时消息、提醒
SERVICE_INFORMATION 服务提醒 后台服务状态
CONTENT_INFORMATION 内容资讯 新闻推送
OTHER_TYPES 其他 通用通知

常见问题

Q1: 通知不显示?

排查步骤:

  1. 检查 isNotificationEnabled() 返回值
  2. 确认用户已授权通知权限
  3. 检查通知ID是否冲突

Q2: 应用被杀后提醒失效?

原因: 定时器随应用进程销毁

解决方案: 使用系统级提醒代理(reminderAgentManager),本文方案适用于应用活跃期间的提醒


关键点总结

步骤 说明
1. 请求权限 requestEnableNotification()
2. 计算延迟 提醒时间 = 行程时间 - 提前分钟数
3. 设置定时器 setTimeout() 延迟执行
4. 发送通知 notificationManager.publish()
5. 管理状态 Map 保存定时器ID,支持取消

更多关于HarmonyOS 鸿蒙Next中本地通知与行程提醒功能实现的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


鸿蒙Next中本地通知使用NotificationManager发布,通过NotificationRequest设置通知内容、触发时间等属性。行程提醒功能基于ReminderRequest实现,可设置提醒时间、重复规则及提醒方式(如弹窗、震动)。两者均需在module.json5中声明相应权限,如ohos.permission.PUBLISH_AGENT_REMINDER。

在HarmonyOS Next中实现行程提醒功能,主要依赖后台任务管理和通知服务。以下是核心实现方案:

1. 后台任务与延时触发

使用@ohos.resourceschedule.backgroundTaskManager创建延时后台任务,确保应用退至后台或设备锁屏后仍能触发提醒。通过startBackgroundRunning()申请长时任务权限,结合delaySuspendManager.requestSuspendDelay()管理任务延迟执行。

2. 通知创建与发送

通过@ohos.notificationManager模块构建通知:

  • 使用NotificationRequest设置通知内容,在content中填入行程标题和开始时间。
  • 通过notification.publish()发送通知,支持在系统通知栏显示。

3. 提醒的增删改查

  • 创建:计算触发时间(如行程开始前30分钟),调用后台任务接口注册延时任务,任务触发时发送通知。
  • 更新:取消原有后台任务,重新创建新任务。
  • 取消:直接取消对应的后台任务。

4. 关键配置

  • module.json5中声明ohos.permission.KEEP_BACKGROUND_RUNNING权限。
  • 合理使用后台任务类型(如数据传输、音频播放等)以符合系统资源调度策略。

此方案能有效满足行程提醒的定时、后台触发和通知展示需求。

回到顶部