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

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

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

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

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

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

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

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

Системные широковещательные действия

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

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

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

Андроид 16

В Android 16 порядок доставки широковещательных сообщений с использованием атрибута android:priority или IntentFilter.setPriority() в разных процессах не будет гарантирован. Приоритеты широковещательных сообщений учитываются только в пределах одного процесса приложения, а не во всех процессах.

Кроме того, приоритеты вещания автоматически ограничиваются диапазоном ( SYSTEM_LOW_PRIORITY + 1, SYSTEM_HIGH_PRIORITY - 1). Только системным компонентам разрешено устанавливать SYSTEM_LOW_PRIORITY , SYSTEM_HIGH_PRIORITY в качестве приоритета вещания.

Андроид 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 , вы получаете трансляции, пока активность остается активной. Если вы регистрируетесь в контексте Application, вы получаете трансляции, пока приложение работает.

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

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

    классный

    dependencies {
        def core_version = "1.16.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-beta02"
    }

    Котлин

    dependencies {
        val core_version = "1.16.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-beta02")
    }
  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 или activity onResume / onPause : приемник вещания получает обновления только тогда, когда приложение находится в возобновленном состоянии.
  • Методы жизненного цикла LifecycleStartEffect или activity onStart / onStop : приемник вещания получает обновления только тогда, когда приложение находится в возобновленном состоянии.
  • DisposableEffect : вещательный приемник получает обновления только пока компонуемый находится в дереве композиции. Эта область не прикреплена к области жизненного цикла активности. Рассмотрите возможность регистрации приемника в контексте приложения. Это связано с тем, что компонуемый теоретически может пережить область жизненного цикла активности и привести к утечке активности.
  • Activity onCreate / onDestroy : приемник вещания получает обновления, пока activity находится в состоянии создания. Обязательно отмените регистрацию в onDestroy() , а не onSaveInstanceState(Bundle) поскольку это может не вызываться.
  • Пользовательская область: например, вы можете зарегистрировать получателя в области ViewModel , чтобы он пережил воссоздание активности. Обязательно используйте контекст приложения для регистрации получателя, так как получатель может пережить область жизненного цикла активности и утечь активность.

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

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

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

@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 , чтобы система знала, что процесс все еще работает. Background Work Overview предоставляет более подробную информацию.

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

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> в своем манифесте, смогут отправить 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
)

Ява

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