Аварийный сигнал AlarmManager, который случайно слышен, когда телефон не используется

Я вызываю фон Service с интервалом 30 минут, чтобы прочитать широту/долготу текущего местоположения и отправить его на сервер с помощью POST API.

Я использую метод setRepeating() класса AlarmManager для планирования тревоги каждые 30 минут. Но несколько раз сигнал тревоги пропускается, и обслуживание не вызывается. Чтобы отслеживать, вызван ли сигнал или нет, каждые 30 минут я создал файл Log.txt на SD-карте. За каждый раз, когда в файле Log.txt записывается тревога, называемая записью для текущего времени. Но после сравнения 4-5 файлов Log.txt устройств я заметил, что для некоторых устройств будильник не вызывает метод onCreate() UserTrackingReceiver.java (Фоновая служба). Полный фрагмент кода, упомянутый ниже.

Когда при запуске приложения registerUserTrackingReceiver() был вызван метод, который ниже:

public static void registerUserTrackingReceiver(Context context) {
        try {
            Intent intent = new Intent(context, UserTrackingReceiver.class);

            boolean alarmUp = (PendingIntent.getService(context, 1001, intent, PendingIntent.FLAG_NO_CREATE) == null);

            if (alarmUp) {
                Calendar calendar = Calendar.getInstance();

                if (calendar.get(Calendar.MINUTE) > 0 && calendar.get(Calendar.MINUTE) <= 30) {
                    calendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY));
                    calendar.set(Calendar.MINUTE, 30);
                    calendar.set(Calendar.SECOND, 0);
                } else if (calendar.get(Calendar.MINUTE) > 30) {
                    if (calendar.get(Calendar.HOUR_OF_DAY) == 23) {
                        calendar.set(Calendar.HOUR_OF_DAY, 0);
                    } else {
                        calendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY) + 1);
                    }
                    calendar.set(Calendar.MINUTE, 0);
                    calendar.set(Calendar.SECOND, 0);
                } else {
                    calendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY));
                    calendar.set(Calendar.MINUTE, 0);
                    calendar.set(Calendar.SECOND, 0);
                }

                PendingIntent sender = PendingIntent.getService(context, 1001, intent, 0);
                AlarmManager alarmManager = (AlarmManager) context.getSystemService(context.ALARM_SERVICE);
                alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
                        AlarmManager.INTERVAL_HALF_HOUR, sender);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
}

UserTrackingReceiver.java находится ниже:

public class UserTrackingReceiver extends Service
        implements LocationListener,
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

    @Override
    public void onCreate() {
        super.onCreate();

        Calendar calendar = Calendar.getInstance();
        Util.appendLog("Tracking Alarm Called on: " + calendar.get(Calendar.HOUR_OF_DAY) + " : " + calendar.get(Calendar.MINUTE) + " : " + calendar.get(Calendar.SECOND));
        stopSelf();
    }
}

В Util.java есть метод appendLog(), который ниже:

