通过 AlarmManager 和 BroadcastReceiver 发送通知不起作用?

Sending notification via AlarmManager and BroadcastReceiver does not work?

我要提前 5 天安排通知,所以我使用 AlarmManager 创建了一个警报,它会触发一个 PendingIntent,它会触发我的 BroadcastReceiver。

如果我尝试该代码 10 秒钟,它就可以工作。当我尝试了 5 天时,没有任何反应。

class NotificationScheduler 是设置和更新闹钟的助手class。

开火日期是正确的,因为我将它们存储在数据库中,并且我已经对其进行了校对。

清单:

<receiver android:name=".reminder.ReminderReceiver" />

NotificationScheduler:

class NotificationScheduler {

    companion object {

        const val NOTIFICATION_EXTRA_CLAIM_ID = "notification_extra_bookentry_id"

        const val INTENT_ACTION_REMINDER = "at.guger.moneybook.reminder"

        fun setReminder(context: Context, bookEntryId: Long, fireDate: Date? = null) {
            val mCalendar = Calendar.getInstance()

            val lFireDate = if (fireDate == null) {
                mCalendar.timeInMillis += 5 * 24 * 3600 * 1000
                mCalendar.set(Calendar.HOUR_OF_DAY, 12)

                mCalendar.time
            } else {
                fireDate
            }

            create(context, bookEntryId, lFireDate.time)

            AppDatabase.getInstance(context).reminderDao().insert(Reminder(bookEntryId, lFireDate))
        }

        fun updateReminder(context: Context, bookEntryId: Long) {
            cancel(context, bookEntryId)

            val mCalendar = Calendar.getInstance()
            mCalendar.timeInMillis += 5 * 24 * 3600 * 1000
            mCalendar.set(Calendar.HOUR_OF_DAY, 12)

            create(context, bookEntryId, mCalendar.timeInMillis)

            AppDatabase.getInstance(context).reminderDao().update(Reminder(bookEntryId, mCalendar.time))
        }

        fun cancelReminder(context: Context, bookEntryId: Long) {
            cancel(context, bookEntryId)

            AppDatabase.getInstance(context).reminderDao().delete(Reminder(bookEntryId))
        }

        private fun create(context: Context, bookEntryId: Long, fireDate: Long) {
            val mAlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager

            val mComponentName = ComponentName(context, ReminderReceiver::class.java)
            context.packageManager.setComponentEnabledSetting(mComponentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP)

            val mIntent = Intent(context, ReminderReceiver::class.java)
            mIntent.action = INTENT_ACTION_REMINDER
            mIntent.putExtra(NOTIFICATION_EXTRA_CLAIM_ID, bookEntryId)

            val mPendingIntent = PendingIntent.getBroadcast(context, bookEntryId.toInt(), mIntent, PendingIntent.FLAG_UPDATE_CURRENT)

            if (Utils.isKitKat()) {
                mAlarmManager.setWindow(AlarmManager.RTC, fireDate, AlarmManager.INTERVAL_HOUR, mPendingIntent)
            } else {
                mAlarmManager.set(AlarmManager.RTC, fireDate, mPendingIntent)
            }
        }

        private fun cancel(context: Context, bookEntryId: Long) {
            val mAlarmManager: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager

            val mComponentName = ComponentName(context, ReminderReceiver::class.java)
            context.packageManager.setComponentEnabledSetting(mComponentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP)

            val mIntent = Intent(context, ReminderReceiver::class.java)
            mIntent.putExtra(NOTIFICATION_EXTRA_CLAIM_ID, bookEntryId)

            val mPendingIntent = PendingIntent.getBroadcast(context, bookEntryId.toInt(), mIntent, PendingIntent.FLAG_UPDATE_CURRENT)

            mAlarmManager.cancel(mPendingIntent)
            mPendingIntent.cancel()
        }
    }
}

广播接收器:

class ReminderReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        if (context != null && intent != null) {
            when (intent.action) {
                NotificationScheduler.INTENT_ACTION_REMINDER -> {
                    val mPowerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
                    val mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this::class.simpleName)

                    mWakeLock.acquire(WAKELOCK_TIME)

                    val iClaimEntryId = intent.getLongExtra(NotificationScheduler.NOTIFICATION_EXTRA_CLAIM_ID, -1)

                    showNotification(context, iClaimEntryId)
                    AppDatabase.getInstance(context).reminderDao().delete(Reminder(iClaimEntryId))

                    mWakeLock.release()
                }
            }
        }
    }

    private fun showNotification(context: Context, claimEntryId: Long) {
        val mNotificationManager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        val nBuilder: Notification.Builder

        if (Utils.isOreo()) {
            val mNotificationChannel = NotificationChannel(NOTIFICATIONCHANNEL_CLAIMREMINDERID, context.getString(R.string.notificationchannel_claimreminder_title), NotificationManager.IMPORTANCE_DEFAULT)
            mNotificationChannel.description = context.getString(R.string.notificationchannel_claimreminder_description)

            mNotificationManager.createNotificationChannel(mNotificationChannel)

            nBuilder = Notification.Builder(context, NOTIFICATIONCHANNEL_CLAIMREMINDERID)
        } else {
            nBuilder = Notification.Builder(context)
        }

        val mClaimEntry: BookEntry = AppDatabase.getInstance(context).bookEntryDao().get(claimEntryId)

        val mCurrencyFormatter = DecimalFormat.getCurrencyInstance(Preferences.getInstance(context).currency.locale)

        nBuilder.setSmallIcon(R.drawable.ic_money)
        nBuilder.setContentTitle(context.getString(R.string.notification_claimreminder_title, mCurrencyFormatter.format(mClaimEntry.dValue)))
        val sContacts = mClaimEntry.getContacts(context).joinToString().takeIf { it.isNotEmpty() }
                ?: "-"
        nBuilder.setContentText(context.getString(R.string.notification_claimreminder_content, sContacts))
        nBuilder.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
        nBuilder.setAutoCancel(true)

        mNotificationManager.notify(mClaimEntry.lId!!.toInt(), nBuilder.build())
    }

    companion object {
        const val NOTIFICATIONCHANNEL_CLAIMREMINDERID = "notification_channel_claimreminder"

        const val WAKELOCK_TIME: Long = 1000
    }
}

设备是否在这五天内关闭过?根据文档:

By default, all alarms are canceled when a device shuts down. To prevent this from happening, you can design your application to automatically restart a repeating alarm if the user reboots the device. This ensures that the AlarmManager will continue doing its task without the user needing to manually restart the alarm. Set an alarm when the device restarts.

想到的另一件事是您可能想要使用 AlarmManager.RCT_WAKEUP 而不是 AlarmManager.RTC 来确保设备处于唤醒状态以传递意图并创建和发送通知。

尝试:

if (Utils.isKitKat()) {
    mAlarmManager.setWindow(AlarmManager.RTC_WAKEUP, fireDate, AlarmManager.INTERVAL_HOUR, mPendingIntent)
} else {
    mAlarmManager.set(AlarmManager.RTC_WAKEUP, fireDate, mPendingIntent)
}

最有可能的情况是,当您的设备进入打瞌睡模式并且闹钟管理器未触发您预定的闹钟时 尝试使用(这是 Java 代码):

AlarmManager alarmManager = (AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent);
    }
    else{
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent);
        }
    }

看看setExactAndAllowWhileIdle

我在使用 AlarmManager 时遇到问题,所以我现在使用 WorkManager。

WorkManager 与 Android Jetpack/架构组件一起引入。

解决方案

  • 首先添加WorkManager的依赖,可以找到最新版本here.
  • 创建一个Worker class,以及在doWork()中显示通知的代码。

  • 在您的应用程序启动时安排这项工作,同时检查是否尚未安排。为了方便起见,我已经在 class 下面创建了一个名为 isWorkScheduled() 的方法。

  • 您可以通过 setInputData().

  • 向您的任务发送额外的数据(如 putExtra())
  • 首先在 activity onCreate() 或应用程序 class onCreate 中安排您的一次性任务。

例子

public static final String TAG_WORK = "myTag";
if(!MyWork.isWorkScheduled(TAG_WORK))
    MyWork.scheduleOneTimeTask(TAG_WORK, 5, TimeUnit.DAYS)

MyWork.java

public class MyWork extends Worker {
    public static void scheduleOneTimeTask(String tag, long duration, TimeUnit timeUnit) {
        OneTimeWorkRequest compressionWork =
                new OneTimeWorkRequest.Builder(MyWork.class).setInitialDelay(duration, timeUnit).addTag(tag)
                        .build();
        WorkManager instance = WorkManager.getInstance();
        if (instance != null) {
            instance.enqueue(compressionWork);
        }
    }

    private boolean isWorkScheduled(String tag) {
        WorkManager instance = WorkManager.getInstance();
        if (instance == null) return false;
        LiveData<List<WorkStatus>> statuses = instance.getStatusesByTag(tag);
        if (statuses.getValue() == null) return false;
        boolean running = false;
        for (WorkStatus workStatus : statuses.getValue()) {
            running = workStatus.getState() == State.RUNNING | workStatus.getState() == State.ENQUEUED;
        }
        return running;
    }

    @NonNull
    @Override
    public Result doWork() {
        // show notification
        return Result.SUCCESS;
        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }
}

I suggested you WorkManger only, because I created a sample earlier days with JobScheduler, EvernoteJobs, AlarmManager, JobService, and WorkManager. In which I started periodic task of 15 minutes with each of these. and wrote logs of each in separate file when invoked.

Conclusion of this test was that. WorkManager and EvernoteJobs were most efficient to do jobs. Now because EvernoteJobs will use WorkManager from next version. So I came up with WorkManager.

更新

安排周期性任务的最短时间为 15 分钟,请记住。您可以在 documentation.

中阅读更多内容