AlarmManager срабатывает в неподходящее время

Мне все удалось, чтобы создать службу уведомлений, используемую для запуска уведомлений в результате тревоги. К сожалению, установка будильника с помощью AlarmManager работает некорректно. Он срабатывает через несколько минут (не совсем часов, что указывает на проблему с часовым поясом). Повторяющийся период составляет 1 неделю, поэтому я использовал константу INTERVAL_DAY и умножил ее на 7. Чтобы убедиться, что одно PendingIntent не заменяет другое, я передаю dayOfWeek в качестве второго параметра в PendingIntent.getService(). Я проверяю правильность времени срабатывания будильника, записывая его:

Log.d(TAG, "next alarm " + df.format(cal.getTime()));

Неужели нет способа перечислить все установленные будильники - по крайней мере, из моего собственного приложения? Я считаю, что это единственный способ отследить ошибку.

Мой код:

cal.setTimeInMillis(System.currentTimeMillis());
cal.add(Calendar.DATE, 1);
cal.set(Calendar.HOUR_OF_DAY, hour);
cal.set(Calendar.MINUTE, minute);
Log.d(TAG, "next alarm " + df.format(cal.getTime()));
Intent showNotificationIntent = new Intent(context, NotificationService.class);
dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
alarmIntent = PendingIntent.getService(context, dayOfWeek, showNotificationIntent, 0);
getAlarmManager(context).setInexactRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(),
    INTERVAL_WEEK, alarmIntent);

Я хочу предложить будильник каждый день, но в разное время, которое может быть установлено пользователем. Поэтому я использую до 7 будильников, которые должны срабатывать еженедельно.

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


person Holger Jakobs    schedule 30.01.2014    source источник
comment
Из-за setInexactRepeating. Используйте setRepeating, и он будет обработан в нужное время.   -  person Skynet    schedule 30.01.2014
comment
В текущих версиях больше нет разницы между setRepeating() и setInexactRepeating(). Документация всегда не одобряла использование setRepeating(). В моем приложении дело не в секундах, но будильник должен срабатывать в течение минуты от установленного времени.   -  person Holger Jakobs    schedule 31.01.2014
comment
Да, setRepeating не рекомендуется, потому что он вызывает будильник в определенное время, в любом случае вам нужна точность времени. Так что пункт здесь не соответствует действительности.   -  person Skynet    schedule 31.01.2014
comment
На самом деле, вы правы. setRepeating() работает нормально. Как я уже сказал, я ожидал, что неточный означает, что повторяющийся период не точен, а первый сигнал тревоги точен. И повторяющиеся будильники срабатывали бы не по секундам, а в течение минуты. То, что оно может задержаться на несколько минут на неработающем устройстве, стало для меня очень неожиданным.   -  person Holger Jakobs    schedule 31.01.2014
comment
Я согласен с вами - должна быть возможность перечислить все запланированные будильники для приложения. Невероятно раздражает, что для него нет API   -  person Someone Somewhere    schedule 25.07.2015


Ответы (2)


Для уровней API ниже 19 вы должны использовать AlarmManager.setRepeating(), и ваши тревоги будут срабатывать точно в указанное время. На уровне API 19 и выше это больше не работает. Произошло изменение в андроиде, чтобы все повторяющиеся будильники были неточными. Поэтому, если вы хотите добиться точного повторения будильника, вы должны запланировать будильник с помощью AlarmManager.setExact(), а затем, когда сработает будильник, сделать это снова на следующей неделе и так далее каждую неделю.

person Blaz    schedule 30.01.2014
comment
Я ожидаю, что неточное повторение будет достаточно хорошим. Дело не в секундах, но будильник должен сработать в течение минуты установленного времени, по крайней мере, когда устройство бодрствует и бездействует. Я ошибаюсь здесь? Поскольку уровень API 19 — это тот, который я использую в качестве цели, в любом случае нет никакой разницы. Я просто ожидаю, что будильник будет примерно таким же точным, как будильники, установленные в календаре. - person Holger Jakobs; 31.01.2014
comment
На уровне API 19+ setRepeating() будет таким же, как setInexactRepeating() на устройствах ниже 19, так что будьте осторожны с этим. При неточном повторе тревога не произойдет раньше, чем должна, но может произойти почти через полный интервал позже, чем должна. Это, вероятно, не произойдет с недельными интервалами, но с этим следует быть осторожным. Это будет зависеть от других будильников, которые вы установили в то же время на своем устройстве. - person Blaz; 31.01.2014
comment
На самом деле, вы правы. setRepeating() работает нормально. Как я уже сказал, я ожидал, что неточный означает, что повторяющийся период не точен, а первый сигнал тревоги точен. И повторяющиеся будильники срабатывали бы не по секундам, а в течение минуты. То, что оно может задержаться на несколько минут на неработающем устройстве, стало для меня очень неожиданным. - person Holger Jakobs; 31.01.2014

Из-за setInexactRepeating. Используйте setRepeating, и он будет обработан в нужное время.

Вместо:

