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

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

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

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

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

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

Объект Intent оборачивает широковещательное сообщение. Строка action идентифицирует произошедшее событие, например 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.0 (уровень API 28) или выше, система не включает SSID, BSSID, информацию о подключении или результаты сканирования в широковещательные передачи Wi-Fi. Чтобы получить эту информацию, вместо этого вызовите 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) . Объявление получателя в манифесте не работает.

Получать трансляции

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

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

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

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

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

    классный

    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-alpha02"
    }

    Котлин

    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-alpha02")
    }
  2. Создайте экземпляр BroadcastReceiver :

    Котлин

    val myBroadcastReceiver = MyBroadcastReceiver()
    

    Ява

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

    Котлин

    val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA")
    

    Ява

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

    Котлин

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

    Ява

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

    Котлин

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

    Ява

    ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags);
    
  6. Чтобы прекратить получение широковещательных сообщений, вызовите 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)
    }
}

Ява

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 или методы жизненного цикла активности onStart / onStop : приемник вещания получает обновления только тогда, когда приложение находится в возобновленном состоянии.
  • DisposableEffect : широковещательный приемник получает обновления только тогда, когда компонуемый объект находится в дереве композиции. Эта область не привязана к области жизненного цикла действия. Рассмотрите возможность регистрации получателя в контексте приложения. Это связано с тем, что составной объект теоретически может пережить жизненный цикл активности и привести к утечке активности.
  • Activity onCreate / onDestroy : приемник вещания получает обновления, пока действие находится в созданном состоянии. Обязательно отмените регистрацию в onDestroy() , а не onSaveInstanceState(Bundle) потому что это может не вызваться.
  • Пользовательская область: например, вы можете зарегистрировать получателя в области ViewModel , чтобы он сохранял работоспособность при воссоздании активности. Обязательно используйте контекст приложения для регистрации получателя, поскольку получатель может выйти за рамки жизненного цикла действия и привести к утечке действия.

Создание компонуемых объектов с сохранением и без сохранения состояния

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

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

@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
}

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

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

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

  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="com.example.snippets.ACTION_UPDATE_DATA" />
        </intent-filter>
    </receiver>
    

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

  2. Подкласс 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)
            }
        }
    }
    

    Ява

    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 соответствующего фильтра намерений. Приемники с одинаковым приоритетом запускаются в произвольном порядке.
  • Метод 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)

Ява

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 намерения должна содержать синтаксис имени пакета Java приложения и однозначно идентифицировать широковещательное событие. Вы можете прикрепить к намерению дополнительную информацию с помощью putExtra(String, Bundle) . Вы также можете ограничить широковещательную рассылку набором приложений в одной организации, вызвав setPackage(String) для намерения.

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

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

Отправлять трансляции с разрешениями

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

Котлин

context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION)

Ява

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> тег в своем манифесте может отправить намерение получателю. Если разрешение опасно, вещательная компания также должна получить разрешение.

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

<!-- 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
)

Ява

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 . Дополнительные сведения см. в разделе Интеллектуальное планирование заданий .
  • Не начинайте действия с приемников вещания, потому что пользовательский опыт вызывает раздражение; особенно если имеется более одного получателя. Вместо этого рассмотрите возможность отображения уведомления .