在 android 中,安排工作/作业在特定时间运行是很棘手的,因为有很多选项,如 WorkManager、JobScheduler、 Alarm Manager + BroadcastReceivers 以及因为打盹模式。
在此博客中,我将向您展示我在已发布的应用程序 QuoteLab 中使用的实现,以使用 alarm Manager + BroadcastReceivers 在特定时间发送每日提醒通知。
为什么使用 AlarmManager + BroadcastReceivers?
由于 WorkManager 不保证执行时间,据我所知,AlarmManager 是在特定时间触发某些事件的最佳选择,并且在处理打盹模式时也是更安全的选择。
我们将如何去做?
我们的任务比较简单,我们要做的就是
- 创建ReminderManager 负责启动和停止提醒。
- ReminderManager 将使用 AlarmManager 安排警报
- AlarmManager 将在预定时间发生时触发 AlarmReceiver。
- AlarmReceiver 将发送通知并可选择重新安排提醒。
1. ReminderManager
让我们创建一个具有以下两个功能的对象……
object RemindersManager {
const val REMINDER_NOTIFICATION_REQUEST_CODE = 123
fun startReminder(
context: Context,
reminderTime: String = "08:00",
reminderId: Int = REMINDER_NOTIFICATION_REQUEST_CODE
) {}
fun stopReminder(
context: Context,
reminderId: Int = REMINDER_NOTIFICATION_REQUEST_CODE
) {}
}
提示 :如果您使用 DI,您可以创建一个类而不是对象,并在构造函数中注入 applicationContext。
然后 startReminder 函数会像……
fun startReminder(
context: Context,
reminderTime: String = "08:00",
reminderId: Int = REMINDER_NOTIFICATION_REQUEST_CODE
) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val (hours, min) = reminderTime.split(":").map { it.toInt() }
val Intent =
Intent(context.applicationContext, AlarmReceiver::class.java).let { intent ->
PendingIntent.getBroadcast(
context.applicationContext,
reminderId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
val calendar: Calendar = Calendar. getInstance (Locale.ENGLISH).apply {
set(Calendar.HOUR_OF_DAY, hours)
set(Calendar.MINUTE, min)
}
// If the trigger time you specify is in the past, the alarm triggers immediately.
// if soo just add one day to required calendar
// Note: i'm also adding one min cuz if the user clicked on the notification as soon as
// he receive it it will reschedule the alarm to fire another notification immediately
if (Calendar.getInstance(Locale.ENGLISH)
.apply { add(Calendar.MINUTE, 1) }.timeInMillis - calendar.timeInMillis > 0
) {
calendar.add(Calendar.DATE, 1)
}
alarmManager.setAlarmClock(
AlarmManager.AlarmClockInfo(calendar.timeInMillis, intent),
intent
)
}
- 在这里,我们使用remindId 来识别警报,以防我们以后想停止它(例如:如果用户禁用了设置中的通知)
- 我们使用提醒时间来安排下一个选定时间的警报,但是如果您想安排它,例如五天后,您可以发送一个日历并将其传递给 AlarmManager。
- 我们将在我们的预定闹钟中增加一天,否则如果您尝试设置的时间早于当前时间,它将立即触发闹钟。
注意 :面向 SDK 级别 31 或更高级别的应用需要请求 SCHEDULE_EXACT_ALARM 权限才能使用此 API。 用户和系统可以通过设置中的特殊应用访问屏幕撤销此权限。
而 stopReminder 功能将是……
fun stopReminder(
context: Context,
reminderId: Int = REMINDER_NOTIFICATION_REQUEST_CODE
) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, AlarmReceiver::class.java).let { intent ->
PendingIntent.getBroadcast(
context,
reminderId,
intent,
0
)
}
alarmManager.cancel(intent)
}
2.报警接收器
在此类中,onReceive 将按计划时间触发,我们将发送通知并选择性地重新安排警报。
class AlarmReceiver : BroadcastReceiver() {
/**
* sends notification when receives alarm
* and then reschedule the reminder again
* */
override fun onReceive(context: Context, intent: Intent) {
val notificationManager = ContextCompat.getSystemService(
context,
NotificationManager::class.java
) as NotificationManager
notificationManager.sendReminderNotification(
applicationContext = context,
channelId = context.getString(R.string.reminders_notification_channel_id)
)
// Remove this line if you don't want to reschedule the reminder
RemindersManager.startReminder(context.applicationContext)
}
}
fun NotificationManager.sendReminderNotification(
applicationContext: Context,
channelId: String,
) {
val contentIntent = Intent(applicationContext, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
applicationContext,
1,
contentIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
val builder = NotificationCompat.Builder(applicationContext, channelId)
.setContentTitle(applicationContext.getString(R.string.title_notification_reminder))
.setContentText(applicationContext.getString(R.string.description_notification_reminder))
.setSmallIcon(R.drawable.ic_logo)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(applicationContext.getString(R.string.description_notification_reminder))
)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
notify(NOTIFICATION_ID, builder.build())
}
const val NOTIFICATION_ID = 1
然后我们需要在 manifest .xml 文件中注册它。
<receiver
android:name="your.package.AlarmReceiver"
android:enabled="true" />
最后我们将创建通知通道并启动提醒…
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
createNotificationsChannels()
RemindersManager.startReminder(this)
}
private fun createNotificationsChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
getString(R.string.reminders_notification_channel_id),
getString(R.string.reminders_notification_channel_name),
NotificationManager.IMPORTANCE_HIGH
)
ContextCompat.getSystemService(this, NotificationManager::class.java)
?.createNotificationChannel(channel)
}
}
提示 :在 Application 类中创建通知通道更有意义。
3. 引导接收器
当用户关闭他的手机时,您之前安排的所有警报都会消失,因此要重新启动它们,我们可以使用 BootReceiver 将在用户启动手机时触发。
class BootReceiver : BroadcastReceiver() {
/*
* restart reminders alarms when user's device reboots
* */
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == "android.intent.action.BOOT_COMPLETED") {
RemindersManager.startReminder(context)
}
}
}
当然,在 manifest.xml 文件中声明它。
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /><application
.../>
<receiver
android:name="your.package.BootReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
都搞定了,试试看。
而已。 我希望你喜欢这个博客