You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

基于SQLite的多任务本地通知设置及相关功能实现咨询

解决Android任务本地通知与数据同步的三个问题

嘿,我来一步步帮你搞定这三个常见的Android开发场景,都是和本地通知、SQLite数据联动相关的,咱们逐个拆解:

1. 任务到期前30分钟发送本地通知,多任务不覆盖

核心思路是给每个任务分配唯一的标识(直接用task的id就行),确保闹钟和通知都不会互相覆盖。具体步骤如下:

  • 计算触发时间:把任务的dueDate(timestamp)转换成毫秒,减去30分钟(30*60*1000),得到闹钟触发的时间戳。
  • 设置唯一的闹钟:使用AlarmManager时,每个任务的PendingIntent要传入唯一的requestCode(直接用task的id),这样系统会把每个任务的闹钟当成独立事件,不会覆盖。注意Android 12+需要申请SCHEDULE_EXACT_ALARM权限,并且用setExactAndAllowWhileIdle来保证精确触发。
  • 发送唯一通知:调用NotificationManager.notify()时,把task的id作为通知的id参数,这样同一时间多个任务的通知会并排显示,不会被覆盖。

代码示例:

// 设置闹钟的方法
fun setAlarmForTask(task: Task) {
    val triggerTime = task.dueDate.time - 30 * 60 * 1000 // 提前30分钟
    val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
    
    val intent = Intent(context, NotificationReceiver::class.java).apply {
        putExtra("TASK_ID", task.id)
    }
    // 用task.id作为requestCode,确保PendingIntent唯一
    val pendingIntent = PendingIntent.getBroadcast(
        context,
        task.id,
        intent,
        PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
    )
    
    // Android 12+精确闹钟处理
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        if (alarmManager.canScheduleExactAlarms()) {
            alarmManager.setExactAndAllowWhileIdle(
                AlarmManager.RTC_WAKEUP,
                triggerTime,
                pendingIntent
            )
        } else {
            // 引导用户开启精确闹钟权限
            val permissionIntent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
            context.startActivity(permissionIntent)
        }
    } else {
        alarmManager.setExactAndAllowWhileIdle(
            AlarmManager.RTC_WAKEUP,
            triggerTime,
            pendingIntent
        )
    }
}

// 广播接收器中发送通知
class NotificationReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        val taskId = intent?.getIntExtra("TASK_ID", -1) ?: return
        val task = getTaskFromDb(taskId) // 从SQLite查询任务
        
        val notificationManager = context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val channelId = "task_reminder_channel"
        // Android 8.0+必须创建通知渠道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "任务提醒",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(channel)
        }
        
        val notification = NotificationCompat.Builder(context, channelId)
            .setContentTitle("任务提醒")
            .setContentText("任务「${task.name}」即将到期")
            .setSmallIcon(R.drawable.ic_notification)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .build()
        
        // 用task.id作为通知id,确保不覆盖
        notificationManager.notify(taskId, notification)
    }
}

2. 编辑/删除任务时同步更新本地通知

要做到数据和通知的联动,关键是在任务数据变更后,同步处理对应的闹钟和通知

(1)删除任务时

  • 取消该任务对应的闹钟:用task.id作为requestCode,调用AlarmManager.cancel(pendingIntent)
  • 移除已发送的通知:调用notificationManager.cancel(taskId),避免通知还留在状态栏。

(2)编辑任务时

  • 先取消旧的闹钟:和删除逻辑一样,用旧的task.id取消之前设置的闹钟。
  • 根据新的dueDate重新设置闹钟:调用上面的setAlarmForTask()方法传入更新后的任务对象。
  • 如果通知已经发送(比如距离旧dueDate不到30分钟,通知已经显示),还要更新通知内容:重新构建通知,用同一个task.id调用notify(),系统会自动更新状态栏的通知。

代码示例(删除任务的处理):

fun deleteTask(taskId: Int) {
    // 1. 从SQLite删除任务
    db.taskDao().deleteTaskById(taskId)
    
    // 2. 取消对应的闹钟
    val intent = Intent(context, NotificationReceiver::class.java)
    val pendingIntent = PendingIntent.getBroadcast(
        context,
        taskId,
        intent,
        PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_NO_CREATE
    )
    pendingIntent?.let {
        val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        alarmManager.cancel(it)
    }
    
    // 3. 取消已显示的通知
    val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    notificationManager.cancel(taskId)
}

右键菜单的编辑/删除功能,只需要在菜单项的点击事件中调用上述对应的处理方法即可,比如:

// 上下文菜单(右键菜单)的创建
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
    super.onCreateContextMenu(menu, v, menuInfo)
    menuInflater.inflate(R.menu.task_context_menu, menu)
    val info = menuInfo as AdapterView.AdapterContextMenuInfo
    val taskId = info.id.toInt() // 获取当前点击的task id
    
    // 绑定编辑点击事件
    menu.findItem(R.id.menu_edit).setOnMenuItemClickListener {
        // 跳转到编辑页面,更新完成后调用同步逻辑
        true
    }
    // 绑定删除点击事件
    menu.findItem(R.id.menu_delete).setOnMenuItemClickListener {
        deleteTask(taskId)
        true
    }
}

3. 点击通知跳转至对应任务子Activity

要实现点击通知跳转,需要在创建通知时给PendingIntent携带任务id,然后在子Activity中获取id并加载数据:

  • 创建带参数的PendingIntent:在通知的Intent中添加TASK_ID的extra,然后用这个Intent创建PendingIntent,设置启动子Activity的意图。
  • 子Activity获取参数并加载数据:在子Activity的onCreate()方法中,从Intent中取出TASK_ID,然后从SQLite查询对应的任务详情,填充页面。

代码示例:

// 在NotificationReceiver中修改通知的PendingIntent
val detailIntent = Intent(context, TaskDetailActivity::class.java).apply {
    putExtra("TASK_ID", task.id)
    flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
val detailPendingIntent = PendingIntent.getActivity(
    context,
    task.id,
    detailIntent,
    PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)

// 更新通知构建器,添加点击跳转
val notification = NotificationCompat.Builder(context, channelId)
    // ...其他基础设置
    .setContentIntent(detailPendingIntent)
    .setAutoCancel(true) // 点击后自动移除通知
    .build()

然后在TaskDetailActivity中:

class TaskDetailActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_task_detail)
        
        val taskId = intent.getIntExtra("TASK_ID", -1)
        if (taskId != -1) {
            val task = getTaskFromDb(taskId) // 从SQLite查询任务
            // 填充页面控件
            tvTaskName.text = task.name
            tvDueDate.text = formatDate(task.dueDate)
            // ...其他任务信息展示
        }
    }
}

别忘了在AndroidManifest.xml中注册子Activity:

<activity
    android:name=".TaskDetailActivity"
    android:parentActivityName=".CategoryMainActivity">
    <meta-data
        android:name="android.support.PARENT_ACTIVITY"
        android:value=".CategoryMainActivity" />
</activity>

内容的提问来源于stack exchange,提问作者Shiva

火山引擎 最新活动