1. 程式人生 > 其它 >Android - Broadcasts overview(不完整)

Android - Broadcasts overview(不完整)

技術標籤:Androidandroid

文章目錄

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.


準備

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);
    }
}

參考

Broadcasts overview

Intent.ACTION_AIRPLANE_MODE_CHANGED

WifiManager.WIFI_STATE_CHANGED_ACTION

WifiManager.NETWORK_STATE_CHANGED_ACTION