Android BroadcastReceiver

BroadcastReceiver

使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型。

广播分为普通广播(无序广播)和有序广播。而广播的注册方式又有两种,分静态注册和动态注册,xml中注册的(静态广播)优先级高于动态注册广播

  • 普通广播:异步的,逻辑上讲是可以在同一时刻被所有接收者接收到,消息传递效率比较高。
    但缺点是:接收者不能将处理结果传递给下一个接收者,并且无法终止广播Intent的传递。
    通过Context.sendBroadcast()发送普通广播。
  • 有序广播:按照接收者声明的优先级别(声明在intent-filter元素的android:priority属性中,数越大优先级别越高,取值范围:-1000到1000。也可以调用IntentFilter对象的setPriority()进行设置)按顺序接受。
    前面的接收者有权终止广播(BroadcastReceiver.abortBroadcast())。
    前面的接收者可以将处理结果通过setResultExtras(Bundle)方法存放进结果对象,然后传给下一个接收者,通过代码:Bundle bundle =getResultExtras(true))可以获取上一个接收者存入在结果对象中的数据。
    系统收到短信,发出的广播属于有序广播。如果想阻止用户收到短信,可以通过设置优先级,让你们自定义的接收者先获取到广播,然后终止广播,这样用户就接收不到短信了。
    通过Context.sendOrderedBroadcast()发送有序广播。
  • 粘性广播:

静态广播注册步骤:

  1. 自定义一个类继承BroadcastReceiver
  2. 重写onReceive方法
  3. 在manifest.xml中注册

示例:

1
2
3
4
5
6
7
8
9
10
11
public class MyBroadcastReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Log.i("fuck","intent-action : " + intent.getAction());
if(intent.getAction().equals("test")){
Toast.makeText(context,"fuck",Toast.LENGTH_LONG).show();
}
}

}

注册:

1
2
3
4
5
6
7
8
9
//广播接收器
<receiver android:name=".broadcast.MyBroadcastReceiver">

<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="test"/>//这里自定义一个广播动作
</intent-filter>

</receiver>

注:BroadcastReceiver生命周期很短,如果需要在onReceiver完成一些耗时操作,应该考虑在Service中开启一个新线程处理耗时操作,不应该在BroadcastReceiver中开启一个新的线程,因为BroadcastReceiver生命周期很短,在执行完onReceiver以后就结束,如果开启一个新的线程,可能出现BroadcastRecevier退出以后线程还在,而如果BroadcastReceiver所在的进程结束了,该线程就会被标记为一个空线程,根据Android的内存管理策略,在系统内存紧张的时候,会按照优先级,结束优先级低的线程,而空线程无异是优先级最低的,这样就可能导致BroadcastReceiver启动的子线程不能执行完成。

在 Android 中,程序的响应( Responsive )被活动管理器( Activity Manager )和窗口管理器( Window Manager )这两个系统服务所监视。当 BroadcastReceiver 在 10 秒内没有执行完毕,Android 会认为该程序无响应。所以在 BroadcastReceiver 里不能做一些比较耗时的操作,否侧会弹出ANR ( Application No Response )的对话框。如果需要完成一项比较耗时的工作,应该通过发送Intent 给 Service ,由 Service 来完成。而不是使用子线程的方法来解决,因为 BroadcastReceiver 的生命周期很短(在 onReceive() 执行后 BroadcastReceiver 的实例就会被销毁),子线程可能还没有结束BroadcastReceiver 就先结束了。如果 BroadcastReceiver 结束了,它的宿主进程还在运行,那么子线程还会继续执行。但宿主进程此时很容易在系统需要内存时被优先杀死,因为它属于空进程(没有任何活动组件的进程)。

动态广播注册步骤:

  1. registerReceiver(new MyBroadcastReceiver(),new IntentFilter("test")); 注册动态广播。
  2. new Intent("test").sendBroadcast(intent); 发送动态广播。
  3. <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/> 注意权限

静态注册与动态注册的区别

  • 动态注册广播不是常驻型广播,也就是说广播跟随activity的生命周期。注意: 在activity结束前,移除广播接收器。
    静态注册是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。

  • 当广播为有序广播时:

    1. 优先级高的先接收
    2. 同优先级的广播接收器,动态优先于静态。
    3. 同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。
  • 当广播为普通广播时:

    1. 无视优先级,动态广播接收器优先于静态广播接收器。
    2. 同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。

