基于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




