Android development foundation - broadcast mechanism

In order to facilitate system-level message notification, Android introduces a broadcast message mechanism, but the broadcast mechanism in Android is quite flexible.

Broadcast mechanism in Android

Each application program in Android can register the broadcast that it is interested in, so that the program can receive the broadcast content that it cares about. These broadcasts may come from the system or other applications. Android provides io complete API that allows applications to freely send and receive broadcasts.

Broadcasts are sent with Intent and received with BroadcastReceiver.

Broadcasting in Android is mainly divided into two types

  • Standard broadcast: A completely asynchronously executed broadcast. After the broadcast is sent out, all BroadcastReceivers will receive the broadcast message almost at the same time, so there is no sequence between them. This kind of broadcast is more efficient, but it also means that it cannot be truncated.
  • Orderly broadcast: A synchronously executed broadcast. After the broadcast is sent, only one BroadcastReceiver can receive the broadcast information at the same time. After the logic in the BroadcastReceiver is executed, the broadcast will continue to be delivered. Therefore, the BroadcastReceivers at this time are in order. The BroadcastReceiver with high priority can receive the broadcast information first, and the previous BroadcastReceiver can also intercept the broadcast being delivered, so that the latter BroadcastReceiver cannot receive the broadcast information.

Receive system broadcast

Android has built-in many system-level broadcasts, and users can get various system status information by listening to these broadcasts in the application.

Dynamic registration monitoring time changes

Users can freely register BroadcastReceiver according to their actual situation, so that when a corresponding broadcast is sent out, the corresponding BroadcastReceiver can receive the broadcast and perform logical processing internally. There are generally two ways to register BroadcastReceiver:

  • Register in code: also known as dynamic registration
  • Register in AndroidManifest.xml: also known as static registration

To create a BroadcastReceiver, you only need to create a new class, make it inherit from BroadcastReceiver, and rewrite the onReceive method of the parent class. In this way, when the broadcast arrives, the onReceive method will be executed, and the specific logic can be processed in this method.

class MainActivity : AppCompatActivity() {

    lateinit var timeChangeReceiver:TimeChangeReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val intentFilter = IntentFilter()
        intentFilter.addAction("android.intent.action.TIME_TICK")
        timeChangeReceiver = TimeChangeReceiver()
        registerReceiver(timeChangeReceiver, intentFilter)
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(timeChangeReceiver)
    }

    inner class TimeChangeReceiver: BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            Toast.makeText(context, "Time has changed", Toast.LENGTH_SHORT).show()
        }
    }
}

For example, in the above code, first define an internal class TimeChangeReceiver, make it inherit from BroadcastReceiver, and rewrite the onReceiver method, so that when the system time changes, the onReceiver method will be executed to prompt the current time to change.

In the onCreate method, first create an instance of IntentFilter, and add an action whose value is android:intent.action.TIME_TICK. When the system time changes, the system will send out a broadcast named this action, that is to say, the user If the BroadcastReceiver wants to listen to any broadcast, it needs to add the corresponding action. Then create an instance of TimeChangeReceiver, then call the registerReceiver method to register, and pass in both the instance of TimeChangeReceiver and the instance of IntentFilter, so that TimeChangeReceiver will also receive the broadcast with the value android:intent.action.TIME_TICK (the system every one minute A broadcast of android:intent.action.TIME_TICK will be sent out), which realizes the function of monitoring system time changes.

The result after running the program is:

At the same time, it should be noted that the dynamically registered BroadcastReceiver must be unregistered, which is implemented by calling the unregisterReceiver method in the onDestroy method.

The complete list of system broadcasts is in the following path:

Sdk\platforms\android-N\data\broadcast_actions.txt

Static registration to achieve booting

The dynamically registered BroadcastReceiver can freely control the registration and logout, which has great advantages in flexibility, but it must be able to receive the broadcast after the program starts, because the registration logic is written in the onCreate method. And use the way of static registration to realize booting.

Although in theory, dynamic registration can listen to system broadcasts, static registration should also be able to listen. However, because a large number of malicious applications use this mechanism to listen to system broadcasts when the program is not started, any application can be frequently woken up from the background, seriously affecting the power and performance of the user's mobile phone, so almost every Android system Versions are cutting the functionality of statically registered BroadcastReceivers.

After the Android 8.0 system, all implicit broadcasts are not allowed to be received by static registration. Implicit broadcasts refer to broadcasts that do not specify which application to send to. Most system broadcasts are implicit broadcasts, but a few special system broadcasts are still allowed to be received by static registration.

These special system broadcasts are listed in the following paths:

https://developer.android.google.cn/guide/components/broadcast-exceptions.html

Among these special system broadcasts, there is a boot broadcast android.intent.action.BOOT_COMPLETED, which can be used to start the application program.

When booting, the application is definitely not started, so this function obviously cannot be realized by dynamic registration, but should use static registration to receive the boot broadcast, and then execute the corresponding logic in the onReceiver method to achieve Startup function.

Here right click on the com.example.boradcasttest package (New-Other-Broadcast Reveiver):

 The two checkboxes above:

  • exported: Indicates whether the BroadcastReceiver is allowed to receive broadcasts other than this program
  • enabled: indicates whether to enable the BroadcastReceiver

Then modify the code in BootCompleteReceiver:

class BootCompleteReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        // This method is called when the BroadcastReceiver is receiving an Intent broadcast.
        Toast.makeText(context, "Boot Complete", Toast.LENGTH_SHORT).show()
    }
}

 Moreover, the static BroadcastReceiver must be registered in the AndroidManifest.xml file before it can be used. However, due to the way the BroadcastReceiver was created, Android Studio has automatically completed the registration in the AndroidManifest.xml file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BroadcastTest">
        <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true"></receiver>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

In the above code, the receiver tag appears in the application tag, and all static BroadcastReceivers are registered here.

However, the current BootCompleteReceiver cannot receive the boot broadcast, and AndroidManifest.xml needs to be modified:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest">

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BroadcastTest">
        <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>

        </receiver>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Since the android.permission.RECEIVE_BOOT_COMPLETED broadcast will be sent after the Android system is started, it is necessary to add the corresponding action to the intent-filter tag in the reveiver.

In order to protect the security and privacy of the user's device, the Android system has made strict regulations: if the program needs to perform some operations that are sensitive to the user, the permissions must be specified in the AndroidManifest.xml, otherwise the program will directly crash. For example, the uses-permission tag is used to declare the android.permission.RECEIVE_BOOT_COMPLETED permission.

Run the program, press and hold the power button to restart, the result is:

 Send a custom broadcast

What I mentioned before is to receive broadcasts, here's how to send custom broadcasts.

Send a standard broadcast

Before sending a broadcast, you need to define a BroadcastReceiver to prepare to receive the broadcast. Create a new MyBroadcastReceiver:

class MyBroadcastReceiver:BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Toast.makeText(context, "reveived in MyBroadcastReceiver", Toast.LENGTH_SHORT).show()
    }
}

When MyBroadcastReceiver receives a custom broadcast, it will pop up a prompt.

Then modify the BroadcastReceiver in AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest">

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BroadcastTest">
        <receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
            </intent-filter>

        </receiver>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

The above code will make MyBroadcastReceiver receive a broadcast whose value is com.example.broadcasttest.MY_BROADCAST, so when sending a broadcast later, it will send this broadcast.

Then modify the code in activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send Broadcast"/>

</LinearLayout>

Here use this button to send the broadcast, and then modify MainActivity:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        button.setOnClickListener {
            val intent = Intent("com.example.broadcasttest.MY_BROADCAST")
            intent.setPackage(packageName)
            sendBroadcast(intent)
        }
    }
}

In the above code, the Intent object is constructed first, and the broadcast value to be sent is passed in, and then the setPackage method of the Intent is called to pass in the package name of the current program. Finally, call the sendBroadcast method to send the broadcast, so that the BroadcastReceiver listening to the broadcast will receive the message, and the broadcast sent at this time is a standard broadcast.

It should be noted that after the Android 8.0 system, the statically registered BroadcastReceiver cannot receive implicit broadcasts, and the custom broadcasts sent by default are all implicit broadcasts, so here you need to call the setPackage method to specify that the broadcast should be sent To which application, so that it becomes an explicit broadcast, otherwise the statically registered BroadcastReceiver will not be able to receive the broadcast.

The result after running the program is:

 In this way, a custom broadcast is sent, and at the same time, some data can be carried in the Intent and passed to the corresponding BroadcastReceiver.

send ordered broadcast

Unlike standard broadcasts, ordered broadcasts are broadcasts that execute synchronously and can be truncated. Verify here, create a new AnotherBroadcastReceiver:

class AnotherBroadcastReceiver:BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Toast.makeText(context, "received in AnotherBroadcastReceiver", Toast.LENGTH_SHORT).show()
    }
}

Then modify AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest">

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BroadcastTest">
        <receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
            </intent-filter>

        </receiver>
        <receiver
            android:name=".AnotherBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
            </intent-filter>

        </receiver>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

The result of running the program is:

 In other words, the broadcast at this time will be received by two BroadcastReceivers. But the program sends out standard broadcasts, this is an attempt to send ordered broadcasts. Modify the code in MainActivity:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.setOnClickListener {
            val intent = Intent("com.example.broadcasttest.MY_BROADCAST")
            intent.setPackage(packageName)
            sendOrderedBroadcast(intent, null)
        }
    }
}

 In the above code, one line of code is modified, that is, sendBroadcast is changed to sendOrderedBroadcast. This method receives two parameters. The first parameter is still Intent, and the second parameter is a string related to permissions. Here, just pass in null.

The result after running the program is:

 

 However, the result here is the same as before, and since it is an orderly broadcast, it is possible to set the order in which it is received. At this time, you need to modify AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest">

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BroadcastTest">
        <receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter android:priority="100">
                <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
            </intent-filter>

        </receiver>
        <receiver
            android:name=".AnotherBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
            </intent-filter>

        </receiver>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

The above code only specifies the android:priority attribute for MyBroadcastReceiver, which corresponds to the priority when receiving ordered broadcasts, and those with higher priority will receive the broadcast first.

Now that the priority of receiving the broadcast has been obtained, MyBroadcastReciver can choose whether to allow the broadcast to continue to be delivered, and modify the code in MyBroadcastReciver:

class MyBroadcastReceiver:BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Toast.makeText(context, "reveived in MyBroadcastReceiver", Toast.LENGTH_SHORT).show()
        abortBroadcast()
    }
}

The above code means that the delivery of the broadcast is cut off in MyBroadcastReceiver, that is to say, the subsequent BroadcastReceiver can no longer receive the broadcast.

At this point, the result of running the program is:

Guess you like

Origin blog.csdn.net/SAKURASANN/article/details/127038929