Как проверить, если AlarmManager уже имеет установленный будильник?

Когда мое приложение запускается, я хочу, чтобы оно проверило, установлен ли и работает ли определенный сигнал тревоги (зарегистрированный с помощью AlarmManager). Результаты от Google, кажется, указывают, что нет никакого способа сделать это. Это все еще правильно? Мне нужно сделать эту проверку, чтобы уведомить пользователя, прежде чем предпринимать какие-либо действия для создания новой тревоги.

13 ответов

В продолжение комментария Рона, вот подробное решение. Допустим, вы зарегистрировали повторяющуюся тревогу с таким намерением, как это:

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 (вместо создания нового)

Для тех, кому это может понадобиться, вот ответ.

использование 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 - действие, данные, категории, компоненты, флаги), оно получит PendingIntent, представляющий тот же токен, если он все еще действителен, и Таким образом, можно вызвать метод cancel(), чтобы удалить его.

Короче говоря, ваш PendingIntent должен иметь те же функции (структура операции и намерения), чтобы управлять им.

Обратите внимание на эту цитату из документации для менеджера по тревоге:

Если для этого намерения уже запланирован сигнал тревоги (с равенством двух намерений, определяемых Intent.filterEquals), то он будет удален и заменен этим.

Не беспокойтесь о том, существует ли сигнал тревоги, если вы пытаетесь решить, создавать ли сигнал тревоги или нет. Просто создайте его каждый раз, когда загружается ваше приложение. Вы замените все прошлые тревоги, которые вы настроили.

Вам нужен другой подход, если вы пытаетесь подсчитать, сколько времени осталось на ранее созданной тревоге, или если вам действительно нужно знать, существует ли такая тревога. Чтобы ответить на эти вопросы, рассмотрите возможность сохранения общих данных pref во время создания будильника. Вы можете сохранить метку времени на момент, когда будильник был установлен, время, которое вы ожидаете, что будильник сработает, и период повторения (если вы устанавливаете повторяющийся будильник).

У меня 2 тревоги. Я использую намерение с дополнениями вместо действия, чтобы идентифицировать события:

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

Дело в том, что с дополнительными возможностями намерение (и будильник) не будут уникальными. Чтобы определить, какая сигнализация активна или нет, мне пришлось определить 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);

В то время как почти все здесь дали правильный ответ, никто не объяснил, на каком основании работают аварийные сигналы.

Вы можете узнать больше о AlarmManager и это работает здесь. Но вот быстрый ответ

Ты видишь AlarmManager в основном планирует PendingIntent в какое-то время в будущем. Таким образом, чтобы отменить запланированный будильник, необходимо отменить PendingIntent,

Всегда помните о двух вещах при создании PendingIntent

PendingIntent.getBroadcast(context,REQUEST_CODE,intent, PendingIntent.FLAG_UPDATE_CURRENT);
  • Код запроса - действует как уникальный идентификатор
  • Флаг - определяет поведение PendingIntent

Теперь, чтобы проверить, запланирован ли будильник, или для отмены будильника, вам просто нужно получить доступ к нему. 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 это вернется null если PendingIntent не существует Если он уже существует, он возвращает ссылку на существующий PendingIntent

Просто нашел другое решение, похоже, у меня работает

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);
    }

Если вы ориентируетесь на Android 12 (т.е.) Target SDK 31, то для AlarmManager, PendingIntentне может быть создан без флага Mutable или Immutable. Без этого флага изменчивости приложение выдаст ошибку времени выполнения. См. эту документацию для получения более подробной информации об этом. Следующий фрагмент кода работает для меня и поможет другим людям, которые ориентируют свое приложение на 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"/>

Я сделал простой (глупый или нет) сценарий bash, который извлекает long из оболочки adb, конвертирует их в метки времени и показывает их красным.

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;)

попытайся;)

Исходя из моего опыта, я считаю, что в более поздней версии Android разрешены только широковещательные сообщения для пробуждения по тревоге, а не запуск служб напрямую. Смотрите эту ссылку: https://developer.android.com/training/scheduling/alarms, там написано:

Сигналы тревоги имеют следующие характеристики:

  • Они позволяют запускать намерения в установленное время и/или через определенные промежутки времени.
  • Вы можете использовать их вместе с широковещательными приемниками для запуска служб и выполнения других операций.

Рабочее слово во втором предложении — «союз». В нем прямо указано, что аварийные сигналы предназначены для широковещательной передачи (что означает, что они не предназначены для непосредственного запуска служб). В течение нескольких часов я пытался использовать 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)
    );
}

Кстати, это запись AndroidManifest.xml для широковещательного приемника:

      <receiver android:name=".ServerQueryService$AlarmReceiverWakeup"
    android:enabled="true">
    <intent-filter>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</receiver>
    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");

            }

Я нахожусь под впечатлением, что нет никакого способа сделать это, хотя было бы неплохо.

Вы можете достичь аналогичного результата, записав где-то Alarm_last_set_time и имея On_boot_starter BroadcastReciever:BOOT_COMPLETED, что-то вроде.

Я нашел обходной путь с помощью рабочего менеджера Android Jetpack.

Вместо того, чтобы сразу устанавливать будильник в alarmManager, установите работу в workManager, где вы можете установить время срабатывания (установить время срабатывания в workManager) и добавить тег к этой работе. Сигнализация может быть установлена ​​и активирована, когда работа срабатывает.

И чтобы ответить на ваш вопрос, вы просто добавляете тег к работе,.addTag("something")а потом при проверке на активную работу использовать.getWorkInfosByTag("something")

Другие вопросы по тегам