Andorid广播

以下内容摘自郭霖《第一行代码》第三版

文章目录

    • 一、广播机制简介
      • 1.1 广播的类型
        • 1.1.1 标准广播
        • 1.1.2 有序广播
    • 二、接收系统广播
      • 2.1 动态注册监听时间变化
      • 2.2 静态注册实现开机启动
    • 三、发送自定义广播
      • 3.1 发送标准广播
      • 3.2 发送有序广播

一、广播机制简介

1.1 广播的类型

Android中的广播主要可以分为两种类型:标准广播和有序广播。

1.1.1 标准广播

标准广播(normal broadcasts)是一种完全异步执行的广播,在广播发出之后,所有的BroadcastReceiver几乎会在同一时刻收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的。
Andorid广播_第1张图片

1.1.2 有序广播

有序广播(ordered broadcasts)则是一种同步执行的广播,在广播发出之后,同一时刻只会有一个BroadcastReceiver能够收到这条广播消息,当这个BroadcastReceiver中的逻辑执行完毕后,广播才会继续传递。所以此时的BroadcastReceiver是有先后顺序的,优先级高的BroadcastReceiver就可以先收到广播消息,并且前面的BroadcastReceiver还可以截断正在传递的广播,这样后面的BroadcastReceiver就无法收到广播消息了。
Andorid广播_第2张图片

二、接收系统广播

2.1 动态注册监听时间变化

注册BroadcastReceiver的方式一般有两种:在代码中注册和在AndroidManifest.xml中注册。其中前者也被称为动态注册,后者也被称为静态注册。

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

观察onCreate()方法,首先我们创建了一个IntentFilter的实例,并给它添加了一个值为android.intent.action.TIME_TICK的action,因为当系统时间发生变化时,系统发出的正是一条值为android.intent.action.TIME_TICK的广播,也就是说BroadcastReceiver想要监听什么广播,就在这里添加相应的action。接下来创建了一个TimeChangeReceiver的实例,然后调用registerReceiver()方法进行注册,将TimeChangeReceiver的实例和IntentFilter的实例都传了进去,这样TimeChangeReceiver就会收到所有值为android.intent.action.TIME_TICK的广播,也就实现了监听系统时间变化的功能。

动态注册的BroadcastReceiver一定要取消注册才行,这里是在onDestroy()方法中通过调用unregisterReceiver()方法来实现的。

Android系统还会在亮屏熄屏、电量变化、网络变化等场景下发出广播。如果想查看完整的系统广播列表,可以到如下的路径中去查看:

<Android SDK>/platforms/<任意android api版本>/data/broadcast_actions.txt

2.2 静态注册实现开机启动

在Android 8.0系统之后,所有隐式广播都不允许使用静态注册的方式来接收了。隐式广播指的是那些没有具体指定发送给哪个应用程序的广播,大多数系统广播属于隐式广播,但是少数特殊的系统广播目前仍然允许使用静态注册的方式来接收。这些特殊的系统广播列表详见https://developer.android.google.cn/guide/components/broadcast-exceptions?hl=zh-cn。

class BootCompleteReceiver : BroadcastReceiver() {
	override fun onReceive(context: Context, intent: Intent) {
		Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show()
	}
}

静态的BroadcastReceiver一定要在AndroidManifest.xml文件中注册才可以使用。如果使用Android Studio的快捷方式创建BroadcastReceiver,注册这一步已经自动完成了。

<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/AppTheme">
		...
		<receiver
			android:name=".BootCompleteReceiver"
			android:enabled="true"
			android:exported="true">
		receiver>
	application>
manifest>

不过目前的BootCompleteReceiver是无法收到开机广播的,因为没有进行授权,因此我们还需要对AndroidManifest.xml文件进行修改才行,添加授权:

<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/AppTheme">
		...
		<receiver
			android:name=".BootCompleteReceiver"
			android:enabled="true"
			android:exported="true">
			<intent-filter>
				<action android:name="android.intent.action.BOOT_COMPLETED" />
			intent-filter>
		receiver>
	application>
manifest>

Android 系统为了保护用户设备的安全和隐私,做了严格的规定:如果程序需要进行一些对用户来说比较敏感的操作,必须在AndroidManifest.xml文件中进行权限声明,否则程序将会直接崩溃。

注意:不要在onReceive()方法中添加过多的逻辑或者进行任何的耗时操作,因为BroadcastReceiver中是不允许开启线程的,当onReceive()方法运行了较长时间而没有结束时,程序就会出现错误。

三、发送自定义广播

3.1 发送标准广播

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

然后在AndroidManifest.xml中对这个BroadcastReceiver进行修改:

<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/AppTheme">
		...
		<receiver
			android:name=".MyBroadcastReceiver"
			android:enabled="true"
			android:exported="true">
			<intent-filter>
				<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
			intent-filter>
		receiver>
	application>
manifest>
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)
		}
		...
	}
	...
}

对第2步调用的setPackage()方法进行更详细的说明:在Android 8.0系统之后,静态注册的BroadcastReceiver是无法接收隐式广播的,而默认情况下发出的自定义广播恰恰都是隐式广播。因此这里一定要调用setPackage()方法,指定这条广播是发送给哪个应用程序的,从而让它变成一条显式广播,否则静态注册的BroadcastReceiver将无法接收到这条广播。

另外,由于广播是使用Intent来发送的,因此你还可以在Intent中携带一些数据传递给相应的BroadcastReceiver,这一点和Activity的用法是比较相似的

3.2 发送有序广播

和标准广播不同,有序广播是一种同步执行的广播,并且是可以被截断的。
新增AnotherBroadcastReceiver

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

修改Manifest.xml

<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/AppTheme">
		 ...
		 <receiver
			 android:name=".AnotherBroadcastReceiver"
			 android:enabled="true"
			 android:exported="true">
			 <intent-filter>
				 <action android:name="com.example.broadcasttest.MY_BROADCAST" />
			 intent-filter>
		 receiver>
	 application>	
manifest>

不过,到目前为止,程序发出的都是标准广播,现在我们来尝试一下发送有序广播。修改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)
		}
		...
	}
	...
}

sendOrderedBroadcast()方法接收两个参数:第一个参数仍然是Intent;第二个参数是一个与权限相关的字符串,这里传入null就行了。
设定BroadcastReceiver的先后顺序:

<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/AppTheme">
		...
		<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>
		...
	</application>
</manifest>

通过android:priority属性给BroadcastReceiver设置了优先级,优先级比较高的BroadcastReceiver就可以先收到广播。这里将MyBroadcastReceiver的优先级设成了100,以保证它一定会在AnotherBroadcastReceiver之前收到广播。
既然已经获得了接收广播的优先权,那么MyBroadcastReceiver就可以选择是否允许广播继续传递了。修改MyBroadcastReceiver中的代码,如下所示:

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

如果在onReceive()方法中调用了abortBroadcast()方法,就表示将这条广播截断,后面的BroadcastReceiver将无法再接收到这条广播。

你可能感兴趣的:(Android,android)