如何查看AlarmManager是否已有告警设置?

当我的应用程序启动时,我想让它检查一个特定的警报(通过AlarmManager注册)是否已经设置并运行。谷歌的结果似乎表明没有办法做到这一点。这仍然正确吗?我需要做这个检查,以便在采取任何行动创建一个新的警报之前建议用户。

114232 次浏览

我的印象是没有办法做到这一点,尽管这将是很好的。

你可以通过在某个地方记录Alarm_last_set_time来实现类似的结果,并有一个On_boot_starter BroadcastReciever:BOOT_COMPLETED之类的东西。

根据ron发表的评论,下面是详细的解决方案。让我们假设你已经注册了一个重复的警报,它的意图是这样的:

Intent intent = new Intent("com.my.package.MY_UNIQUE_ACTION");
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.add(Calendar.MINUTE, 1);


AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 1000 * 60, pendingIntent);

检查它是否处于活动状态的方法是:

boolean alarmUp = (PendingIntent.getBroadcast(context, 0,
new Intent("com.my.package.MY_UNIQUE_ACTION"),
PendingIntent.FLAG_NO_CREATE) != null);


if (alarmUp)
{
Log.d("myTag", "Alarm is already active");
}

这里的关键是FLAG_NO_CREATE,正如在javadoc: if the described PendingIntent **does not** already exists, then simply return null中所描述的那样(而不是创建一个新的)

我有两个闹钟。我使用带有额外内容的意图而不是行动来识别事件:

Intent i = new Intent(context, AppReciever.class);
i.putExtra("timer", "timer1");

问题是,不同的额外的意图(和警报)不会是唯一的。因此,为了能够识别哪个警报处于活动状态,我必须定义diff requestCode-s:

boolean alarmUp = (PendingIntent.getBroadcast(context, MyApp.TIMER_1, i,
PendingIntent.FLAG_NO_CREATE) != null);

下面是如何创建警报的:

public static final int TIMER_1 = 1;
public static final int TIMER_2 = 2;


PendingIntent pending = PendingIntent.getBroadcast(context, TIMER_1, i,
PendingIntent.FLAG_CANCEL_CURRENT);
setInexactRepeating(AlarmManager.RTC_WAKEUP,
cal.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pending);
pending = PendingIntent.getBroadcast(context, TIMER_2, i,
PendingIntent.FLAG_CANCEL_CURRENT);
setInexactRepeating(AlarmManager.RTC_WAKEUP,
cal.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pending);

对于其他可能需要这个的人,这里有一个答案。

使用adb shell dumpsys alarm

你可以知道闹铃已经设置好了,什么时候闹铃和间隔时间。还有这个警报被触发的次数。

接收器的工作示例(上面的答案只是与动作有关)。

//starting
AlarmManager alarmManager = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(getActivity(), MyReceiver.class);
intent.setAction(MyReceiver.ACTION_ALARM_RECEIVER);//my custom string action name
PendingIntent pendingIntent = PendingIntent.getBroadcast(getActivity(), 1001, intent, PendingIntent.FLAG_CANCEL_CURRENT);//used unique ID as 1001
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), aroundInterval, pendingIntent);//first start will start asap


//and stopping
Intent intent = new Intent(getActivity(), MyReceiver.class);//the same as up
intent.setAction(MyReceiver.ACTION_ALARM_RECEIVER);//the same as up
PendingIntent pendingIntent = PendingIntent.getBroadcast(getActivity(), 1001, intent, PendingIntent.FLAG_CANCEL_CURRENT);//the same as up
alarmManager.cancel(pendingIntent);//important
pendingIntent.cancel();//important


//checking if alarm is working with pendingIntent
Intent intent = new Intent(getActivity(), MyReceiver.class);//the same as up
intent.setAction(MyReceiver.ACTION_ALARM_RECEIVER);//the same as up
boolean isWorking = (PendingIntent.getBroadcast(getActivity(), 1001, intent, PendingIntent.FLAG_NO_CREATE) != null);//just changed the flag
Log.d(TAG, "alarm is " + (isWorking ? "" : "not") + " working...");

值得一提的是:

如果稍后创建的应用程序(进程)重新检索相同类型的 PendingIntent(相同的操作,相同的Intent's - action, data, categories, 组件,flags),它将接收一个PendingIntent 表示相同的令牌,如果该令牌仍然有效,则可以调用

.取消()删除它

简而言之,你的PendingIntent应该有相同的特性(操作和意图的结构)来控制它。

注意文档中关于Alarm Manager的set方法的这句话:

如果已经为该Intent计划了一个告警(使用 由Intent.filterEquals定义的两个意图的相等性),然后它

如果你知道你想要闹钟设置,那么你不需要费心检查它是否已经存在。每次你的应用启动时创建它。你将用相同的Intent替换任何过去的警报。

如果您试图计算以前创建的警报还剩多少时间,或者您确实需要知道这样的警报是否存在,则需要使用不同的方法。要回答这些问题,请考虑在创建警报时保存共享pref数据。您可以存储闹钟设置时刻的时钟时间戳,您希望闹钟响起的时间,以及重复周期(如果您设置了重复闹钟)。

我做了一个简单(愚蠢与否)的bash脚本,从adb shell中提取long,将它们转换为时间戳并以红色显示。

echo "Please set a search filter"
read search


adb shell dumpsys alarm | grep $search | (while read i; do echo $i; _DT=$(echo $i | grep -Eo 'when\s+([0-9]{10})' | tr -d '[[:alpha:][:space:]]'); if [ $_DT ]; then echo -e "\e[31m$(date -d @$_DT)\e[0m"; fi; done;)

试试吧;)

    Intent intent = new Intent("com.my.package.MY_UNIQUE_ACTION");
