Обзор трансляций

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

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

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

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

О системных трансляциях

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

Само широковещательное сообщение заключено в объект Intent , строка действия которого идентифицирует произошедшее событие (например, android.intent.action.AIRPLANE_MODE ). Намерение также может включать дополнительную информацию, включенную в дополнительное поле. Например, намерение режима полета включает дополнительное логическое значение, указывающее, включен ли режим полета.

Дополнительные сведения о том, как читать намерения и получать строку действия из намерения, см. в разделе Намерения и фильтры намерений .

Полный список системных широковещательных действий см. в файле BROADCAST_ACTIONS.TXT в Android SDK. С каждым широковещательным действием связано постоянное поле. Например, значение константы ACTION_AIRPLANE_MODE_CHANGEDandroid.intent.action.AIRPLANE_MODE . Документация для каждого широковещательного действия доступна в соответствующем постоянном поле.

Изменения в системных трансляциях

По мере развития платформы Android периодически меняется поведение системных трансляций. Имейте в виду следующие изменения для поддержки всех версий Android.

Андроид 14

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

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

Андроид 9

Начиная с Android 9 (уровень API 28), широковещательная рассылка NETWORK_STATE_CHANGED_ACTION не получает информацию о местоположении пользователя или личные данные.

Кроме того, если ваше приложение установлено на устройстве под управлением Android 9 или более поздней версии, системные трансляции от Wi-Fi не содержат SSID, BSSID, информацию о подключении или результаты сканирования. Чтобы получить эту информацию, вместо этого вызовите getConnectionInfo() .

Андроид 8.0

Начиная с Android 8.0 (уровень API 26), система накладывает дополнительные ограничения на получателей, объявленных в манифесте.

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

Андроид 7.0

Android 7.0 (уровень API 24) и выше не отправляет следующие системные сообщения:

Кроме того, приложения, предназначенные для Android 7.0 и более поздних версий, должны зарегистрировать широковещательную рассылку CONNECTIVITY_ACTION с помощью registerReceiver(BroadcastReceiver, IntentFilter) . Объявление получателя в манифесте не работает.

Прием трансляций

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

Получатели, объявленные в манифесте

Если вы объявите получатель широковещательной рассылки в своем манифесте, система запустит ваше приложение (если приложение еще не запущено) при отправке широковещательной рассылки.

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

  1. Укажите элемент <receiver> в манифесте вашего приложения.

    <!-- If this receiver listens for broadcasts sent from the system or from
         other apps, even other apps that you own, set android:exported to "true". -->
    <receiver android:name=".MyBroadcastReceiver" android:exported="false">
        <intent-filter>
            <action android:name="APP_SPECIFIC_BROADCAST" />
        </intent-filter>
    </receiver>
    

    Фильтры намерений определяют широковещательные действия, на которые подписывается ваш получатель.

  2. Подкласс BroadcastReceiver и реализация onReceive(Context, Intent) . Приемник широковещательной передачи в следующем примере регистрирует и отображает содержимое широковещательной передачи:

    Котлин

    private const val TAG = "MyBroadcastReceiver"
    
    class MyBroadcastReceiver : BroadcastReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            StringBuilder().apply {
                append("Action: ${intent.action}\n")
                append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
                toString().also { log ->
                    Log.d(TAG, log)
    
                    val binding = ActivityNameBinding.inflate(layoutInflater)
                    val view = binding.root
                    setContentView(view)
    
                    Snackbar.make(view, log, Snackbar.LENGTH_LONG).show()
                }
            }
        }
    }
    

    Ява

    public class MyBroadcastReceiver extends BroadcastReceiver {
            private static final String TAG = "MyBroadcastReceiver";
            @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();
                Log.d(TAG, log);
    
                ActivityNameBinding binding =
                        ActivityNameBinding.inflate(layoutInflater);
                val view = binding.root;
                setContentView(view);
    
                Snackbar.make(view, log, Snackbar.LENGTH_LONG).show();
            }
        }
    

    Чтобы включить привязку представления, настройте viewBinding в файле build.gradle на уровне модуля.

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