示例(Kotlin)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
* 第一行代码第三版
*
* 在一个 IP 网络范围中,最大的 IP 地址是被保留作为广播地址来使用的。
* 比如某个网络的 IP 范围是 192.168.0.XXX,子网掩码是 255.255.255.0,那么这个网络的广播地址就是 192.168.0.255。
* 广播数据包会被发送到同一网络上的所有端口,这样该网络中的每台计算机都会收到这条广播。
*
* Android 中的广播主要可分为两种类型:
* 标准广播(normal broadcasts):是一种完全异步执行的广播,
* 在广播发出之后,所有的 BroadcastReceiver 几乎会在同一时刻收到这条广播消息,因此它们之间没有任何先后顺序可言。
* 这种广播的效率比较高,但同时也意味着它是无法被截断的。
*
* 有序广播(ordered broadcasts):是一种同步执行的广播,
* 在同一时刻只会有一个 BroadcastReceiver 能够接收到这条广播消息,当这个 BroadcastReceiver 中的逻辑执行完毕后,广播才会继续。
* 所以此时的 BroadcastReceiver 是有先后顺序的,优先级高的就可以先收到广播消息,
* 并且前面的还可以截断正在传递的广播,这样后面的 BroadcastReceiver 就无法收到广播消息了。
*
* Android 内置了很多系统级别的广播,比如开机完成,亮屏息屏,电量变化,系统时间变化等都会发出一条广播,
* 可到如下路径查看完整的系统广播列表:
* <Android SDK>/platforms/<任意 android api 版本>/data/broadcast_actions.txt
* 发送广播是借助 Intent,而接收广播则使用 BroadcastReceiver,可自由注册自己感兴趣的广播来监听。
*/
class BroadcastActivity :BaseActivity(){

lateinit var timeChangeReceiver: TimeChangeReceiver

/**
* 注册 BR 的方式一般有两种:静态注册(在 AndroidManifest.xml 中注册)和动态注册。
* 创建 BR 的方式:只需新建一个类,让它继承自 BroadcastReceiver,并重写父类的 onReceive()。
* 当有广播到来时,onReceive() 会得到执行。
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_broadcast)

initView()
}

private fun initView() {
// 发送自定义广播
btnSendBR.setOnClickListener{
// 静态注册
val intent = Intent("com.example.myapplication.broadcast.MY_BROADCAST")
// Android 8.0 后,静态注册的 BroadcastReceiver 是无法接收隐式广播的,
// 而默认情况下发出的自定义广播恰恰都是隐式广播。
// 因此这里调用 setPackage() 指定这条广播是发送给哪个应用程序的,从而让它变成一条显式广播。
// packageName 是 getPackageName() 的语法糖写法,用于获取当前应用的包名。
intent.setPackage(packageName)
// 因为是通过 intent 发送的,还可以如 Activity 一般携带一些数据。
// 发送标准广播(无序广播)
// sendBroadcast(intent)
// 发送有序广播,第二个参数是一个与权限相关的字符串,传入 null 即可。
// <intent-filter android:priority="100"> 指定优先级,优先级高的先收到广播
// onReceive() 中调用 abortBroadcast() 可以拦截广播。
sendOrderedBroadcast(intent,null)
}

// 动态注册
val intentFilter = IntentFilter()
// 当系统时间发生变化时,每隔一分钟,系统发出的正是一条值为 android.intent.action.TIME_TICK 的广播
intentFilter.addAction("android.intent.action.TIME_TICK")
timeChangeReceiver = TimeChangeReceiver()
registerReceiver(timeChangeReceiver,intentFilter)

// 实现强制下线功能
btnForce.setOnClickListener{
// 这条广播就是用于通知程序强制下线的。
// 强制下线的逻辑并不写在这里,而应该写在接收这条广播的 BroadcastReceiver 里,
// 这样强制下线的功能就不会依附于任何界面了。
// 由于 BR 中需要弹框来阻塞用户操作,而静态的 BR 是没办法在 onReceive() 里弹出对话框这样的 UI 控件的,
// 但又不能在每个 Activity 中都注册一个动态的 BR,所以只需要在 BaseActivity() 动态注册一个 BR 即可。
val intent = Intent("com.example.broadcast.FORCE_OFFLINE")
sendBroadcast(intent)
}
}

inner class TimeChangeReceiver:BroadcastReceiver(){

override fun onReceive(context: Context?, intent: Intent?) {
Toast.makeText(context,"Time is changed",Toast.LENGTH_SHORT).show()
}
}

companion object{
fun actionStart(context: Context){
val intent = Intent(context, BroadcastActivity::class.java)
context.startActivity(intent)
}
}

override fun onDestroy() {
super.onDestroy()
// 动态注册的广播一定要取消注册才行
unregisterReceiver(timeChangeReceiver)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 动态注册必须在程序启动之后才能接收广播,因为注册的逻辑是写在 onCreate() 中的。
* Android 8.0 系统后,所有隐式广播都不允许使用静态注册的方式来接收了,
* 隐式广播指的是那些没有具体指定发送给哪个应用程序的广播,大多系统广播属于隐式广播,
* 但是少数特殊的系统广播目前仍然允许使用静态注册的方式接收,这些特殊的系统广播列表详见:
* https://developer.android.google.cn/guide/components/broadcast-exceptions.html
*
* 这里采用静态注册的方式,实现开机启动。
* 权限:<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
*/
class BootCompleteReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
// BroadcastReceiver 中是不允许开启线程的,所以要避免在这里添加过多的逻辑或者是耗时操作。
Toast.makeText(context,"Boot Complete",Toast.LENGTH_LONG).show()
Log.d("TAG","Boot Complete")
}
}
1
2
3
4
5
6
7
class MyReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context,"receiver in MyBroadcastReceiver",Toast.LENGTH_SHORT).show()
abortBroadcast()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
open class BaseActivity : AppCompatActivity() {

lateinit var receiver:ForceOfflineReceiver

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// 隐藏标题栏:调用 getSupprotActionBar 获得 ActionBar 的实例,并隐藏。
supportActionBar?.hide()

// 知晓当前是哪一个 Activity:
// javaClass 代表获取当前实例的 Class 对象,相当于 Java 中调用 getClass()
// BaseActivity::class.java 表示获取此类的 Class 对象,相当于 Java 中调用 BaseActivity.class。
Log.d("TAG_BaseActivity",javaClass.simpleName)
ActivityCollector.addActivity(this)
}