PendingIntent pendingIntent = PendingIntent.getBroadcast(
sqlitewraper.context, 0, intent,
PendingIntent.FLAG_NO_CREATE);

FLAG_NO_CREATE不是创建挂起意图,因此它给出布尔值false。

            boolean alarmUp = (PendingIntent.getBroadcast(sqlitewraper.context, 0,
new Intent("com.my.package.MY_UNIQUE_ACTION"),
PendingIntent.FLAG_NO_CREATE) != null);


if (alarmUp) {
System.out.print("k");


}


AlarmManager alarmManager = (AlarmManager) sqlitewraper.context
.getSystemService(Context.ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis(), 1000 * 60, pendingIntent);

在AlarmManager检查Pending Intent的值后,它给出true,因为AlarmManager更新了Pending Intent的标志。

            boolean alarmUp1 = (PendingIntent.getBroadcast(sqlitewraper.context, 0,
new Intent("com.my.package.MY_UNIQUE_ACTION"),
PendingIntent.FLAG_UPDATE_CURRENT) != null);
if (alarmUp1) {
System.out.print("k");


}

刚找到另一个解决办法,似乎对我有用

Intent myIntent = new Intent(MainActivity.this, MyReceiver.class);


boolean isWorking = (PendingIntent.getBroadcast(MainActivity.this, 0, myIntent, PendingIntent.FLAG_NO_CREATE) != null);
if (isWorking) {Log.d("alarm", "is working");} else {Log.d("alarm", "is not working");}


if(!isWorking) {
pendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, myIntent,    PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
int timeNotif = 5 * 60 * 1000;//time in ms, 7*24*60*60*1000 for 1 week
Log.d("Notif", "Notification every (ms): " + timeNotif);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), timeNotif, pendingIntent);
}

虽然这里几乎每个人都给出了正确答案,但没有人解释警报的工作依据是什么

你可以学到更多关于AlarmManager和它工作的在这里的知识。但这里有一个简单的答案

你可以看到AlarmManager基本上是在未来的某个时间安排一个PendingIntent。所以为了取消预定的闹钟,你需要取消PendingIntent

在创建PendingIntent时,始终要注意两件事

PendingIntent.getBroadcast(context,REQUEST_CODE,intent, PendingIntent.FLAG_UPDATE_CURRENT);
  • 请求代码——作为唯一标识符
  • Flag -定义PendingIntent的行为

现在要检查Alarm是否已经被安排或取消Alarm,你只需要访问相同的PendingIntent。这可以做到,如果你使用相同的请求代码,并使用FLAG_NO_CREATE如下所示

PendingIntent pendingIntent=PendingIntent.getBroadcast(this,REQUEST_CODE,intent,PendingIntent.FLAG_NO_CREATE);


if (pendingIntent!=null)
alarmManager.cancel(pendingIntent);

对于FLAG_NO_CREATE,如果PendingIntent不存在,它将返回null。如果它已经存在,则返回对现有PendingIntent的引用

根据我的经验,在最新版本的Android中,我认为它只允许广播消息唤醒警报,而不是直接启动服务。看这个链接:https://developer.android.com/training/scheduling/alarms,它说:

报警器具有以下特点:

  • 它们允许你在设定的时间和/或间隔内发射intent。
  • 可以与广播接收器配合使用,用于启动服务和执行其他操作。

第二句中的关键词是“连词”。它明确指出,警报是为Broadcast设计的(这意味着不是为直接启动服务而设计的)。我尝试了几个小时使用一个PendingIntent与getService(),但不能让它工作,即使我确认挂起的意图是正确地使用:

pendingIntent.send(0);

对于“;targetSdkVersion 29”;这个没有工作..[不会触发onStartCommand()]:

Intent launchIntent = new Intent(context, MyService.class);
launchIntent.putExtra(Type.KEY, SERVER_QUERY);
PendingIntent pendingIntent =
PendingIntent.getService(context, 0, launchIntent, 0);

我可以验证警报正在运行使用:

adb shell dumpsys alarm | grep com.myapp

然而,这个做了工作:

public static class AlarmReceiverWakeup extends BroadcastReceiver {


@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive Alarm wakeup");
startService(context);
}
}




