广播机制简介
Android的广播机制非常灵活,Android的每一个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会接收到自己所关心的广播内容,这些广播可能是来自系统的,也可能是来自于其他应用程序的。
Android提供了一套完整的API,允许应用程序自由地发送和接收广播。发送广播借助Intent,而接收广播的方法利用广播接收器Broadcast Receiver。
广播(Broadcast)机制用于进程/线程间通信,因此在我们应用程序内发出的广播,其他的应用程序应该也是可以收到的。广播分为广播发送和广播接收两个过程,其中广播接收者BroadcastReceiver便是Android四大组件之一。
BroadcastReceiver分为两类:
- 静态广播接收者:通过AndroidManifest.xml的标签来申明的BroadcastReceiver。
- 动态广播接收者:通过AMS.registerReceiver()方式注册的BroadcastReceiver,动态注册更为灵活,可在不需要时通过unregisterReceiver()取消注册。
从广播发送方式可分为三类:
- 普通广播:通过Context.sendBroadcast()发送,可并行处理
- 有序广播:通过Context.sendOrderedBroadcast()发送,串行处理
- Sticky广播:通过Context.sendStickyBroadcast()发送
广播的分类
按照发送的方式分类
标准广播
一种完全异步执行的广播。在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播信息,因此他们之间没有任何先后顺序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的。
发送标准广播(异步,无序)
1 | sendBroadcast(intent); |
有序广播
一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播信息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。此时的广播是有先后顺序的,优先级别高的广播接收器就可以先收到广播信息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播信息了。
发送有序广播(同步,有序)
1 | sendOrderedBroadcast(intent,null); |
按照注册的方式分类
动态注册广播
顾名思义,就是在代码中注册的。
静态注册广播
动态注册要求程序必须在运行时才能进行,有一定的局限性,如果我们需要在程序还没启动的时候就可以接收到注册的广播,就需要静态注册了。主要是在AndroidManifest中进行注册。
按照定义的方式分类
系统广播
Android系统中内置了多个系统广播,每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部当特定事件发生时,由系统自动发出。
自定义广播
由应用程序开发者自己定义的广播
标准广播
有序广播
接收系统广播
Android 内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种系统的状态信息。比如手机开机完成后会发出一条广播,电池的电量发生变化会发出一条广播,时间或时区发生改变也会发出一条广播等。而想要接收到这些广播,需要使用广播接收器。
实现一个广播接收器
1 | public class MyBroadcastReceiver extends BroadcastReceiver { |
主要就是继承一个BroadcastReceiver,重写onReceive方法,当有广播来的时候,onReceive()方法就会得到执行。在其中实现自己的业务逻辑就可以了。
动态注册监听网络变化
动态注册:代码中注册。
动态注册:调用Context的registerReceiver函数注册BroadcastReceiver; 当应用程序不再需要监听广播时,则需要调用unregisterReceiver函数进行反注册
静态注册:AndroidManifest.xml注册
例如:
1 | public class MainActivity extends AppCompatActivity { |
- 创建广播过滤器 new IntentFilter() ,添加一个值为android.net.conn.CONNECTIVIT_CHANGE的action,之所以添加这个值,是因为当网络状态发生变化的时候,系统发出的正是一条值为android.net.conn.CONNECTIVITY_CHANGE的广播。
- 创建广播接收者NetworkChangeReceiver,并重写onReceive方法,每当网络状态发生变化的时候,onReceive方法就会得到执行,这里只是简单地使用Toast提示一段文本信息。
- 调用registerReceiver方法进行注册,将广播接收者和过滤者的实例都传进去,就可以监听网络变化的状态了。
- 动态注册的广播接收器一定要取消注册才行:在onDestory()方法中调用ungisterReceiver()方法来实现。
- 首先注册完成的时候会收到一条广播,然后修改下网络状态,又会受到一条广播,就 Toast提醒网络状态发生了变化。
注意事项
1. 发送异步广播(标准广播,无序) 使用sendBroadcast(intent)方法; 而发送同步广播(有序广播) 使用sendOrderedBroadcast(intent,null)方法;
2. 动态注册的广播接收器一定都要取消注册才行,这里我们是在 onDestroy()方法中通过调用unregisterReceiver()方法来实现的。
3. 动态注册 的广播接收器可以自由地控制注册与注销,在灵活性方面有很大的优势,但它也存在着一个缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在 onCreate()方法中的。
只是提醒网络是否发生变化并不太人性化,还要准确地告诉用户当前是有网络还是没有网络,因此我们还需要对上面的代码进行优化,修改onReceive中的代码:
1 | class NetworkChangeReceiver extends BroadcastReceiver { |
- 在onReceive方法中,首先通过getSystemService方法得到ConnectivityManager的实例,这是系统服务类,专门用于管理网络连接的。
- ==然后调用它的getActiveNetworkInfo方法可以得到NetworkInfor实例==。
- 接着调用NetworkInfo的isAvailable方法,就可以判断出当前是否有网络了。
- 添加访问网络状态的权限(6.0就要动态注册了,后面讲):
1 | <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
静态注册实现开机启动
动态注册的广播接收器虽然可以自由地控制注册和注销,但是必须在程序启动之后才能接收到广播。如果想让程序在未启动的情况下就能接收到广播,就需要静态注册了。现在我们让程序接收一条开机广播,当收到这个条广播时就可以在onReceive()方法里执行相应的逻辑,从而实现开机启动的功能。
在程序包->New—>Other->Broadcast Receiver新建广播接收器,可选 Exported属性表示是否允许这个广播接收器接收本程序以外的广播,Enabled属性表示是否启用这个广播接收器。
新建的广播接收器,会由Android Studio自动帮你在AndroidManifest中注册。
代码:
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
静态的广播接收器一定要在AndroidManifest.xml中注册才可以使用,不过由于先前是使用AS快捷方式创建的广播接收器,因此注册这一步已经被自动完成了。会发现application标签内多了如下代码:
1 | <receiver |
说明:在标签内出现了一个新的标签,所有静态的广播接收器都是在这里进行注册的。它的用法其实和标签很相似,都是通过android:name来指定具体注册哪一个广播接收器,而 enaled和exported属性则是根据我们刚才勾选的状态自动生成的。
1 | <receiver |
几个相关解释:
android:exported
此BroadcastReceiver能否接收其他App发出的广播(其默认值是由receiver中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false);
android:name
此broadcastReceiver类名;
android:permission
如果设置,具有相应权限的广播发送方发送的广播才能被此broadcastReceiver所接收;
android:process
broadcastReceiver运行所处的进程。默认为App的进程。可以指定独立的进程(Android四大组件都可以通过此属性指定自己的独立进程);
不过目前BootCompleteReceiver还是不能接收到开机广播的,还需要对广播进行限定,添加广播过滤器和申请权限。
1 | <receiver |
1 | <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> |
- 由于Android系统启动完成后会发出一条值为android.permission.RECEIVE_BOOT_COMPLETED的广播,因此我们 标签里添加了相应的action。
- 监听系统开机广播也是需要声明权限的,我们使用标签又加入一条android.permission.RECEIVE_BOOT_COMPLETED权限。
将模拟器重新启动就可以收到开机广播了。
需要额外注意的是,不要在onReceive()方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中是不允许开启线程的,当onReceive方法运行较长时间而没有结束时,程序就会报错。所以广播接收器更多的是扮演一种打开程序其他组件的角色,比如创建一条状态栏通知,或者启动一个服务等。
发送广播
根据广播的发送方式,可以将其分为以下几种类型:
- Normal Broadcast:普通广播
- Ordered broadcast:有序广播
- Sticky Broadcast:粘性广播 (在 android 5.0/api 21中deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated)
发送标准广播
标准广播的主要特点为:
- 同级别接收先后是随机的(无序的)
- 级别低的后接收到广播
- 接收器不能截断广播的继续传播,也不能处理广播
- 同级别动态注册(代码中注册)高于静态注册(AndroidManifest中注册)
Context类提供两个方法可以用于发送普通广播:
sendBroadcast(Intent intent);
**sendBroadcast(Intent intent, String receiverPermission);**【第一个参数是intent,第二个参数是一个与权限有关的字符串】
差别是第二个设置权限。
发给特定的用户:
sendBroadcastAsUser(Intent intent, UserHandle user);
sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission);
发送有序广播
有序广播(Ordered broadcasts)则是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了,只是其的主要发送方式变为:sendOrderedBroadcast(intent, receiverPermission, …)。
1>多个具当前已经注册且有效的BroadcastReceiver接收有序广播时,是按照先后顺序接收的,先后顺序判定标准遵循为:将当前系统中所有有效的动态注册和静态注册的BroadcastReceiver按照priority属性值从大到小排序,对于具有相同的priority的动态广播和静态广播,动态广播会排在前面。
2>先接收的BroadcastReceiver可以对此有序广播进行截断,使后面的BroadcastReceiver不再接收到此广播,也可以对广播进行修改,使后面的BroadcastReceiver接收到广播后解析得到错误的参数值。当然,一般情况下,不建议对有序广播进行此类操作,尤其是针对系统中的有序广播。
有序广播的主要特点:
同级别接收是随机的(结合下一条)
同级别动态注册(代码中注册)高于静态注册(AndroidManifest中注册)
排序规则为:将当前系统中所有有效的动态注册和静态注册的BroadcastReceiver按照priority属性值从大到小排序
先接收的BroadcastReceiver可以对此有序广播进行截断,使后面的BroadcastReceiver不再接收到此广播,也可以对广播进行修改,使后面的BroadcastReceiver接收到广播后解析得到错误的参数值。当然,一般情况下,不建议对有序广播进行此类操作,尤其是针对系统中的有序广播。实现截断的代码为:
1 | abortBroadcast(); |
发送本地广播
前面涉及的都是系统的全局广播。容易引起安全性的问题。
本地广播就只能在应用程序的内部进行传递,广播接收器也只能接收来自本应用程序发出的广播。
发送本地广播主要使用LocalBroadcastManager来对广播进行管理,并提供了发送广播和注册广播接收器的方法
代码实现:
1 | public class MainActivity extends Activity { |
对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册的ContextReceiver才有可能接收到(静态注册或其他方式动态注册的ContextReceiver是接收不到的)。
本地广播是无法通过静态注册的方式来接收的,因为静态注册主要就是为了让程序在未启动的情况下也能收到广播,而发送本地广播时,我们的程序肯定已经启动了。
本地广播的优势:
- 可以明确知道正在发送的广播不会离开我们的程序,不需要担心机密数据泄漏的问题。
- 其他的程序无法将广播发送到我们程序的内部,不需要担心会有安全漏洞的隐患。
- 发送本地广播比起发送系统全局广播将会更加高效。
广播的安全性问题
由前文阐述可知,Android中的广播可以跨进程甚至跨App直接通信,且exported属性在有intent-filter的情况下默认值是true,由此将可能出现的安全隐患如下:
- 其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;
- 其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息。
无论哪种情形,这些安全隐患都确实是存在的。由此,业界常见的一些增加安全性的方案包括:
- 对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收;
- 在广播发送和接收时,都增加上相应的permission,用于权限验证;
- 发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
- 采用LocalBroadcastManager的方式