public static void appendLog(String text) {

        String baseDir = Environment.getExternalStorageDirectory().getAbsolutePath();

        File logFile = new File(baseDir + "/" + Constant.AppNameSuper + "/log.txt");
        if (!logFile.exists()) {
            try {
                logFile.createNewFile();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        try {
            //BufferedWriter for performance, true to set append to file flag
            BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true));
            buf.append(text);
            buf.newLine();
            buf.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
}

Если будильник вызывается каждые 30 минут в соответствии с приведенным выше кодом, он должен быть записан в файле Log.txt в SDCARD. Но проблема в том, что он не может записывать файл журнала каждые 30 минут, что означает отсутствие тревоги. Что касается чтения в течение двух дней, я заметил, что будильник не пропускается в дневное время, так как пользователь постоянно использует свой телефон, но он пропускается ночью, когда телефон не используется.

Вывод лог файлов с различными устройствами ниже:

Устройство A Log.txt

  • Трекинг Тревога Вызов: 0: 0: 31 (Начало с 12:00 ночи)
  • Трекинг Тревога Вызов: 1:10: 27
  • Трекинг Тревога Вызов: 3: 5: 25
  • Трекинг Тревога Вызов: 6: 55: 31
  • Трекинг Тревога Вызов: 7: 0: 6
  • Трекинг Тревога Вызов: 7: 30: 0
  • Трекинг Тревога Вызов: 8: 0: 6
  • Трекинг Тревога Вызов: 8: 30: 0
  • Трекинг Тревога Вызов: 9: 0: 6
  • Трекинг Тревога Вызов: 9: 30: 0
  • Трекинг Тревога Вызов: 10: 0: 0

Устройство B Log.txt

  • Трекинг Тревога Вызов: 0: 0: 27 (Начало с 12:00 ночи)
  • Трекинг Тревога Вызов: 0: 30: 1
  • Тревога отслеживания Вызов: 1: 0: 1
  • Трекинг Тревога Вызов: 1: 30: 2
  • Трекинг Тревога Вызов: 2: 0: 1
  • Трекинг Тревога Вызов: 2: 30: 1
  • Трекинг Тревога Вызов: 3: 0: 1
  • Трекинг Тревога Вызов: 3: 30: 1
  • Трекинг Тревога Вызов: 4: 0: 1
  • Трекинг Тревога Вызов: 4: 30: 29
  • Трекинг Тревога Вызов: 5: 0: 1
  • Трекинг Тревога Вызов: 5: 30: 2
  • Трекинг Тревога Вызов: 6: 0: 30
  • Трекинг Тревога Вызов: 6: 30: 1
  • Трекинг Тревога Вызов: 7: 0: 1
  • Трекинг Тревога Вызов: 7: 30: 1
  • Трекинг Тревога Вызов: 8: 0: 1
  • Трекинг Тревога Вызов: 8: 30: 1
  • Трекинг Тревога Вызов: 9: 0: 32
  • Трекинг Тревога Вызов: 9: 30: 1

Device C Log.txt

  • Трекинг Тревога Вызов: 0: 0: 7 (Начало с 12:00 ночи)
  • Трекинг Тревога Вызов: 0: 30: 3
  • Тревога отслеживания Вызов: 1: 0: 6
  • Трекинг Тревога Вызов: 1: 30: 1
  • Трекинг Тревога Вызов: 2: 0: 32
  • Трекинг Тревога Вызов: 2: 30: 3
  • Трекинг Тревога Вызов: 3: 1: 50
  • Трекинг Тревога Вызов: 3: 30: 5
  • Трекинг Тревога Вызов: 4: 1: 58
  • Трекинг Тревога Вызов: 4: 31:14
  • Трекинг Тревога Вызов: 5: 0: 1
  • Трекинг Тревога Вызов: 5: 30: 1
  • Трекинг Тревога Вызов: 6: 2: 1
  • Трекинг Тревога Вызов: 6: 30: 1
  • Трекинг Тревога Вызов: 7: 0: 1
  • Трекинг Тревога Вызов: 7: 30: 1
  • Трекинг Тревога Вызов: 8: 0: 1
  • Трекинг Тревога Вызов: 8: 30: 4
  • Трекинг Тревога Вызов: 9: 1: 44
  • Трекинг Тревога Вызов: 9: 30: 1

Устройство D Log.txt

  • Трекинг Тревога Вызов: 0: 1: 25 (начало с 12:00 ночи)
  • Трекинг Тревога Вызов: 0: 30: 0
  • Трекинг Тревога Вызов: 1: 31: 41
  • Трекинг Тревога Вызов: 2: 39: 52
  • Трекинг Тревога Вызов: 3: 0: 25
  • Трекинг Тревога Вызов: 3: 30: 58
  • Трекинг Тревога Вызов: 4: 0: 25
  • Трекинг Тревога Вызов: 4: 30: 56
  • Трекинг Тревога Вызов: 5: 30: 51
  • Трекинг Тревога Вызов: 7: 18: 55
  • Трекинг Тревога Вызов: 7: 30: 0
  • Трекинг Тревога Вызов: 8: 0: 25
  • Трекинг Тревога Вызов: 8: 30: 43
  • Трекинг Тревога Вызов: 9: 0: 3
  • Трекинг Тревога Вызов: 9: 30: 25
  • Трекинг Тревога Вызов: 10: 0: 25
  • Трекинг Тревога Вызов: 10: 30: 4
  • Трекинг Тревога Вызов: 11:1: 52
  • Трекинг Тревога Вызов: 11: 30: 27
  • Трекинг Тревога Вызов: 12: 1: 6⁠⁠⁠⁠

Ответ 1

Проблема может заключаться в том, что ваш PendingIntent вызывает Service. Устройство может вернуться в режим сна, прежде чем ваш Service закончит выполнение (или даже запускает).

Я предлагаю вам вместо этого использовать BroadcastReceiver (так как WakeLock гарантируется во время onReceive()).

Приобретите WakeLock в onReceive(), отпустите Service и отпустите WakeLock из Service, если это необходимо.

Чтобы упростить этот процесс, вы можете использовать WakefulBroadcastReceiver вспомогательный класс:

  • Вызовите PendingIntent.getBroadcast() вместо PendingIntent.getService().
  • Запустите IntentService из onReceive(), вызвав WakefulBroadcastReceiver.startWakefulService().
  • Сделайте свой материал в onHandleIntent() и вызовите WakefulBroadcastReceiver.completeWakefulIntent() по завершении.

Например, a BroadcastReceiver, который запускает бодрствование Service:

public class ExampleReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Intent wakefulServiceIntent = new Intent(context,
            ExampleWakefulService.class);

        WakefulBroadcastReceiver.startWakefulService(context,
            wakefulServiceIntent);
    }
}

