文章目录
Broadcasts overview
Android apps can send or receive broadcast messages from the Android system and other Android apps, similar to the publish-subscribe design pattern. These broadcasts are sent when an event of interest occurs. For example, the Android system sends broadcasts when various system events occur, such as when the system boots up or the device starts charging. Apps can also send custom broadcasts, for example, to notify other apps of something that they might be interested in (for example, some new data has been downloaded).
Apps can register to receive specific broadcasts. When a broadcast is sent, the system automatically routes broadcasts to apps that have subscribed to receive that particular type of broadcast.
Generally speaking, broadcasts can be used as a messaging system across apps and outside of the normal user flow. However, you must be careful not to abuse the opportunity to respond to broadcasts and run jobs in the background that can contribute to a slow system performance, as described in the following video.
About system broadcasts
The system automatically sends broadcasts when various system events occur, such as when the system switches in and out of airplane mode. System broadcasts are sent to all apps that are subscribed to receive the event.
The broadcast message itself is wrapped in an Intent
object whose action string identifies the event that occurred (for example android.intent.action.AIRPLANE_MODE
). The intent may also include additional information bundled into its extra field. For example, the airplane mode intent includes a boolean extra that indicates whether or not Airplane Mode is on.
For more information about how to read intents and get the action string from an intent, see Intents and Intent Filters.
For a complete list of system broadcast actions, see the BROADCAST_ACTIONS.TXT
file in the Android SDK. Each broadcast action has a constant field associated with it. For example, the value of the constant ACTION_AIRPLANE_MODE_CHANGED
is android.intent.action.AIRPLANE_MODE
. Documentation for each broadcast action is available in its associated constant field.
Changes to system broadcasts
As the Android platform evolves, it periodically changes how system broadcasts behave. Keep the following changes in mind if your app targets Android 7.0 (API level 24) or higher, or if it’s installed on devices running Android 7.0 or higher.
Android 9
Beginning with Android 9 (API level 28), The NETWORK_STATE_CHANGED_ACTION
broadcast doesn’t receive information about the user’s location or personally identifiable data.
In addition, if your app is installed on a device running Android 9 or higher, system broadcasts from Wi-Fi don’t contain SSIDs, BSSIDs, connection information, or scan results. To get this information, call getConnectionInfo()
instead.
Android 8.0
Beginning with Android 8.0 (API level 26), the system imposes additional restrictions on manifest-declared receivers.
If your app targets Android 8.0 or higher, you cannot use the manifest to declare a receiver for most implicit broadcasts (broadcasts that don’t target your app specifically). You can still use a context-registered receiver when the user is actively using your app.
Android 7.0
Android 7.0 (API level 24) and higher don’t send the following system broadcasts:
ACTION_NEW_PICTURE
ACTION_NEW_VIDEO
Also, apps targeting Android 7.0 and higher must register the CONNECTIVITY_ACTION
broadcast using registerReceiver(BroadcastReceiver, IntentFilter)
. Declaring a receiver in the manifest doesn’t work.
Receiving broadcasts
Apps can receive broadcasts in two ways: through manifest-declared receivers and context-registered receivers.
Manifest-declared receivers
If you declare a broadcast receiver in your manifest, the system launches your app (if the app is not already running) when the broadcast is sent.
Note: If your app targets API level 26 or higher, you cannot use the manifest to declare a receiver for implicit broadcasts (broadcasts that do not target your app specifically), except for a few implicit broadcasts that are exempted from that restriction. In most cases, you can use scheduled jobs instead.
To declare a broadcast receiver in the manifest, perform the following steps:
-
Specify the `` element in your app’s manifest.
<receiver android:name=".MyBroadcastReceiver" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.INPUT_METHOD_CHANGED" /> </intent-filter> </receiver>
The intent filters specify the broadcast actions your receiver subscribes to.
-
Subclass
BroadcastReceiver
and implementonReceive(Context, Intent)
. The broadcast receiver in the following example logs and displays the contents of the broadcast: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); Toast.makeText(context, log, Toast.LENGTH_LONG).show(); } }
The system package manager registers the receiver when the app is installed. The receiver then becomes a separate entry point into your app which means that the system can start the app and deliver the broadcast if the app is not currently running.
The system creates a new BroadcastReceiver
component object to handle each broadcast that it receives. This object is valid only for the duration of the call to onReceive(Context, Intent)
. Once your code returns from this method, the system considers the component no longer active.
Context-registered receivers
To register a receiver with a context, perform the following steps:
-
Create an instance of
BroadcastReceiver
.BroadcastReceiver br = new MyBroadcastReceiver();
-
Create an
IntentFilter
and register the receiver by callingregisterReceiver(BroadcastReceiver, IntentFilter)
:IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); this.registerReceiver(br, filter);
Note: To register for local broadcasts, call
LocalBroadcastManager.registerReceiver(BroadcastReceiver, IntentFilter)
instead.Context-registered receivers receive broadcasts as long as their registering context is valid. For an example, if you register within an
Activity
context, you receive broadcasts as long as the activity is not destroyed. If you register with the Application context, you receive broadcasts as long as the app is running. -
To stop receiving broadcasts, call
unregisterReceiver(android.content.BroadcastReceiver)
. Be sure to unregister the receiver when you no longer need it or the context is no longer valid.Be mindful of where you register and unregister the receiver, for example, if you register a receiver in
onCreate(Bundle)
using the activity’s context, you should unregister it inonDestroy()
to prevent leaking the receiver out of the activity context. If you register a receiver inonResume()
, you should unregister it inonPause()
to prevent registering it multiple times (If you don’t want to receive broadcasts when paused, and this can cut down on unnecessary system overhead). Do not unregister inonSaveInstanceState(Bundle)
, because this isn’t called if the user moves back in the history stack.
Sending broadcasts
Android provides three ways for apps to send broadcast:
- The
sendOrderedBroadcast(Intent, String)
method sends broadcasts to one receiver at a time. As each receiver executes in turn, it can propagate a result to the next receiver, or it can completely abort the broadcast so that it won’t be passed to other receivers. The order receivers run in can be controlled with the android:priority attribute of the matching intent-filter; receivers with the same priority will be run in an arbitrary order. - The
sendBroadcast(Intent)
method sends broadcasts to all receivers in an undefined order. This is called a Normal Broadcast. This is more efficient, but means that receivers cannot read results from other receivers, propagate data received from the broadcast, or abort the broadcast. - The
LocalBroadcastManager.sendBroadcast
method sends broadcasts to receivers that are in the same app as the sender. If you don’t need to send broadcasts across apps, use local broadcasts. The implementation is much more efficient (no interprocess communication needed) and you don’t need to worry about any security issues related to other apps being able to receive or send your broadcasts.
The following code snippet demonstrates how to send a broadcast by creating an Intent and calling sendBroadcast(Intent)
.
Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data", "Nothing to see here, move along.");
sendBroadcast(intent);
The broadcast message is wrapped in an Intent
object. The intent’s action string must provide the app’s Java package name syntax and uniquely identify the broadcast event. You can attach additional information to the intent with putExtra(String, Bundle)
. You can also limit a broadcast to a set of apps in the same organization by calling setPackage(String)
on the intent.
Note: Although intents are used for both sending broadcasts and starting activities with
startActivity(Intent)
, these actions are completely unrelated. Broadcast receivers can’t see or capture intents used to start an activity; likewise, when you broadcast an intent, you can’t find or start an activity.
准备
IDE:
Android Studio 4.1.1
Build #AI-201.8743.12.41.6953283, built on November 5, 2020
Runtime version: 1.8.0_242-release-1644-b01 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Windows 10 10.0
Android Virtual Devices:
Name: Pixel_2_API_28
CPU/ABI: Google Play Intel Atom (x86)
Path: C:\Users\86188\.android\avd\Pixel_2_API_28.avd
Target: google_apis_playstore [Google Play] (API level 28)
Skin: pixel_2
SD Card: 512M
fastboot.chosenSnapshotFile:
runtime.network.speed: full
hw.accelerometer: yes
hw.device.name: pixel_2
hw.lcd.width: 1080
hw.initialOrientation: Portrait
image.androidVersion.api: 28
tag.id: google_apis_playstore
hw.mainKeys: no
hw.camera.front: emulated
avd.ini.displayname: Pixel 2 API 28
hw.gpu.mode: auto
hw.ramSize: 1536
PlayStore.enabled: true
fastboot.forceColdBoot: no
hw.cpu.ncore: 4
hw.keyboard: yes
hw.sensors.proximity: yes
hw.dPad: no
hw.lcd.height: 1920
vm.heapSize: 256
skin.dynamic: yes
hw.device.manufacturer: Google
hw.gps: yes
hw.audioInput: yes
image.sysdir.1: system-images\android-28\google_apis_playstore\x86\
showDeviceFrame: yes
hw.camera.back: virtualscene
AvdId: Pixel_2_API_28
hw.lcd.density: 420
hw.arc: false
hw.device.hash2: MD5:55acbc835978f326788ed66a5cd4c9a7
fastboot.forceChosenSnapshotBoot: no
fastboot.forceFastBoot: yes
hw.trackBall: no
hw.battery: yes
hw.sdCard: yes
tag.display: Google Play
runtime.network.latency: none
disk.dataPartition.size: 6442450944
hw.sensors.orientation: yes
avd.ini.encoding: UTF-8
hw.gpu.enabled: yes
注意:以下示例仅在安卓虚拟设备上运行测试,并没有在真实的设备上运行测试。
接收广播
新建项目,选择 Empty Activity,在配置项目时,Minimum SDK
选择 API 16: Android 4.1 (Jelly Bean)
。
编辑 MainActivity
文件:
package com.mk;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private Context activityContext;
private static final String TAG = "MainActivity";
private BroadcastReceiver airplaneModeChangedBroadcastReceiver;
private BroadcastReceiver networkStateChangedBroadcastReceiver;
private BroadcastReceiver wifiStateChangedBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
activityContext = this;
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume: " + System.currentTimeMillis());
airplaneModeChangedBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
StringBuilder sb = new StringBuilder();
Bundle extras = intent.getExtras();
boolean state = extras.getBoolean("state");
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
sb.append("Airplane Mode: " + (state ? "On" : "Off"));
String log = sb.toString();
Log.d(TAG, "onReceive: \n" + log);
}
};
networkStateChangedBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
StringBuilder sb = new StringBuilder();
NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
sb.append("WiFi is " + (networkInfo.isConnected() ? "connected" : "disconnected") + "\n");
String log = sb.toString();
Log.d(TAG, "onReceive: \n" + log);
}
};
wifiStateChangedBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
StringBuilder sb = new StringBuilder();
String wifiState = null;
String previousWifiState = null;
switch (intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, -1)) {
case WifiManager.WIFI_STATE_DISABLED:
wifiState = "WifiManager.WIFI_STATE_DISABLED";
break;
case WifiManager.WIFI_STATE_DISABLING:
wifiState = "WifiManager.WIFI_STATE_DISABLING";
break;
case WifiManager.WIFI_STATE_ENABLED:
wifiState = "WifiManager.WIFI_STATE_ENABLED";
break;
case WifiManager.WIFI_STATE_ENABLING:
wifiState = "WifiManager.WIFI_STATE_ENABLING";
break;
case WifiManager.WIFI_STATE_UNKNOWN:
wifiState = "WifiManager.WIFI_STATE_UNKNOWN";
break;
}
switch (intent.getIntExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, -1)) {
case WifiManager.WIFI_STATE_DISABLED:
previousWifiState = "WifiManager.WIFI_STATE_DISABLED";
break;
case WifiManager.WIFI_STATE_DISABLING:
previousWifiState = "WifiManager.WIFI_STATE_DISABLING";
break;
case WifiManager.WIFI_STATE_ENABLED:
previousWifiState = "WifiManager.WIFI_STATE_ENABLED";
break;
case WifiManager.WIFI_STATE_ENABLING:
previousWifiState = "WifiManager.WIFI_STATE_ENABLING";
break;
case WifiManager.WIFI_STATE_UNKNOWN:
previousWifiState = "WifiManager.WIFI_STATE_UNKNOWN";
break;
}
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
sb.append("Previous WiFi state: " + previousWifiState + "\n");
sb.append("Current WiFi state: " + wifiState + "\n");
String log = sb.toString();
Log.d(TAG, "onReceive: \n" + log);
}
};
networkStateChangedBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
StringBuilder sb = new StringBuilder();
NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
sb.append("WiFi is " + (networkInfo.isConnected() ? "connected" : "disconnected") + "\n");
String log = sb.toString();
Log.d(TAG, "onReceive: \n" + log);
}
};
activityContext.registerReceiver(airplaneModeChangedBroadcastReceiver, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
activityContext.registerReceiver(wifiStateChangedBroadcastReceiver, new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION));
activityContext.registerReceiver(networkStateChangedBroadcastReceiver, new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION));
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause: " + System.currentTimeMillis());
activityContext.unregisterReceiver(airplaneModeChangedBroadcastReceiver);
activityContext.unregisterReceiver(wifiStateChangedBroadcastReceiver);
activityContext.unregisterReceiver(networkStateChangedBroadcastReceiver);
}
}
发送广播
新建项目,选择 Empty Activity,在配置项目时,Minimum SDK
选择 API 16: Android 4.1 (Jelly Bean)
。
编辑 src\main\res\layout\activity_main.xml
布局文件,添加一个 Button
元素:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/buttonSendingBroadcast"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="Sending Broadcast"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
编辑 MainActivity
文件:
package com.mk;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final String MY_ACTION = "com.mk.MY_ACTION";
// 广播接收器
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (MY_ACTION.equals(action)) {
String data = intent.getStringExtra("data");
Toast.makeText(context, data, Toast.LENGTH_SHORT).show();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((Button) findViewById(R.id.buttonSendingBroadcast)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MY_ACTION);
intent.putExtra("data", "extra data");
intent.setPackage("com.mk");
sendBroadcast(intent); // 发送广播
}
});
// 注册广播接收器
registerReceiver(broadcastReceiver, new IntentFilter(MY_ACTION));
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销广播接收器
unregisterReceiver(broadcastReceiver);
}
}
参考
Intent.ACTION_AIRPLANE_MODE_CHANGED