Приложения Android отправляют и получают широковещательные сообщения от системы Android и других приложений Android, аналогично шаблону проектирования «публикация-подписка» . Система и приложения обычно отправляют широковещательные сообщения при возникновении определенных событий. Например, система Android отправляет широковещательные сообщения при различных системных событиях, таких как загрузка системы или зарядка устройства. Приложения также отправляют пользовательские широковещательные сообщения, например, чтобы уведомить другие приложения о чем-то, что может их заинтересовать (например, о загрузке новых данных).
Приложения могут регистрироваться для получения определенных широковещательных сообщений. Когда отправляется широковещательное сообщение, система автоматически перенаправляет его приложениям, которые подписались на получение данного типа сообщений.
В целом, широковещательные сообщения можно использовать как систему обмена сообщениями между приложениями и вне обычного пользовательского интерфейса. Однако следует проявлять осторожность, чтобы не злоупотреблять возможностью отвечать на широковещательные сообщения и запускать фоновые процессы, которые могут замедлить работу системы.
О системных трансляциях
Система автоматически отправляет широковещательные сообщения при возникновении различных системных событий, например, при переключении системы в режим полета и обратно. Все подписанные приложения получают эти сообщения.
Объект Intent оборачивает широковещательное сообщение. Строка action идентифицирует произошедшее событие, например, android.intent.action.AIRPLANE_MODE . Интент также может содержать дополнительную информацию, заключенную в поле extra. Например, интент «Режим полета» включает логическое значение extra, указывающее, включен ли режим полета.
Для получения дополнительной информации о том, как считывать интенты и получать строку действия из интента, см. разделы «Интенты» и «Фильтры интентов» .
Действия системы широковещательной рассылки
Полный список действий системной широковещательной рассылки см. в файле BROADCAST_ACTIONS.TXT в Android SDK. Каждое действие широковещательной рассылки имеет связанное с ним поле константы. Например, значение константы ACTION_AIRPLANE_MODE_CHANGED равно android.intent.action.AIRPLANE_MODE . Документация для каждого действия широковещательной рассылки доступна в соответствующем поле константы.
Изменения в системных трансляциях
По мере развития платформы Android периодически меняются способы обработки системных широковещательных сообщений. Для обеспечения поддержки всех версий Android учитывайте следующие изменения.
Android 16
В Android 16 порядок доставки широковещательных сообщений с использованием атрибута android:priority или IntentFilter.setPriority() в разных процессах не гарантируется. Приоритеты широковещательных сообщений соблюдаются только в рамках одного процесса приложения, а не во всех процессах.
Кроме того, приоритеты широковещательной рассылки автоматически ограничиваются диапазоном ( SYSTEM_LOW_PRIORITY + 1, SYSTEM_HIGH_PRIORITY - 1). Только системные компоненты могут устанавливать SYSTEM_LOW_PRIORITY и SYSTEM_HIGH_PRIORITY в качестве приоритета широковещательной рассылки.
Android 14
Пока приложения находятся в кэшированном состоянии , система оптимизирует доставку широковещательных сообщений для поддержания работоспособности системы. Например, система откладывает менее важные системные широковещательные сообщения, такие как ACTION_SCREEN_ON , пока приложение находится в кэшированном состоянии. Как только приложение переходит из кэшированного состояния в активный жизненный цикл процесса , система доставляет все отложенные широковещательные сообщения.
Важные широковещательные сообщения, объявленные в манифесте, временно удаляют приложения из кэшированного состояния для доставки.
Android 9
Начиная с Android 9 (уровень API 28), широковещательное сообщение NETWORK_STATE_CHANGED_ACTION не получает информацию о местоположении пользователя или персональные данные.
Если ваше приложение установлено на устройстве под управлением Android 9.0 (уровень API 28) или выше, система не включает SSID, BSSID, информацию о подключении или результаты сканирования в широковещательные сообщения Wi-Fi. Чтобы получить эту информацию, вызовите вместо этого getConnectionInfo() .
Android 8.0
Начиная с Android 8.0 (уровень API 26), система накладывает дополнительные ограничения на получателей, указанных в манифесте.
Если ваше приложение ориентировано на Android 8.0 или выше, вы не можете использовать манифест для объявления приемника для большинства неявных широковещательных сообщений (сообщений, которые не нацелены конкретно на ваше приложение). Вы по-прежнему можете использовать приемник, зарегистрированный в контексте, когда пользователь активно использует ваше приложение.
Android 7.0
В Android 7.0 (уровень API 24) и выше следующие системные широковещательные сообщения не отправляются:
Кроме того, приложениям, ориентированным на Android 7.0 и выше, необходимо зарегистрировать широковещательное сообщение CONNECTIVITY_ACTION с помощью registerReceiver(BroadcastReceiver, IntentFilter) . Объявление приемника в манифесте не работает.
Принимать трансляции
Приложения могут получать широковещательные сообщения двумя способами: через контекстно-зарегистрированные приемники и через приемники, объявленные в манифесте.
Приемники, зарегистрированные в контексте
Приемники, зарегистрированные в контексте, получают широковещательные сообщения до тех пор, пока их контекст регистрации действителен. Обычно это происходит между вызовами функций registerReceiver и unregisterReceiver . Контекст регистрации также становится недействительным, когда система уничтожает соответствующий контекст. Например, если вы регистрируетесь в контексте Activity , вы получаете широковещательные сообщения до тех пор, пока Activity остается активной. Если вы регистрируетесь в контексте Application, вы получаете широковещательные сообщения до тех пор, пока приложение работает.
Для регистрации приемника в контексте выполните следующие действия:
В файле сборки модуля вашего приложения добавьте библиотеку AndroidX Core версии 1.9.0 или выше:
Классный
dependencies { def core_version = "1.18.0" // 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.1.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" }
Котлин
dependencies { val core_version = "1.18.0" // 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.1.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") }
Создайте экземпляр класса
BroadcastReceiver:Котлин
val myBroadcastReceiver = MyBroadcastReceiver()Java
MyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();Создайте экземпляр
IntentFilter:Котлин
val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA")Java
IntentFilter filter = new IntentFilter("com.example.snippets.ACTION_UPDATE_DATA");Выберите, следует ли экспортировать и отображать широковещательный приемник для других приложений на устройстве. Если этот приемник прослушивает широковещательные сообщения, отправляемые системой или другими приложениями — даже теми, которые принадлежат вам, — используйте флаг
RECEIVER_EXPORTED. Если же этот приемник прослушивает только широковещательные сообщения, отправляемые вашим приложением, используйте флагRECEIVER_NOT_EXPORTED.Котлин
val listenToBroadcastsFromOtherApps = false val receiverFlags = if (listenToBroadcastsFromOtherApps) { ContextCompat.RECEIVER_EXPORTED } else { ContextCompat.RECEIVER_NOT_EXPORTED }Java
boolean listenToBroadcastsFromOtherApps = false; int receiverFlags = listenToBroadcastsFromOtherApps ? ContextCompat.RECEIVER_EXPORTED : ContextCompat.RECEIVER_NOT_EXPORTED;Зарегистрируйте приемник, вызвав функцию
registerReceiver():Котлин
ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags)Java
ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags);Чтобы прекратить получение широковещательных сообщений, вызовите
unregisterReceiver(android.content.BroadcastReceiver). Обязательно отмените регистрацию приемника, когда он вам больше не нужен или контекст перестанет быть актуальным.
Отмените регистрацию вашего приемника вещания.
Пока широковещательный приемник зарегистрирован, он хранит ссылку на контекст, в котором вы его зарегистрировали. Это потенциально может привести к утечкам памяти, если область регистрации приемника выходит за рамки жизненного цикла контекста. Например, это может произойти, когда вы регистрируете приемник в области действия, но забываете отменить его регистрацию при уничтожении действия системой. Поэтому всегда отменяйте регистрацию широковещательного приемника.
Котлин
class MyActivity : ComponentActivity() {
private val myBroadcastReceiver = MyBroadcastReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags)
setContent { MyApp() }
}
override fun onDestroy() {
super.onDestroy()
// When you forget to unregister your receiver here, you're causing a leak!
this.unregisterReceiver(myBroadcastReceiver)
}
}
Java
class MyActivity extends ComponentActivity {
MyBroadcastReceiver myBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags);
// Set content
}
}
Зарегистрируйте приемники в самом малом диапазоне.
Ваш вещательный приемник следует регистрировать только тогда, когда вас действительно интересует результат. Выберите максимально маленький размер осциллографа приемника:
- Методы жизненного цикла
LifecycleResumeEffectилиonResume/onPause: Получатель широковещательных сообщений получает обновления только тогда, когда приложение находится в возобновленном состоянии. - Методы жизненного цикла
LifecycleStartEffectили activityonStart/onStop: Приемник широковещательных сообщений получает обновления только тогда, когда приложение находится в возобновленном состоянии. -
DisposableEffect: Приемник широковещательных сообщений получает обновления только тогда, когда компонуемый объект находится в дереве композиции. Эта область видимости не привязана к области видимости жизненного цикла активности. Рекомендуется зарегистрировать приемник в контексте приложения. Это связано с тем, что компонуемый объект теоретически может пережить область видимости жизненного цикла активности и привести к утечке активности. - Activity
onCreate/onDestroy: Получатель широковещательных сообщений получает обновления, пока активность находится в состоянии создания. Убедитесь, что вы отменяете регистрацию вonDestroy(), а неonSaveInstanceState(Bundle)поскольку этот метод может не быть вызван. - Пользовательская область видимости: Например, вы можете зарегистрировать приемник в области видимости вашей
ViewModel, чтобы он сохранялся при повторном создании активности. Убедитесь, что вы используете контекст приложения для регистрации приемника, поскольку приемник может пережить область жизненного цикла активности и привести к утечке активности.
Создавайте компонуемые объекты с сохранением и без сохранения состояния.
В Compose есть компонуемые объекты с сохранением и без сохранения состояния. Регистрация или отмена регистрации широковещательного приемника внутри компонуемого объекта делает его состоятельным. Компонуемый объект не является детерминированной функцией, которая отображает одно и то же содержимое при передаче одних и тех же параметров. Внутреннее состояние может изменяться в зависимости от вызовов зарегистрированного широковещательного приемника.
В качестве лучшей практики в Compose мы рекомендуем разделять ваши компонуемые объекты на версии с сохранением состояния и без сохранения состояния. Поэтому мы рекомендуем вынести создание широковещательного приемника за пределы компонуемого объекта, чтобы сделать его без сохранения состояния:
@Composable
fun MyStatefulScreen() {
val myBroadcastReceiver = remember { MyBroadcastReceiver() }
val context = LocalContext.current
LifecycleStartEffect(true) {
// ...
ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, flags)
onStopOrDispose { context.unregisterReceiver(myBroadcastReceiver) }
}
MyStatelessScreen()
}
@Composable
fun MyStatelessScreen() {
// Implement your screen
}
Заявленные получатели
Если вы укажете широковещательный приемник в своем манифесте, система запустит ваше приложение при отправке широковещательного сообщения. Если приложение еще не запущено, система запустит его.
Чтобы указать широковещательный приемник в манифесте, выполните следующие действия:
Укажите элемент
<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="com.example.snippets.ACTION_UPDATE_DATA" /> </intent-filter> </receiver>Фильтры намерений определяют действия широковещательной рассылки, на которые подписан ваш приемник.
Создайте подкласс
BroadcastReceiverи реализуйтеonReceive(Context, Intent). В следующем примере приемник широковещательных сообщений регистрирует и отображает содержимое сообщения:Котлин
class MyBroadcastReceiver : BroadcastReceiver() { @Inject lateinit var dataRepository: DataRepository override fun onReceive(context: Context, intent: Intent) { if (intent.action == "com.example.snippets.ACTION_UPDATE_DATA") { val data = intent.getStringExtra("com.example.snippets.DATA") ?: "No data" // Do something with the data, for example send it to a data repository: dataRepository.updateData(data) } } }Java
public static class MyBroadcastReceiver extends BroadcastReceiver { @Inject DataRepository dataRepository; @Override public void onReceive(Context context, Intent intent) { if (Objects.equals(intent.getAction(), "com.example.snippets.ACTION_UPDATE_DATA")) { String data = intent.getStringExtra("com.example.snippets.DATA"); // Do something with the data, for example send it to a data repository: if (data != null) { dataRepository.updateData(data); } } } }
Менеджер пакетов системы регистрирует приемник при установке приложения. После этого приемник становится отдельной точкой входа в ваше приложение, что означает, что система может запустить приложение и доставить широковещательное сообщение, даже если приложение не запущено.
Система создает новый объект компонента BroadcastReceiver для обработки каждого полученного широковещательного сообщения. Этот объект действителен только в течение времени вызова метода onReceive(Context, Intent) . Как только ваш код завершит работу этого метода, система считает компонент неактивным.
Влияние на состояние процесса
Работает ли ваш BroadcastReceiver или нет, влияет на содержащийся в нём процесс, что может изменить вероятность его завершения системой. Процесс переднего плана выполняет метод onReceive() приёмника. Система продолжает работу процесса, за исключением случаев сильной нехватки памяти.
Система деактивирует BroadcastReceiver после onReceive() . Значимость процесса-хоста приемника зависит от компонентов приложения, входящего в его состав. Если этот процесс содержит только объявленный в манифесте приемник, система может завершить его работу после onReceive() , чтобы освободить ресурсы для других, более важных процессов. Это часто встречается в приложениях, с которыми пользователь никогда не взаимодействовал или взаимодействовал не так давно.
Таким образом, широковещательные приемники не должны запускать длительно работающие фоновые потоки. Система может остановить процесс в любой момент после onReceive() для освобождения памяти, завершив созданный поток. Чтобы процесс оставался активным, запланируйте вызов JobService от приемника с помощью JobScheduler , чтобы система знала, что процесс все еще работает. Более подробная информация содержится в разделе «Обзор фоновой работы» .
Отправка сообщений
Android предоставляет приложениям два способа отправки широковещательных сообщений:
- Метод
sendOrderedBroadcast(Intent, String)отправляет широковещательные сообщения одному получателю за раз. По мере выполнения каждого получателя, результат может передаваться следующему получателю. Также можно полностью прервать широковещательную рассылку, чтобы она не достигла других получателей. Вы можете управлять порядком выполнения получателей в рамках одного процесса приложения. Для этого используйте атрибутandroid:priorityсоответствующего intent-filter. Получатели с одинаковым приоритетом выполняются в произвольном порядке. - Метод
sendBroadcast(Intent)отправляет широковещательные сообщения всем получателям в неопределенном порядке. Это называется обычной широковещательной рассылкой. Это более эффективно, но означает, что получатели не могут считывать результаты от других получателей, передавать данные, полученные в результате широковещательной рассылки, или прерывать рассылку.
Приведённый ниже фрагмент кода демонстрирует, как отправить широковещательное сообщение, создав объект Intent и вызвав sendBroadcast(Intent) .
Котлин
val intent = Intent("com.example.snippets.ACTION_UPDATE_DATA").apply {
putExtra("com.example.snippets.DATA", newData)
setPackage("com.example.snippets")
}
context.sendBroadcast(intent)
Java
Intent intent = new Intent("com.example.snippets.ACTION_UPDATE_DATA");
intent.putExtra("com.example.snippets.DATA", newData);
intent.setPackage("com.example.snippets");
context.sendBroadcast(intent);
Широковещательное сообщение заключено в объект Intent . Строка action Intent должна содержать синтаксис имени пакета Java приложения и однозначно идентифицировать событие широковещательной рассылки. Вы можете добавить дополнительную информацию к Intent с помощью putExtra(String, Bundle) . Вы также можете ограничить широковещательную рассылку набором приложений в одной организации, вызвав setPackage(String) для Intent.
Ограничивать трансляции с помощью разрешений
Права доступа позволяют ограничивать рассылку сообщений только теми приложениями, которые обладают определенными правами. Вы можете устанавливать ограничения как для отправителя, так и для получателя сообщения.
Отправляйте сообщения с разрешениями.
При вызове sendBroadcast(Intent, String) или sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) можно указать параметр разрешения. Только получатели, запросившие это разрешение с помощью тега <uses-permission> в своем манифесте, могут получать широковещательные сообщения. Если разрешение опасно, необходимо предоставить его до того, как получатель сможет получить широковещательное сообщение. Например, следующий код отправляет широковещательное сообщение с разрешением:
Котлин
context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION)
Java
context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION);
Для приема трансляции принимающее приложение должно запросить разрешение следующим образом:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Вы можете указать либо существующее системное разрешение, например BLUETOOTH_CONNECT , либо определить пользовательское разрешение с помощью элемента <permission> . Для получения информации о разрешениях и безопасности в целом см. раздел «Системные разрешения» .
Получайте трансляции с соответствующими разрешениями.
Если вы указываете параметр разрешения при регистрации широковещательного приемника (либо с помощью registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) , либо в теге <receiver> в вашем манифесте), то только широковещательные приемники, запросившие разрешение с помощью тега <uses-permission> в своем манифесте, могут отправлять Intent приемнику. Если разрешение опасно, то широковещатель также должен получить это разрешение.
Например, предположим, что ваше принимающее приложение имеет в манифесте объявленный приемник следующего вида:
<!-- 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=".MyBroadcastReceiverWithPermission"
android:permission="android.permission.ACCESS_COARSE_LOCATION"
android:exported="true">
<intent-filter>
<action android:name="com.example.snippets.ACTION_UPDATE_DATA" />
</intent-filter>
</receiver>
Или же ваше принимающее приложение имеет контекстно-зарегистрированный приемник следующим образом:
Котлин
ContextCompat.registerReceiver(
context, myBroadcastReceiver, filter,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
null, // scheduler that defines thread, null means run on main thread
receiverFlags
)
Java
ContextCompat.registerReceiver(
context, myBroadcastReceiver, filter,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
null, // scheduler that defines thread, null means run on main thread
receiverFlags
);
Затем, чтобы иметь возможность отправлять широковещательные сообщения этим получателям, отправляющее приложение должно запросить разрешение следующим образом:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Вопросы безопасности
Вот несколько моментов, которые следует учитывать при отправке и получении широковещательных сообщений:
Если множество приложений зарегистрировались для получения одного и того же широковещательного сообщения в своем манифесте, это может привести к запуску системой большого количества приложений, что существенно повлияет как на производительность устройства, так и на пользовательский опыт. Чтобы избежать этого, предпочтительнее использовать регистрацию контекста вместо объявления в манифесте. Иногда система Android сама принуждает к использованию зарегистрированных в контексте получателей. Например, широковещательное сообщение
CONNECTIVITY_ACTIONдоставляется только зарегистрированным в контексте получателям.Не передавайте конфиденциальную информацию, используя неявное намерение. Любое приложение может прочитать эту информацию, если оно зарегистрируется для получения сообщений. Существует три способа контролировать, кто может получать ваши сообщения:
- При отправке широковещательной рассылки можно указать разрешение.
- В Android 4.0 (уровень API 14) и выше можно указать пакет с помощью
setPackage(String)при отправке широковещательного сообщения. Система ограничивает рассылку набором приложений, соответствующих указанному пакету.
При регистрации приемника любое приложение может отправлять потенциально вредоносные широковещательные сообщения вашему приемнику. Существует несколько способов ограничить количество широковещательных сообщений, получаемых вашим приложением:
- При регистрации широковещательного приемника можно указать права доступа.
- Для приемников, объявленных в манифесте, можно установить атрибут android:exported в значение "false". Приемник не будет получать широковещательные сообщения из источников вне приложения.
Пространство имен для широковещательных действий является глобальным. Убедитесь, что имена действий и другие строки записаны в принадлежащем вам пространстве имен. В противном случае вы можете непреднамеренно конфликтовать с другими приложениями.
Поскольку метод
onReceive(Context, Intent)получателя выполняется в основном потоке, он должен выполняться и возвращать результат быстро. Если вам необходимо выполнять длительные операции, будьте осторожны с созданием потоков или запуском фоновых служб, поскольку система может завершить весь процесс после возврата из методаonReceive(). Для получения дополнительной информации см. раздел «Влияние на состояние процесса». Для выполнения длительных операций мы рекомендуем:- Вызов метода
goAsync()в методеonReceive()вашего приемника и передача объектаBroadcastReceiver.PendingResultв фоновый поток. Это позволяет поддерживать активность трансляции после возврата изonReceive(). Однако даже при таком подходе система ожидает, что вы завершите трансляцию очень быстро (менее чем за 10 секунд). Это позволяет перенести работу в другой поток, чтобы избежать сбоев в основном потоке. - Планирование задания с помощью
JobScheduler. Для получения дополнительной информации см. раздел «Интеллектуальное планирование заданий» .
- Вызов метода
Не следует запускать действия с широковещательных приемников, поскольку это создает неудобства для пользователя, особенно если приемников несколько. Вместо этого рассмотрите возможность отображения уведомления .