И Service:

public class ExampleWakefulService extends IntentService {

    private static final String NAME = "com.example.ExampleWakefulService";

    public ExampleWakefulService() {
        super(NAME);
    }

    @Override
    protected void onHandleIntent(Intent intent) {

        // doing stuff

        WakefulBroadcastReceiver.completeWakefulIntent(intent);
    }
}

Кроме того, ознакомьтесь с этой статьей из руководства разработчика по поддержанию работоспособности устройства.

В уровне API 23 + вам нужно иметь дело с Doze.

Из документация:

Чтобы помочь в планировании аварийных сигналов, Android 6.0 (API-уровень 23) вводит два новых метода AlarmManager: setAndAllowWhileIdle() и setExactAndAllowWhileIdle(). С помощью этих методов вы можете установить аварийные сигналы который будет срабатывать, даже если устройство находится в режиме Doze.

К сожалению, нет альтернативы для setRepeating(), поэтому у вас есть два варианта:

  • Установите точные аварийные сигналы (используя соответствующий метод в зависимости от уровня API устройства, посмотрите этот ответ для примера) и перепланировать их каждый раз, когда они срабатывают.
  • белый список вашего приложения (не рекомендуется из-за строгой политики пересмотра Google).

Ответ 2

Вам нужно использовать BroadcastReceiver и wakelock, чтобы надежно сделать это, когда устройство не работает. Кроме того, обратите внимание, что начиная с сигналов API 19 по умолчанию неточно, что будет играть в этом. если вы нацеливаете API 21 или новее, рассмотрите возможность использования JobScheduler. Аналогично этому сообщению Alarm Manager с двумя ожидающими намерениями работает только 1?

Ответ 3

В соответствии с Android-разработчиком document Примечание:

Примечание: по API 19 все повторяющиеся сигналы тревоги неточны. Если ваш приложение требует точных сроков доставки, тогда оно должно использовать одноразовое точные тревоги, перепланирование каждый раз, как описано выше. наследие приложения, чья targetSdkVersion ранее, чем API 19, будет продолжают иметь все свои тревоги, в том числе повторяющиеся сигналы тревоги, считаются точными.