setInexactRepeating 

использовать

setRepeating

setInexactRepeating, является дружественным к ОС и аккумулятору, он объединяет всю работу, которая должна быть выполнена при получении сигнала тревоги, и выполняет одну за другой, в то время как setRepeating мгновенно запускает сигнал тревоги.

Также примечание: сигналы тревоги стираются после перезагрузки телефона, возможно, вам придется реализовать загрузочный широковещательный приемник, чтобы сделать его постоянным. Убедитесь, что вы не используете эту среду выполнения, вам нужно реализовать ее в манифесте, иначе, когда ваше приложение не находится в фоновом режиме, вы не будете получать никаких трансляций.

Небольшой пример:

Это рабочий код. Он пробуждает ЦП каждые 10 минут, пока телефон не выключится.

Добавьте в Manifest.xml:

...
<uses-permission android:name="android.permission.WAKE_LOCK"></uses-permission>
...
<receiver  android:process=":remote" android:name="Alarm"></receiver>
...

Код:

    package YourPackage;
    import android.app.AlarmManager;
    import android.app.PendingIntent;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.os.PowerManager;
    import android.widget.Toast;

    public class Alarm extends BroadcastReceiver 
    {    
         @Override
         public void onReceive(Context context, Intent intent) 
         {   
             PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
             PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "");
             wl.acquire();

             // Put here YOUR code.
             Toast.makeText(context, "Alarm !!!!!!!!!!", Toast.LENGTH_LONG).show(); // For example

             wl.release();
         }

     public void SetAlarm(Context context)
     {
         AlarmManager am=(AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
         Intent i = new Intent(context, Alarm.class);
         PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);
         am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 1000 * 60 * 10, pi); // Millisec * Second * Minute
     }

     public void CancelAlarm(Context context)
     {
         Intent intent = new Intent(context, Alarm.class);
         PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);
         AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         alarmManager.cancel(sender);
     }
 }

Установить будильник из службы:

package YourPackage;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;

public class YourService extends Service
{
    Alarm alarm = new Alarm();
    public void onCreate()
    {
        super.onCreate();       
    }

    public void onStart(Context context,Intent intent, int startId)
    {
        alarm.SetAlarm(context);
    }

    @Override
    public IBinder onBind(Intent intent) 
    {
        return null;
    }
}

Если вы хотите установить повтор будильника во время загрузки телефона:

Добавьте разрешение в Manifest.xml:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"></uses-permission>
...
<receiver android:name=".AutoStart">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"></action>
    </intent-filter>
</receiver>
...

И создайте новый класс:

package YourPackage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class AutoStart extends BroadcastReceiver
{   
    Alarm alarm = new Alarm();
    @Override
    public void onReceive(Context context, Intent intent)
    {   
        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED"))
        {
            alarm.SetAlarm(context);
        }
    }
}
person Skynet    schedule 30.01.2014
comment
Переустановка будильника после перезагрузки не проблема, я уже позаботился об этом. Поскольку я использую уровень API 19 в качестве цели, нет никакой разницы между setRepeating() и setInexactRepeating(), но я ожидаю, что будильник сработает в течение минуты установленного времени, по крайней мере, когда устройство бодрствует и бездействует. Я ошибаюсь здесь? - person Holger Jakobs; 31.01.2014
comment
Методы setRepeating на уровне Android API 19 изменились, поэтому setRepeating не даст желаемого результата, теперь у вас есть два способа добиться того, чего вы хотите: 1) Установите максимальную цель SDK и версию Target SDK на 18, это будет работать просто отлично на 19 или 2) включают методы точного повторения сигнала тревоги для уровня API 19. Дополнительная информация: developer.android.com/reference/android/app/, длинный, android.app.PendingIntent) - person Skynet; 31.01.2014
comment
На самом деле, вы правы. setRepeating() работает нормально. Как я уже сказал, я ожидал, что неточный означает, что повторяющийся период не точен, а первый сигнал тревоги точен. И повторяющиеся будильники срабатывали бы не по секундам, а в течение минуты. То, что оно может задержаться на несколько минут на неработающем устройстве, стало для меня очень неожиданным. - person Holger Jakobs; 31.01.2014
comment
В официальной документации не очень ясно, что и как все работает, несколько дней назад я тоже работал над Alarm Manager, и благодаря обширным исследованиям и различным источникам я узнал обо всем этом. В ближайшие выходные я планирую опубликовать сообщение в блоге, которое может быть полезно для сообщества. Убедитесь, что вы сохраняете maxSDK равным 18, если вы идете вышеописанным путем. - person Skynet; 31.01.2014
comment
Почему вы запускаете приемник в удаленном процессе? - person MobileMon; 25.07.2014
comment
Это связано с моим вариантом использования для запуска новой виртуальной машины с использованием удаленного процесса, потому что моя служба работает, даже если мое приложение не работает. Также, что я получаю новую кучу в этом процессе. Что очень полезно, когда мое приложение интенсивно использует память. - person Skynet; 25.07.2014