/**
* 通过广播,实现强制下线功能。
*/
inner class ForceOfflineReceiver:BroadcastReceiver(){
override fun onReceive(context: Context, intent: Intent?) {
AlertDialog.Builder(context).apply {
setTitle("Warning")
setMessage("You are forced to offline, Please try to login again.")
// 对话框不可取消
setCancelable(false)
// 注册确定按钮
setPositiveButton("OK"){_,_ ->
// 销毁所有 Activity
ActivityCollector.finishAll()
val i = Intent(context,MainActivity::class.java)
// 重新启动主页面
context.startActivity(i)
}
show()
}
}
}

/**
* 这里使用 onResume() 和 onPause() 来注册和取消注册
* 因为我们始终需要保证只有处于栈顶的 Activity 才能接收到这条强制下线的广播。
*/
override fun onResume() {
super.onResume()
val intentFilter = IntentFilter()
intentFilter.addAction("com.example.broadcast.FORCE_OFFLINE")
receiver = ForceOfflineReceiver()
registerReceiver(receiver,intentFilter)
}

override fun onPause() {
super.onPause()
unregisterReceiver(receiver)
}

override fun onDestroy() {
super.onDestroy()
ActivityCollector.removeActivity(this)
}
}
AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<receiver
android:name=".broadcast.MyReceiver"
android:enabled="true"
android:exported="true">

<!-- android:priority="100" 指定有序广播的优先级 -->
<intent-filter android:priority="100">
<action android:name="com.example.myapplication.broadcast.MY_BROADCAST" />
</intent-filter>
</receiver> <!-- exported 属性表示是否允许这个 BroadcastReceiver 接收本程序以外的广播 -->
<!-- enabled 属性表示是否启用这个 BroadcastReceiver -->

<receiver
android:name=".broadcast.BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>