Android - Broadcasts overview(不完整)

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:

  1. 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.

  2. Subclass BroadcastReceiver and implement onReceive(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:

  1. Create an instance of BroadcastReceiver.

    BroadcastReceiver br = new MyBroadcastReceiver();
    
  2. Create an IntentFilter and register the receiver by calling registerReceiver(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.

  3. 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 in onDestroy() to prevent leaking the receiver out of the activity context. If you register a receiver in onResume(), you should unregister it in onPause() 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 in onSaveInstanceState(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);
    }
}

参考

Broadcasts overview

Intent.ACTION_AIRPLANE_MODE_CHANGED

WifiManager.WIFI_STATE_CHANGED_ACTION

WifiManager.NETWORK_STATE_CHANGED_ACTION

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值