Android Broadcast Receivers для начинающих

Перевод статьи на Медиуме  о технологии Broadcast Receivers (широковещательные приемники). Это компоненты андроид, которые отслеживают широковещательные сообщения (broadcast messages) или события (events).


Для чего нужны Broadcast Receivers?

Допустим, у вас есть приложение, которое зависит от постоянного интернет-соединения. Вы хотите, чтобы ваше приложение получало уведомление при изменении интернет-соединения. Как это сделать? Возможным решением будет сервис, который всегда проверяет интернет-соединение. Эта реализация плоха по разным причинам, поэтому мы не будем ее рассматривать. Решением этой проблемы является широковещательный приемник (Broadcast Receiver), который прослушивает изменения, о которых вы сообщаете. Получатель трансляции всегда будет получать уведомления о трансляции, независимо от состояния вашего приложения. Неважно, работает ли ваше приложение в фоновом режиме или вообще не работает.

Теория Broadcast Receivers

Широковещательные приемники — это компоненты в вашем приложении Android, которые прослушивают широковещательные сообщения (или события) из разных точек:

  • Из других приложений
  • Из самой системы
  • Из вашего приложения

Это означает, что они вызываются, когда происходит определенное действие, которое они запрограммированы на прослушивание, например, трансляция (broadcast).

Трансляция (broadcast) — это просто сообщение, заключенное в объект Intent. Трансляция может быть неявной или явной.

  • Неявная широковещательная трансляция (implicit broadcast) — это такая, которая не предназначена специально для вашего приложения, поэтому она не является эксклюзивной для вашего приложения. Чтобы зарегистрироваться, вам нужно использовать IntentFilter и объявить его в своем манифесте. Вы должны сделать все это, потому что операционная система Android просматривает все объявленные фильтры намерений (IntentFilter) в вашем манифесте и проверяет, есть ли совпадение. Из-за этого поведения неявные широковещательные сообщения не имеют целевого атрибута. Примером неявной трансляции может быть действие входящего SMS-сообщения.
  • Явная трансляция (explicit broadcast) — это то, что предназначено специально для вашего приложения на заранее известном компоненте. Это происходит из-за атрибута target, который содержит имя пакета приложения или имя класса компонента.

Смотрите также Что такое Intent, Intent Filter, Context

Есть два способа объявить приемник:

1.Объявив его в файле AndroidManifest.xml с тегом <receiver> (также называемый статическим способом):

<receiver android:name=".YourBrodcastReceiverClass"  android:exported="true">
    <intent-filter>
        <!-- The actions you wish to listen to, below is an example -->
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>

Вы заметите, что заявленный широковещательный приемник имеет свойство exported = ”true”. Этот атрибут сообщает получателю, что он может принимать широковещательные сообщения вне области приложения.

2. Или динамически путем регистрации экземпляра с помощью registerReceiver (так называемый зарегистрированный контекст):

public abstract Intent registerReceiver (BroadcastReceiver receiver, 
                IntentFilter filter);

Реализация Broadcast Receivers

Чтобы создать собственный широковещательный приемник, вы должны сначала расширить родительский класс BroadcastReceiver и переопределить обязательный метод onReceive:

 public void onReceive(Context context, Intent intent) {
    //Implement your logic here
 }

Собрав все вместе, получим:

public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        StringBuilder sb = new StringBuilder();
        sb.append("Action: " + intent.getAction() + "\n");
        sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
        String log = sb.toString();
        Toast.makeText(context, log, Toast.LENGTH_LONG).show();

    }
}

Метод onReceive выполняется в главном потоке, и поэтому его выполнение должно быть кратким.

Если выполняется долгий процесс, система может завершить процесс после возврата метода. Чтобы обойти это, рассмотрите возможность использования goAsync или планировщиков заданий (scheduling a job). Вы можете прочитать больше об этом в нижней части этой статьи.

Пример динамической регистрации

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

BroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();

Затем вы можете зарегистрировать его в зависимости от конкретного контекста, который вы хотите:

IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
this.registerReceiver(myBroadcastReceiver, filter);

Первый параметр для IntentFilter — это строка, представляющая действие.

Не забудьте отменить регистрацию вашего вещательного приемника, когда он вам больше не нужен

@Override
protected void onStop() {
  super.onStop();
  unregisterReceiver(myBroadcastReceiver);
}

