Как зарегистрировать периодический запрос на работу в системе WorkManger один раз (т.е. после загрузки или установки) - PullRequest
0 голосов
/ 29 октября 2018

Мне нужен компонент в моем приложении для Android, который лучше всего описать как сторожевой таймер, то есть функцию, которая выполняется каждые 30 минут +/- 5 минут и утверждает, что определенное условие все еще выполняется. Сторожевой таймер также должен быть запущен после перезагрузки устройства без явного открытия пользователем приложения. То же самое относится и к установке приложения. Сторожевой таймер должен быть запланирован на периодическое выполнение, даже если приложение не было открыто открыто после установки.

Я понимаю, что использование WorkManager - лучший или "современный" способ. Без WorkManager мне придется писать индивидуальный код для разных уровней API, т. Е. Использовать BroadcastReceiver для устройств с уровнем API <27 и <code>JobScheduler для более высоких уровней API. WorkManager следует абстрагироваться от этих различий.

Но я не понимаю, куда звонить WorkManager.getInstance().enqueue( myWatchdogRequest );. Использование любого из основных обратных вызовов activitiy (то есть onCreate и аналогичных) не является правильным местом, потому что я не должен полагаться на когда-либо созданное действие.

Я ожидал, что помимо программной постановки в очередь заданий также должен быть способ объявления этих заданий в манифесте и, следовательно, объявления их в системе (аналогично старомодному BroadcastReceiver). На самом деле, у меня была бы такая же проблема с JobScheduler, если бы я решил использовать этот подход.

Где я могу поставить в очередь WorkRequest «глобально»?

Ответы [ 2 ]

0 голосов
/ 27 ноября 2018

В первой части я просто представляю решение в виде фрагментов кода без особых пояснений. Во второй части я подробно рассмотрю решение, объясню, почему оно не является точным, но наилучшим из возможных, и укажу на некоторые ошибки в документации Google, которые привели меня к вопросу в первую очередь.

Решение

Фактический рабочий, который работает каждые 30 минут с гибкостью 10 минут:

public class WatchDogWorker extends Worker {
  private static final String uniqueWorkName = "my.package.name.watch_dog_worker";
  private static final long repeatIntervalMin = 30;
  private static final long flexIntervalMin = 10;

  public WatchDogWorker( @NonNull Context context, @NonNull WorkerParameters params) {
    super( context, params );
  }

  private static PeriodicWorkRequest getOwnWorkRequest() {
    return new PeriodicWorkRequest.Builder(
      WatchDogWorker.class, repeatIntervalMin, TimeUnit.MINUTES, flexIntervalMin, TimeUnit.MINUTES
    ).build();
  }

  public static void enqueueSelf() {
    WorkManager.getInstance().enqueueUniquePeriodicWork( uniqueWorkName, ExistingPeriodicWorkPolicy.KEEP, getOwnWorkRequest() );
  }

  public Worker.Result doWork() {
    // Put the actual code of the watchdog that needs to be run every 30mins here
    return Result.SUCCESS;
  }
}

Примечание: a) Поскольку этот работник должен быть зарегистрирован для планирования в двух разных точках исполнения (см. Ниже) таким же образом, я решил, что WatchDogWorker должен "знать", как ставить в очередь сам. Поэтому он предоставляет статические методы getOwnWorkRequest и enqueueSelf. б) Закрытые статические константы нужны только один раз, но использование констант позволяет избежать магических чисел в коде и придает числам смысловое значение.

Чтобы поставить в очередь WatchDogWorker для планирования после загрузки устройства, требуется следующий широковещательный приемник:

public class BootCompleteReceiver extends BroadcastReceiver {
  public void onReceive( Context context, Intent intent ) {
    if( intent.getAction() == null || !intent.getAction().equals( "android.intent.action.BOOT_COMPLETED" ) ) return;
    WatchDogWorker.enqueueSelf();
  }
}

По сути, вся магия однострочна и вызывает WatchDogWorker.enqueueSelf. Приемник вещания должен вызываться один раз после загрузки. Для этого приемник вещания должен быть объявлен в AndroidManifest.xml таким образом, чтобы система Android знала о приемнике и вызывала его при загрузке:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  package="...">

  ...

  <application>
    ...
    <receiver
      android:name=".BootCompleteReceiver"
      android:enabled="true"
      android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
      </intent-filter>
    </receiver>
  </application>
</manifest>

Однако этого недостаточно. Если пользователь только что установил приложение, мы не хотим ждать следующей перезагрузки, пока сторожевой таймер не будет запланирован на первый раз, но мы хотим, чтобы оно было запланировано как можно скорее. Следовательно, WatchDogWorker также ставится в очередь, если основное действие создано.

public class MainActivity extends AppCompatActivity {
  ...
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...

    // Schedule WatchDogWorker (after a fresh install we must not rely on the BootCompleteReceiver)
    WatchDogWorker.enqueueSelf();
  }
}

