在 Android 中,广播(Broadcast) 是用来在组件之间传递数据的一种机制。这些组件可以在同一进程中,也可以在不同进程中。
Android中广播分为两个角色:广播发送者/广播接收者
监听APP/系统发出的广播消息,并做出响应。
这里简单说下广播的原理。
1、采用的原型
Android 中的广播采用的是设计模式中的观察者模式:基于消息的发布/订阅时间模型。Android 将广播的发送者和接收者进行解耦,使得系统更方便集成,更容易扩展。
2、原理(图片去自网络,如果侵权请联系本人删除)
BroadcastReceiver是对发送出来的符合过滤条件的Broadcast进行接收和响应的组件,是 Android 四大组件之一。
首先要将需要发送的消息和用于过滤接收者的信息(Action、Category)装入到一个 Intent 对象中,然后通过调用 Context.sendBroadcast或者 Context.sendOrderBroadcast 把 Intent 通过广播形式发送出去。广播发送出去后 AMS 会检查已注册的 BroadcastReceiver 的 IntentFilter 是否满足当前的Intent 的过滤条件,如果满足,则调用 BroadcastReceiver 的 onReceive 方法。默认情况下 BroadcastReceiver是运行在 UI 线程中,所以onReceive 方法中不能执行耗时的操作,否则会 ANR。一旦 onReceive 方法执行完毕,该 Receiver 的生命周期也就结束了。
Android 中的广播分为两种:
标准广播
标准广播是一种完全异步执行的广播,在广播发出去之后,所有广播接收者会同时接收到广播,没有顺序而言,效率比较高,且不会被截断。
有序广播
有序广播是一种同步执行的广播,在广播发出的同一时刻只有一个广播接收者可以收到广播,优先级高的接收者先接收到广播,等该接收者的 onReceive方法执行完毕之后,广播才会继续往优先级低的广播接收者传递。如果当前的广播接收者截断了此广播,那么优先级低的广播接收者就无法接收到广播了。另外,优先级高的广播接收者可以修改当前广播的内容,后面接收者接收到的广播就是修改过后的内容了。
1、静态注册
标签的 process 可以设置成和所有组件都不同的默认名,但是这些组件可以通过自己设置process 来覆盖这个默认值,这样就可以将一个应用跨多进程运行。
//如果这个属性以冒号开头,那么这个进程是应用程序私有的,当他被需要时系统会创建一个新的进程,并且该 接收器就运行在该进程中。如果不是以小写字符开头的,那么该接收器就运行在这个名称命名的全局的进程中,并且他提供使其工作的权限,这样就可以在不同应用中共享一个工作进程,减少内存消耗。
android:process="string" >
//用于指定接收器的过滤器,也就是接收哪种类型的广播
1)、标准广播静态注册
首先定义一个 BroadcastReceiver
public class NormalReceiver extends BroadcastReceiver {
private static String TAG = "NormalReceiver";
public NormalReceiver() {
Log.e(TAG, "NormalReceiver Constructor");
}
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("MSG");
Log.e(TAG, msg);
}
}
再在 mainfest 中进行静态注册
接着进行调用了
Intent intent = new Intent();
intent.setAction(NORMAL_ACTION);
intent.putExtra("MSG", "value1");
sendBroadcast(intent);
接下来就是满怀期待的等待触发了。
但是我点了无数次发现并没有打印出 log,也就是说接收器没有收到广播,那这是怎么回事呢。这个问题曾经也困扰过我一段时间,最后发现文档中有这样一段
https://developer.android.com/guide/components/broadcasts.html#receiving_broadcasts
Beginning with Android 8.0 (API level 26), the system imposes additional restrictions on manifest-declared receivers.
If your app targets API level 26 or higher, you cannot use the manifest to declare a receiver for most implicit broadcasts (broadcasts that do not target your app specifically).
也就是说
在Android 8.0 及以上 在xml中注册的广播,在接收的时候收到了额外的限制,如果你的app目标等级是26及以上,将无法接收到xml注册的广播
到 gradle 里面一看,果然 targetSdkVersion 是27,我现在改成25看下能不能触发。
果然触发了 onReceive 方法。
那么该怎么解决呢,主要有三种解决方式:
1、显示调用BroadcastReceiver,但是这样的话 intent-filter 属性就没有用了。
Intent intent = new Intent(this, NormalReceiver.class);
intent.putExtra("MSG", "value1");
sendBroadcast(intent);
2、动态注册广播(后面会介绍)
3、将 gradle 中的 targetSdkVersion 设置小于26
我们接下来就先将targetSdkVersion设置成25方便介绍静态注册。
2、有序广播
首先我们定义三个有序广播 OrderReceiver1、OrderReceiver2和OrderReceiver3,优先级依次降低,在 OrderReceiver1中我们对数据进行修改,在 OrderReceiver2中对广播进行拦截,来看下打印的结果。
OrderReceiver1关键代码
public class OrderReceiver1 extends BroadcastReceiver {
private static final String TAG = "OrderReceiver1";
@Override
public void onReceive(Context context, Intent intent) {
//取出intent 中的数据
String msg = intent.getStringExtra("MSG");
Log.e(TAG, msg);
//对数据进行修改传递给下面优先级低的receiver
Bundle bundle = new Bundle();
bundle.putString("MSG", msg + " OrderReceiver1 changed value");
setResultExtras(bundle);
}
}
OrderReceiver2关键代码
public class OrderReceiver2 extends BroadcastReceiver {
private static final String TAG = "OrderReceiver2";
@Override
public void onReceive(Context context, Intent intent) {
//原始数据
String msg = intent.getStringExtra("MSG");
Log.e(TAG, msg);
//上个 receiver 修改的数据
String msg2 = getResultExtras(true).getString("MSG");
Log.e(TAG, msg2);
//拦截广播
abortBroadcast();
}
}
OrderReceiver3关键代码
public class OrderReceiver3 extends BroadcastReceiver {
private static final String TAG = "OrderReceiver3";
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "test value");
}
}
manifest中进行注册
//设置优先级,数字越大优先级越高
//设置优先级
//设置优先级
发送广播
Intent intent = new Intent();
intent.setAction("broadcast.OrderReceiver");
intent.putExtra("MSG", "value1");
//调用sendOrderedBroadcast,后面一个参数值在自定义权限时使用
sendOrderedBroadcast(intent, null);
我们看到最终打印的结果为
说明有序广播可以对广播内容进行修改(或者用补充更为恰当)和拦截。
动态注册是在代码中定义并设置好IntentFilter 对象,然后在需要注册的地方调用Context.registerReceiver(),在需要解除注册的时候调用Context.unregisterReceiver()f 方法进行解除。而不需要在manifest 中去注册。
public class DynamicBroadcastActivity extends AppCompatActivity {
private BroadcastReceiver normalReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dynamic_broadcast);
registerReceiver();
findViewById(R.id.btn_normal_dynamic).setOnClickListener(view -> {
Intent intent = new Intent("Dynamic_Normal_Receiver");
intent.putExtra("MSG", "value");
sendBroadcast(intent);
});
}
private void registerReceiver() {
normalReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("MSG");
Log.e("Dynamic Receiver", msg);
}
};
IntentFilter intentFilter = new IntentFilter("Dynamic_Normal_Receiver");
registerReceiver(normalReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(normalReceiver);
}
}
打印结果
也可以通过动态注册有序广播,在注册的时候intentFilter.setPriority()方法设置优先级就行了,这里就不单独介绍了。
之前我们说到 exported 属性在至少有一个intent-filter的情况下默认是 ture。也就是说这些广播都是全局的系统广播,那么就可能导致以下两个问题。
1、如果其它应用发出的广播和当前的应用的 intent-filter相匹配,那么当前应用就可以接收此广播,有可能会导致当前应用不断的收到其它应用发出的广播导致应用出现异常。
2、如果其它应用注册的Receiver的 intent-filter 与当前应用发出的广播相匹配,就可能会获取到广播的信息,从而造成安全性和效率问题。
那么就可以使用本地广播(或者叫应用内广播),使用这个机制发出的广播只能在应用内进行传递。本地广播可以理解成局部广播,广播的发送者和接收者都是同一个应用内的。
具体做法几种:
1、注册广播时将 exported属性设置成 false,这样就只能接收本应用内的广播。
2、在发送和接收广播时增加相应的permission,用于权限验证。后面会介绍。
3、发送广播时时指定Receiver 所在的包名intent.setPackage(packageName)。例如上面的发送广播的代码改成这样
Intent intent = new Intent("Dynamic_Normal_Receiver");
intent.setPackage("aaa.bbb");//设置一个与广播所在包名不一致的包名
intent.putExtra("MSG", "value");
sendBroadcast(intent);
会发现之前的 Receiver 无法接收到广播,即使 intent-filter 匹配。改成manifest 中 package 一致的包名就可以正常触发了。
4、使用封装好的LocalBroadcastManager来对广播进行管理
使用LocalBroadcastManager 发送的广播只能通过 LocalBroadcastManager 进行动态注册,不能静态注册。
下面我们简单来通过一个例子来简单看下用法。
首先我们定义一个 Receiver:
public class LocalBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "LocalBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("MSG");
Log.e(TAG, msg);
}
}
再建个 Activity
public class LocalBroadcastActivity extends AppCompatActivity {
private static final String ACTION = "com.test.local.broadcast";
LocalBroadcastReceiver localBroadcastReceiver;
LocalBroadcastManager localBroadcastManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_local_broadcast);
//实例化LocalBroadcastManager的实例
localBroadcastManager = LocalBroadcastManager.getInstance(this);
registerLocalBroadcastReceiver();
findViewById(R.id.btn_local).setOnClickListener(view -> {
triggerBroadcast();
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unRegisterLocalBroadcastReceiver();
}
private void triggerBroadcast() {
Intent intent = new Intent(ACTION);
intent.putExtra("MSG", "value");
localBroadcastManager.sendBroadcast(intent);
}
private void registerLocalBroadcastReceiver() {
localBroadcastReceiver = new LocalBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter(ACTION);
localBroadcastManager.registerReceiver(localBroadcastReceiver, intentFilter);
}
private void unRegisterLocalBroadcastReceiver() {
if (localBroadcastReceiver != null && localBroadcastManager != null) {
localBroadcastManager.unregisterReceiver(localBroadcastReceiver);
}
}
}
代码比较简单,废话就不多说了。
前面我们说过可以通过 permission 来控制广播接收器是否响应外部广播调用。这节我们就来具体说下这个用法。
首先我们定义一个 BroadcastReceiver:
public class PermissionReceiver extends BroadcastReceiver {
private static final String TAG = "PermissionReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("MSG");
Log.e(TAG, msg);
}
}
然后自定义一个权限:
//自定义权限
//申请使用权限
动态注册(静态注册也可以)刚才的 Receiver:
接下来我们新建一个 APP 工程:
然后申请刚才的权限
接下来发送广播:
Intent intent = new Intent("com.permission.example.receiver");
intent.putExtra("MSG", "New APP Value");
//加上权限
sendBroadcast(intent, "com.permission.example.receiver");
最后我们会在日志中看到打印的结果
permission 控制广播就简单介绍到者,关于 permission 的具体用法,以后有时间会单开一篇进行探讨。