Трансляция события

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

Есть три способа отправки трансляций:

  1. Метод sendOrderedBroadcast обеспечивает одновременную отправку широковещательных сообщений только одному получателю. Каждая широковещательная трансляция может, в свою очередь, передавать данные той, которая следует за ней, или останавливать распространение широковещательной трансляции для следующих получателей.
  2. Метод sendBroadcast похож на метод, упомянутый выше, с одним отличием. Все широковещательные приемники получают сообщение и не зависят друг от друга.
  3. Метод LocalBroadcastManager.sendBroadcast отправляет широковещательные сообщения только получателям, определенным в вашем приложении, и не выходит за рамки вашего приложения.

На что обратить внимание

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

Изменения в новых версиях

Следующие пункты относятся к изменениям в широковещательных приемниках, относящихся к каждой версии ОС Android (начиная с 7.0). Для каждой версии были установлены определенные ограничения, а также изменилось поведение. Помните об этих ограничениях, думая об использовании Broadcast Receivers.

  • 7.0 и выше (уровень API 24) — две системные трансляции были отключены, Action_New_Picture и Action_New_Video (но они были возвращены в Android O для зарегистрированных получателей)
  • 8.0 и выше (уровень API 26). Большинство неявных трансляций необходимо регистрировать динамически, а не статически (в вашем манифесте). Вы можете найти трансляции, которые были внесены в белый список по этой ссылке.
  • 9.0 и выше (уровень API 28) — Меньше информации, получаемой при трансляции системы Wi-Fi и Network_State_Changed_Action.

Изменения в Android O — это те, о которых вам нужно знать больше всего. Причина, по которой эти изменения были внесены, заключалась в том, что они приводили к проблемам с производительностью, разрядке аккумулятора и ухудшали работу пользователя. Это произошло из-за того, что многие приложения (даже те, которые в данный момент не запущены) прослушивали общесистемные изменения, и когда это изменение произошло, возник хаос. Представьте, что каждое приложение, зарегистрированное на действия, ожило, чтобы проверить, нужно ли что-то делать из-за трансляции. Примите во внимание что-то вроде состояния Wi-Fi, которое часто меняется, и вы начнете понимать, почему произошли эти изменения.

Альтернативы Broadcast Receivers

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

  • LocalBroadcastManager — Как я уже упоминал выше, это действительно только для трансляций в вашем приложении
  • Scheduling A Job (Планирование задания) — задание может быть запущено в зависимости от полученного сигнала или триггера, поэтому вы можете обнаружить, что прослушиваемая трансляция может быть заменена заданием. Кроме того, JobScheduler гарантирует, что ваша работа будет завершена, но он будет учитывать различные системные факторы (время и условия), чтобы определить, когда он должен работать. При создании задания вы переопределите метод с именем onStartJob. Этот метод выполняется в основном потоке, поэтому убедитесь, что он завершает свою работу за ограниченное время. Если вам нужно выполнить сложную логику, подумайте о запуске фоновой задачи. Кроме того, возвращаемое значение для этого метода является логическим, где true означает, что определенные действия все еще выполняются, а false означает, что задание выполнено.

Ссылки на исходники

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

  1. Custom Broadcast (с декларацией в манифесте)
  2. Registering Broadcast (без объявления в манифесте)
  3. LocalBroadcastManager

Трансляция окончена.

Понравилась статья? Поделиться с друзьями:
Комментарии: 2
  1. genbachae

    Виталий подскажите, есть такая ситуация: пришёл некоторый broadcast, следует загрузить некоторую информацию (гарантируется, что загрузка выполнится не более чем за 2 секунды) и показать Notification. Дополнительное требование: не должно возникать «подвисания» приложения (пропуска кадров отрисовки) в ходе работы BroadcastReceiver.
    Какие из перечисленных вариантов допустимы?

    1) Допустимо произвести загрузку данных прямо в BroadcastReceiver.onReceive(), если данные берутся из локального хранилища
    2) В BroadcastReceiver.onReceive() вызвать метод goAsync(), выданный токен передать в команду на исполнение на побочном треде
    3) Из BroadcastReceiver.onReceive() запустить Service, в нём произвести загрузку дополнительной информации

    1. admin (автор)

      В идеале 3-й, но можно и 1-й вариант

Добавить комментарий