Система создает новый объект компонента BroadcastReceiver для обработки каждой получаемой широковещательной рассылки. Этот объект действителен только на время вызова onReceive(Context, Intent) . Как только ваш код возвращается из этого метода, система считает, что компонент больше не активен.

Получатели, зарегистрированные в контексте

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

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

  1. В файл сборки уровня модуля вашего приложения включите библиотеку AndroidX Core версии 1.9.0 или выше:

    Groovy

    dependencies {
        def core_version = "1.13.1"
    
        // Java language implementation
        implementation "androidx.core:core:$core_version"
        // Kotlin
        implementation "androidx.core:core-ktx:$core_version"
    
        // To use RoleManagerCompat
        implementation "androidx.core:core-role:1.0.0"
    
        // To use the Animator APIs
        implementation "androidx.core:core-animation:1.0.0"
        // To test the Animator APIs
        androidTestImplementation "androidx.core:core-animation-testing:1.0.0"
    
        // Optional - To enable APIs that query the performance characteristics of GMS devices.
        implementation "androidx.core:core-performance:1.0.0"
    
        // Optional - to use ShortcutManagerCompat to donate shortcuts to be used by Google
        implementation "androidx.core:core-google-shortcuts:1.1.0"
    
        // Optional - to support backwards compatibility of RemoteViews
        implementation "androidx.core:core-remoteviews:1.1.0"
    
        // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
        implementation "androidx.core:core-splashscreen:1.2.0-alpha01"
    }
    

    Kotlin

    dependencies {
        val core_version = "1.13.1"
    
        // Java language implementation
        implementation("androidx.core:core:$core_version")
        // Kotlin
        implementation("androidx.core:core-ktx:$core_version")
    
        // To use RoleManagerCompat
        implementation("androidx.core:core-role:1.0.0")
    
        // To use the Animator APIs
        implementation("androidx.core:core-animation:1.0.0")
        // To test the Animator APIs
        androidTestImplementation("androidx.core:core-animation-testing:1.0.0")
    
        // Optional - To enable APIs that query the performance characteristics of GMS devices.
        implementation("androidx.core:core-performance:1.0.0")
    
        // Optional - to use ShortcutManagerCompat to donate shortcuts to be used by Google
        implementation("androidx.core:core-google-shortcuts:1.1.0")
    
        // Optional - to support backwards compatibility of RemoteViews
        implementation("androidx.core:core-remoteviews:1.1.0")
    
        // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
        implementation("androidx.core:core-splashscreen:1.2.0-alpha01")
    }
    
  2. Создайте экземпляр BroadcastReceiver :

    Котлин

    val br: BroadcastReceiver = MyBroadcastReceiver()
    

    Ява

    BroadcastReceiver br = new MyBroadcastReceiver();
    
  3. Создайте экземпляр IntentFilter :

    Котлин

    val filter = IntentFilter(APP_SPECIFIC_BROADCAST)
    

    Ява

    IntentFilter filter = new IntentFilter(APP_SPECIFIC_BROADCAST);
    
  4. Выберите, следует ли экспортировать приемник вещания и сделать его видимым для других приложений на устройстве. Если этот приемник прослушивает трансляции, отправленные из системы или из других приложений (даже других приложений, которыми вы владеете), используйте флаг RECEIVER_EXPORTED . Если вместо этого этот приемник прослушивает только широковещательные сообщения, отправленные вашим приложением, используйте флаг RECEIVER_NOT_EXPORTED .

    Котлин

    val listenToBroadcastsFromOtherApps = false
    val receiverFlags = if (listenToBroadcastsFromOtherApps) {
        ContextCompat.RECEIVER_EXPORTED
    } else {
        ContextCompat.RECEIVER_NOT_EXPORTED
    }
    

    Ява

    boolean listenToBroadcastsFromOtherApps = false;
    if (listenToBroadcastsFromOtherApps) {
        receiverFlags = ContextCompat.RECEIVER_EXPORTED;
    } else {
        receiverFlags = ContextCompat.RECEIVER_NOT_EXPORTED;
    }
    
  5. Зарегистрируйте получателя, вызвав registerReceiver() :

    Котлин

    ContextCompat.registerReceiver(context, br, filter, receiverFlags)
    

    Ява

    ContextCompat.registerReceiver(context, br, filter, receiverFlags);
    
  6. Чтобы прекратить получение широковещательных сообщений, вызовите unregisterReceiver(android.content.BroadcastReceiver) . Обязательно отмените регистрацию получателя, если он вам больше не нужен или контекст больше не действителен.

    Помните о том, где вы регистрируете и отменяете регистрацию получателя. Например, если вы регистрируете получателя в onCreate(Bundle) с использованием контекста действия, вам следует отменить его регистрацию в onDestroy() , чтобы предотвратить утечку получателя из контекста действия. Если вы регистрируете приемник в onResume() , вам следует отменить его регистрацию в onPause() чтобы предотвратить его повторную регистрацию (если вы не хотите получать широковещательные сообщения во время паузы, и это может сократить ненужные системные накладные расходы). Не отменяйте регистрацию в onSaveInstanceState(Bundle) , поскольку это не вызывается, если пользователь возвращается в стек истории.