public static void scheduleAlarmWakeup(Context context) {


Intent broadcastIntent = new Intent(context, AlarmReceiverWakeup.class);
broadcastIntent.putExtra(Type.KEY, SERVER_QUERY);
PendingIntent pendingIntent =
PendingIntent.getBroadcast(context, 0, broadcastIntent, 0);
AlarmManager alarmManager =
(AlarmManager) context.getSystemService(Context.ALARM_SERVICE);


// NOTE: using System.currentTimeMillis() fails w/ELAPSED_REALTIME_WAKEUP
//     use SystemClock.elapsedRealtime() instead
alarmManager.setRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime()+5000,
AlarmManager.INTERVAL_FIFTEEN_MINUTES/4,
getAlarmPendingIntent(context)
);
}

顺便说一下,这是Broadcast Receiver的AndroidManifest.xml条目:

<receiver android:name=".ServerQueryService$AlarmReceiverWakeup"
android:enabled="true">
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>

如果你的目标是Android 12(即目标SDK 31),那么,对于AlarmManagerPendingIntent不能在没有可变或不可变标志的情况下创建。如果没有这个可变性标志,应用程序将抛出一个运行时错误。有关这方面的更多细节,请参阅文档。下面的代码片段对我来说是有用的,它将帮助那些将应用程序定位于Android 12的人。

创建告警:

public static void setupReminderServiceAlarm ( Context context ) {
Log.d ( TAG, "Trying to setup reminder service alarm" );
if (!isReminderServiceAlarmSet ( context )) {
AlarmManager alarmManager = (AlarmManager) context.getApplicationContext ().getSystemService ( Context.ALARM_SERVICE );
Intent intent = new Intent ( context.getApplicationContext (), ReminderIntentReceiver.class );
intent.setAction ( REMINDER_INTENT_ACTION );
PendingIntent pendingIntent;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
pendingIntent = PendingIntent.getBroadcast ( context.getApplicationContext (), REMINDER_INTENT_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE );
} else {
pendingIntent = PendingIntent.getBroadcast ( context.getApplicationContext (), REMINDER_INTENT_REQUEST_CODE, intent, 0 );
}
alarmManager.setRepeating ( AlarmManager.RTC_WAKEUP, getReminderTriggerTime (), REMINDER_INTERVAL, pendingIntent );
Log.d ( TAG, "Reminder service alarm setup completed" );
}
}

查询告警是否已设置:

private static boolean isReminderServiceAlarmSet ( Context context ) {
Intent intent = new Intent ( context.getApplicationContext (), ReminderIntentReceiver.class );
intent.setAction ( REMINDER_INTENT_ACTION );
boolean isBackupServiceAlarmSet;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getBroadcast ( context.getApplicationContext (), REMINDER_INTENT_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_NO_CREATE );
isBackupServiceAlarmSet = (PendingIntent.getBroadcast ( context.getApplicationContext (), REMINDER_INTENT_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_NO_CREATE ) != null);
} else {
PendingIntent.getBroadcast ( context.getApplicationContext (), REMINDER_INTENT_REQUEST_CODE, intent, PendingIntent.FLAG_NO_CREATE );
isBackupServiceAlarmSet = (PendingIntent.getBroadcast ( context.getApplicationContext (), REMINDER_INTENT_REQUEST_CODE, intent, PendingIntent.FLAG_NO_CREATE ) != null);
}
Log.d ( TAG, "Reminder service alarm is " + (isBackupServiceAlarmSet ? "" : "not ") + "set already" );
return isBackupServiceAlarmSet;
}

取消之前设置的闹钟:

public static void cancelReminderServiceAlarm ( Context context ) {
Log.d ( TAG, "Reminder service alarm canceled" );
AlarmManager alarmManager = (AlarmManager) context.getApplicationContext ().getSystemService ( Context.ALARM_SERVICE );
Intent intent = new Intent ( context.getApplicationContext (), ReminderIntentReceiver.class );
intent.setAction ( REMINDER_INTENT_ACTION );
PendingIntent pendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
pendingIntent = PendingIntent.getBroadcast ( context.getApplicationContext (), REMINDER_INTENT_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE );
} else {
pendingIntent = PendingIntent.getBroadcast ( context.getApplicationContext (), REMINDER_INTENT_REQUEST_CODE, intent, 0 );
}
alarmManager.cancel ( pendingIntent );
pendingIntent.cancel ();
}

希望这个答案能帮助那些瞄准Android 12 / SDK 31+应用程序的人。此外,请确保在清单中添加此权限,以调度针对上述告警的告警。

<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>

我用android jetpack workManager做了一个变通。

与其直接在alarmManager中设置警报,不如在workManager中设置work,在那里您可以设置触发时间(在workManager中设置触发时间)并为该work添加一个标签。当工作触发时,可设置并触发报警。

为了回答你的问题,你只需通过.addTag("something")向作品添加标签,然后在检查活动作品时使用.getWorkInfosByTag("something")