Примечание: Это решение может вызывать метод WatchDogWorker.enqueueSelf несколько раз. Однако enqueueSelf внутренне вызывает enqueueUniquePeriodicWork с ExistingPeriodicWorkPolicy.KEEP. Следовательно, последующие звонки на номер enqueueSelf бесполезны и не причиняют вреда.

Предостережение: Представленное решение представляет собой решение только на 95%. Если пользователь никогда не запускает приложение после установки, т. Е. Действие никогда не создается, WatchDogWorker никогда не ставится в очередь и никогда не запускается. Даже если устройство в конечном итоге будет перезагружено в какой-то момент в будущем (но приложение никогда не запускалось), намерение «завершить загрузку» никогда не будет получено, и WatchDogWorker также не ставится в очередь. Для этой ситуации нет обходного пути. (См. Следующую главу.)

Дополнительная справочная информация

Первая проблема, которая привела меня к вопросу, состояла в том, как поставить работника в очередь, если устройство было перезагружено, не полагаясь на действие, которое будет создано. Я знал о приемниках вещания и особенно о BOOT_COMPLETED. Но согласно официальной документации Android почти все радиовещательные приемники были радикально отключены, начиная с Android 8. Это измерение было частью попытки Google улучшить управление питанием. В прошлом многие менее квалифицированные разработчики злоупотребляли вещательными приемниками за то, что они совершали безумные поступки, которые следовало бы сделать лучше другим способом. (Тривиальный пример: неправильно используйте AlarmManager и соответствующий широковещательный приемник, чтобы разбудить ваше приложение каждые 500 мс, просто чтобы проверить, есть ли обновления на вашем сервере.) Контрмера Google состояла в том, чтобы просто отключить эти широковещательные приемники. Точнее цитата из документов :

Начиная с Android 8.0 [...] система налагает [...] ограничения на приемники, объявленные манифестом. [...] вы не можете использовать манифест для объявления получателя для большинства неявных трансляций (трансляций, которые не нацелены конкретно на ваше приложение). Вы все еще можете использовать получателя, зарегистрированного в контексте, когда пользователь активно использует ваше приложение.

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

Это был момент, когда я думал, что потерян. Однако есть некоторые исключения из вышеприведенного правила, и BOOT_COMPLETED относится к этим исключениям. Удивительно, но правильная страница документации называется «Неявные исключения при трансляции», и, что еще более удивительно, найти ее не так просто. Во всяком случае, это говорит

ACTION_LOCKED_BOOT_COMPLETED, ACTION_BOOT_COMPLETED

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

Это именно то, что нужно здесь и было замечено Google. Подводя итог: Да, большинство приемников неявного вещания были заброшены, но не все и BOOT_COMPLETED является одним из них. Это все еще работает и (надеюсь) будет работать в будущем.

Вторая проблема все еще остается открытой: если пользователь никогда не перезагружает устройство и никогда не запускает приложение после установки хотя бы один раз, WatchDogServer никогда не ставится в очередь. (Это недостающие 5% моего решения вопроса.) ACTION_PACKAGE_ADDED -надумано, но здесь это не помогает, потому что конкретное добавленное приложение никогда не получает своего "собственного" намерения.

В любом случае, вышеупомянутый недостаток не может быть преодолен, и он является частью кампании Google по борьбе с вредоносным ПО. (К сожалению, я потерял ссылку на ссылку.) Это прагматичное решение, позволяющее вредоносным программам молча устанавливать фоновые задачи. После установки пакета он остается в каком-то «полуустановленном» состоянии. (Google называет его "приостановленным", но не путайте его с состоянием приостановленной активности. Здесь это относится к состоянию всего пакета.) Пакет остается в этом состоянии до тех пор, пока пользователь не запустится вручную. основное действие с android.intent.action.MAIN -интент из пусковой установки хотя бы один раз. Пока пакет находится в состоянии «приостановлено», он также не получает никаких широковещательных намерений. В этом конкретном случае BOOT_COMPLETED -intent не получено при следующей загрузке. Подводя итог: вы не можете написать приложение, которое состоит только из фоновых задач, даже если это и есть цель вашего приложения. Ваше приложение требует действия, которое должно быть показано пользователю по крайней мере один раз. В противном случае ничто не будет работать вообще. По совпадению, по юридическим причинам большинству приложений в большинстве стран в любом случае требуется какая-то юридическая заметка или политика в отношении данных, поэтому вы можете использовать это действие для статической демонстрации этого. В описании приложения в Playstore попросите пользователя запустить приложение (и, возможно, даже прочитать ваш текст), чтобы завершить установку.

0 голосов
/ 30 октября 2018

вы можете использовать различные ограничения на работу менеджера для достижения ваших целей. См.


Некоторые из ограничений: " Требуется заряд", "Тип сети" и т. Д.


Вы можете использовать их

...