Влияние на состояние процесса

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

BroadcastReceiver деактивируется после onReceive() . Хост-процесс получателя важен ровно настолько, насколько важны его компоненты приложения. Если в этом процессе размещен только получатель, объявленный в манифесте (частое явление для приложений, с которыми пользователь никогда или недавно не взаимодействовал), система может завершить его после onReceive() , чтобы освободить ресурсы для других, более важных процессов.

Таким образом, получателям широковещательных сообщений не следует инициировать длительные фоновые потоки. Система может остановить процесс в любой момент после onReceive() чтобы освободить память, завершив созданный поток. Чтобы поддерживать процесс в рабочем состоянии, запланируйте JobService от получателя с помощью JobScheduler , чтобы система знала, что процесс все еще работает. Обзор фоновой работы содержит более подробную информацию.

Отправка трансляций

Android предоставляет приложениям три способа отправки трансляции:

  • Метод sendOrderedBroadcast(Intent, String) отправляет широковещательные сообщения одному получателю одновременно. По мере того, как каждый получатель выполняет свою работу по очереди, он может передать результат следующему получателю или полностью прервать трансляцию, чтобы он не был передан другим получателям. Получателями заказов можно управлять с помощью атрибута android:priority соответствующего фильтра намерений; приемники с одинаковым приоритетом будут запускаться в произвольном порядке.
  • Метод sendBroadcast(Intent) отправляет широковещательные сообщения всем получателям в неопределенном порядке. Это называется обычной трансляцией. Это более эффективно, но означает, что получатели не могут читать результаты от других получателей, распространять данные, полученные из широковещательной передачи, или прерывать широковещательную передачу.

В следующем фрагменте кода показано, как отправить широковещательную рассылку, создав Intent и вызвав sendBroadcast(Intent) .

Котлин

Intent().also { intent ->
    intent.setAction("com.example.broadcast.MY_NOTIFICATION")
    intent.putExtra("data", "Nothing to see here, move along.")
    sendBroadcast(intent)
}

Ява

Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data", "Nothing to see here, move along.");
sendBroadcast(intent);

Широковещательное сообщение заключено в объект Intent . Строка действия намерения должна содержать синтаксис имени пакета Java приложения и однозначно идентифицировать широковещательное событие. Вы можете прикрепить к намерению дополнительную информацию с помощью putExtra(String, Bundle) . Вы также можете ограничить широковещательную рассылку набором приложений в одной организации, вызвав setPackage(String) для намерения.

Ограничение трансляций с разрешениями

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

Отправка с разрешениями

Когда вы вызываете sendBroadcast(Intent, String) или sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) , вы можете указать параметр разрешения. Только получатели, которые запросили это разрешение с помощью в своем манифесте (и впоследствии получили разрешение, если это опасно) могут получить трансляцию. Например, следующий код отправляет широковещательную рассылку:

Котлин

sendBroadcast(Intent(BluetoothDevice.ACTION_FOUND),
              Manifest.permission.BLUETOOTH_CONNECT)

Ява

sendBroadcast(new Intent(BluetoothDevice.ACTION_FOUND),
              Manifest.permission.BLUETOOTH_CONNECT)

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

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>

Вы можете указать либо существующее системное разрешение, например BLUETOOTH_CONNECT либо определить собственное разрешение с помощью элемента <permission> . Информацию о разрешениях и безопасности в целом см. в разделе «Системные разрешения» .

Получение с разрешениями

Если вы укажете параметр разрешения при регистрации приемника вещания (либо с помощью registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) либо в теге <receiver> в вашем манифесте), то только вещатели, запросившие разрешение с <uses-permission> в своем манифесте (и впоследствии получили разрешение, если это опасно) могут отправить намерение получателю.

Например, предположим, что ваше принимающее приложение имеет получателя, объявленного в манифесте, как показано ниже:

<receiver android:name=".MyBroadcastReceiver"
          android:permission="android.permission.BLUETOOTH_CONNECT">
    <intent-filter>
        <action android:name="android.intent.action.ACTION_FOUND"/>
    </intent-filter>
</receiver>

Или ваше принимающее приложение имеет приемник, зарегистрированный в контексте, как показано ниже:

Котлин

var filter = IntentFilter(Intent.ACTION_FOUND)
registerReceiver(receiver, filter, Manifest.permission.BLUETOOTH_CONNECT, null )

Ява

IntentFilter filter = new IntentFilter(Intent.ACTION_FOUND);
registerReceiver(receiver, filter, Manifest.permission.BLUETOOTH_CONNECT, null );

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

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>

Вопросы безопасности и лучшие практики

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

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

  • Не передавайте конфиденциальную информацию, используя неявное намерение. Информацию может прочитать любое приложение, которое зарегистрируется для получения трансляции. Есть три способа контролировать, кто может получать ваши трансляции:

    • Вы можете указать разрешение при отправке трансляции.
    • В Android 4.0 и выше вы можете указать пакет с помощью setPackage(String) при отправке широковещательной рассылки. Система ограничивает трансляцию набором приложений, соответствующих пакету.
  • Когда вы регистрируете приемник, любое приложение может отправлять потенциально вредоносные широковещательные сообщения на приемник вашего приложения. Существует несколько способов ограничить широковещательные рассылки, которые получает ваше приложение:

    • Вы можете указать разрешение при регистрации приемника вещания.
    • Для получателей, объявленных в манифесте, вы можете установить в манифесте для атрибута android:exported значение false. Ресивер не принимает трансляции из источников за пределами приложения.
  • Пространство имен для широковещательных действий является глобальным. Убедитесь, что имена действий и другие строки записаны в принадлежащем вам пространстве имен, иначе вы можете непреднамеренно конфликтовать с другими приложениями.

  • Поскольку метод onReceive(Context, Intent) получателя выполняется в основном потоке, он должен выполняться и возвращать результат быстро. Если вам необходимо выполнить длительную работу, будьте осторожны при создании потоков или запуске фоновых служб, поскольку система может завершить весь процесс после возврата onReceive() . Дополнительную информацию см. в разделе Влияние на состояние процесса . Для выполнения длительной работы мы рекомендуем:

    • Вызов goAsync() в методе onReceive() вашего приемника и передача BroadcastReceiver.PendingResult в фоновый поток. Это сохраняет активность трансляции после возврата из onReceive() . Однако даже при таком подходе система ожидает, что вы закончите трансляцию очень быстро (менее 10 секунд). Это позволяет вам перенести работу в другой поток, чтобы избежать сбоев в основном потоке.
    • Планирование задания с помощью JobScheduler . Дополнительные сведения см. в разделе Интеллектуальное планирование заданий .
  • Не начинайте действия с приемников вещания, потому что пользовательский опыт вызывает раздражение; особенно если имеется более одного получателя. Вместо этого рассмотрите возможность отображения уведомления .