一. 什么是广播
广播(Broadcast)机制用于进程/线程间通信,广播最大的特点就是发送方并不关心接收方是否接到数据,也不关心接收方是如何处理数据的,它只负责“说”而不管你“听不听”,广播可以来之系统,也可以来自于其他应用程序.
- 按发送方式分类:
标准广播:是一种 完全异步执行 的广播,没有任何先后顺序,所有的广播接收器几乎同一时刻接收到这条广播消息,效率高,无法被截断。
有序广播:是一种 同步执行 的广播,有先后顺序,同一时刻只有一个接收器可以接收这个广播消息,优先级高的广播接收器可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法接收广播消息了。
- 按注册方式分类
静态广播:不管应用程序是否处于活动状态,都会进行监听
动态广播:在代码中进行注册,注意动态注册的广播一定要取消注册才行,通常是在onDestroy()方法中调用unregisterReceiver()方法来实现
静态广播:每次触发都会建立新的Receiver对象。
动态广播:从开始创建直到其被解除注册会使用同一个Receiver,无论这个广播被触发几次;
- 按定义方式分类
系统广播: Android系统中内置了多个系统广播,每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部当特定事件发生时,由系统自动发出。
自定义广播: 由应用程序开发者自己定义的广播
二. 为什么使用广播(什么时候使用广播)
- 比如在收到推送的消息时,当前页面的消息展示数改变,此时可以用到广播;
- 比如qq等断开网络或网络️断开到连接正常时,监测网络状态变化,此时可以用到广播
其实需要用到广播的地方无处不在,只是有的被你用第三方库代替了,比如rxbus、EventBus等
三. 广播的使用
- 静态广播
在AndroidManifest.xml
的application
里面定义receiver
并设置要接收的action
,MyReceiver
为继承BroadcastReceiver
的类,重写了onReceiver
方法,并在onReceiver
方法中对广播进行处理。
标签设置过滤器,接收指定action广播。
- 注册
//指定广播接收器
//设置关闭优先级
//确定需要接收的广播频道(自定义名字)
特点:不管该应用程序是否处于活动状态,都会进行监听
- 发送
Intent intent = new Intent("XXX");//xxx即为action
intent.putExtra("key", "要传的value");
sendBroadcast(intent);
- 接收
public class MyReceiver extends BroadcastReceiver {
//不能在onReceiver做好事操作
@Override
public void onReceive(Context context, Intent intent) {
String value = intent.getStringExtra("test");
Toast.makeText(context, value, Toast.LENGTH_LONG).show();
}
}
- 动态广播
- 注册
MyReceiver myReceiver= new MyReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("XXX"); //加入Action条件
//filter.setPriority(Integer.MAX_VALUE); //在有序广播里可以设置他的优先级别
registerReceiver(myReceiver, filter); //注册接收器的方法:1.接收器的实体类 2.action频道的信息
- 发送:
Intent intent = new Intent();
intent.setAction("XXX");
intent.putExtra("key", "传递的value");
sendBroadcast(intent);
- 接收
public class MyReceiver2 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String value = intent.getStringExtra("value");
Toast.makeText(context, value, Toast.LENGTH_LONG).show();
}
}
- 注销:
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(myReceiver);//注销接收器
}
- 有序广播
- 注册
- 发送
Intent intent2 = new Intent();
intent2.setAction("Test");
intent2.putExtra("key1", "发射了");
sendOrderedBroadcast(intent2, null);//(null不匹配权限)
- 接收
public class MyReceiver1 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, intent.getStringExtra("key1"), Toast.LENGTH_LONG).show();
setResultData("我也要发射了");//给后续的接收器
}
}
public class MyReceiver2 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, getResultData(), Toast.LENGTH_SHORT).show();
//abortBroadcast();//中断后续所有接收器
}
}
-
广播安全性问题
- 设置
intent-filter
的export=false
(接受的只是本应用程序的广播,外部app的广播不接受) - 在广播发送和接收时,都增加上相应的permission,用于权限验证;
- 发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定,这样 此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中;
- 采用
LocalBroadcastManager
.
- 设置
LocalBroadcastManager使用
只能动态注册,不能静态注册
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private LocalReceiver localReceiver;
private LocalBroadcastManager localBroadcastManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
localBroadcastManager.sendBroadcast(intent); // 发送本地广播
}
});
localBroadcastManager = LocalBroadcastManager.getInstance(this); // 获取实例
localReceiver = new LocalReceiver();
intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
localBroadcastManager.registerReceiver(localReceiver, intentFilter); // 注册本地广播监听器
}
@Override
protected void onDestroy() {
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
}
}
}
- 粘性广播Sticky Broadcast
5.0之后Sticky Broadcast被抛弃了,所以现在也没必要讲 - 自定义广播权限
- 静态注册
- 动态注册
MyBroadcastReceiver receiver = new MyBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("action.name");
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
//注册receiver时,直接指定发送者应该具有的权限。不然外部应用依旧可以触及到receiver
registerReceiver(receiver, intentFilter, "com.lazyxu.permission.test", null);
在注册的时候,最关键的一点是用registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)进行注册,而不是平常用的是registerReceiver(BroadcastReceiver, IntentFilter)。相较于后者,前者在注册的时候要求了发送者必须具有的权限。如果发送者没有该权限,那么发送者发送的广播即使经过IntentFilter的过滤,也不会被receiver接收。此时如果再自定义一个权限,并且将权限的protectionLevel设置为signature,那么外部应用便无法使用该权限,也就无法触及到该receiver。
- 发送广播
Intent intent = new Intent("action.name");
sendBroadcast(intent,"com.lazyxu.permission.test");
另外需要在manifest文件中定义权限并声明
在自定义权限时,通常会指定protectionLevel属性,常用的如下:
normal: 默认的,应用安装前用户可以看到相应的权限,但无需用户主动授权。
dangerous:normal安全级别控制以外的任何危险操作。需要dangerous级别权限时,Android会明确要求用户进行授权。常见的如:网络使用权限,相机使用权限及联系人信息使用权限等。
signature:它要求权限声明应用和权限使用应用使用相同的keystore进行签名。如果使用同一keystore,则该权限由系统授予,否则系统会拒绝。并且权限授予时,不会通知用户。它常用于应用内部。把protectionLevel声明为signature,如果别的应用使用的不是同一个签名文件,就没办法使用该权限,从而保护了自己的接收者。
注意
- 如果
BroadcastReceiver
的onReceiver()
方法不能在10秒内执行完成,Android会认为该程序无响应。所以不要在onReceiver()
里执行耗时的操作,否则会弹出ANR(Application No Response)的对话框。如果需要根据Broadcast
来完成一项比较耗时的操作,则可以考虑通过Intent启动一个Service来完成该操作。不赢考虑使用新线程去完成耗时操作,因为BroadcastReceiver
本身生命周期很短,可能出现子线程还没有结束,BroadcastReceiver
就已经退出。
- BroadcastReceiver本身不是Context,其内部也不含有Context,但在onReceive(Context context, Intent intent)中有context参数。这个context随着receiver的注册方式的不同而不同:
静态注册:context为ReceiverRestrictedContext
动态注册:context为Activity的context
LocalBroadcastManager的动态注册:context为Application的context - 静态注册的广播接收器即使app退出,只要有相应的广播发出,仍然可以接收到,但自Android 3.1开始有可能不再成立
- 对于系统广播,由于是系统内部直接发出,无法更改此intent flag值,系统默认直接增加了值为
FLAG_EXCLUDE_STOPPED_PACKAGES
的flag,因此,3.1开始对于静态注册的接收系统广播的BroadcastReceiver,如果App进程已经退出,将不能接收到广播。 - 对于自定义广播,可通过复写flag为
FLAG_INCLUDE_STOPPED_PACKAGES
,使得静态注册的BroadcastReceiver
,即使App退出,也能能接收到广播,并会启动应用进程,但此时的BroadcastReceiver是重新新建的。
实现代码为:
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
sendBroadcast(intent);