Broadcast Receiver 基础
Broadcast Receiver 是四大组件之一,可以用来接受系统或者app(可以app 内部组件,也可以是跨 app)的各种事件,当然这些事件必须通过 sendBroadcast()方法发送出来,Broadcast Receiver 才可以接受到。
广播可以作为组件之间,跨进程,跨应用之间的通信,更多的时候,是配合系统的内置 Broadcast 对我们的 APP 需要的手机转态进行管理,例如 wifi 状态。
概述
注册方式
你可以通过两种方式注册一个 Broadcast Receiver,一种是在AndroidMainFest 中注册,称为静态注册,注册方式如下:
<receiver android:name="br.NetWorkChangeToastReceiver"></receiver>
另一种是动态注册,通过代码注册,调用 Context.registerReceiver() 方法去注册
private void registerBroadcastReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(new NetWorkChangeToastReceiver(), intentFilter);
}
两种方式存在一定的区别,通过代码动态注册的,具有组件级别效果,也就是说,在 Broadcast 未调用 onReceive()之前,如果通过unRegisterReceive()方法被调用,则该 Receiver 被销毁;
注意:
在 onReceive() 是执行在主线程中的,所以不能在 onReceive()里面做耗时操作,否则会引起 anr。
在同等情况下,动态注册的广播接收者会比静态注册的广播接收者优先级高,会先收到 broadcast
生命周期解释
通过 AndroidMainFest 文件注册的 Receiver,在 APP 安装的时候,会通过 package manage 全部注册;从这个角度来说,这些 Receiver 和 APP 是相对独立的,即便你的 APP 没有启动,这些 Receiver 也可以在 onReeiver() 接受特定的事件。
通过 Context 注册的 Broadcast Receiver 的生命周期和注册它的 Context 是息息相关的,在Context 没有被销毁的情况下,这个 Receiver 的onReceive()方法才会被调用 。从另外一点来说,如果你是通过 Activity 去 register(),你需要在退出这个 Activity 之前 unRegister() 这个 Activity(),同时注意调用方法的时刻,如果在 onCreate() register,则需要在 onDestory() 的时候 unRegister():如果是 onResume() 的时候 register(),则需要在 onPasue() 的实现 unRegister();避免多次调用 register() 和 unRegister() 方法。
下面是一个例子,进行跨 APP 发送和接受广播
首先在接收端 APP 实现自定义的 Broadcast Receiver,自定义的 BroadcastReceiver 类文件如下,这里接受对应 Action 的Broadcast Receiver 然后Toast 接收到的内容:
public class AppCrossBroadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TextUtils.equals(action, "com.fx.app.cross.br1")) {
String data = intent.getStringExtra("data");
if (data != null) {
Toast.makeText(context, "收到" + data, Toast.LENGTH_SHORT).show();
}
}
}
}
然后在接收端静态注册这个 Broadcast Receiver,在 androdmainfest 文件中:
<receiver
android:name="common.AppCrossBroadcast"
android:exported="true">
<intent-filter>
<action android:name="com.fx.app.cross.br1"></action>
</intent-filter>
</receiver>
这里 android:exported=”true” 这个属性设置为 true 表示,这个Application 组件是否可以被其它应用访问(调用);
基础用法及语义
有序广播和无序(标准)广播
通过 sendBroadcast(Intent ) 方法发送的广播,称为标准无序广播,相对下面介绍的一种广播,效率要高之。但是意味着所有匹配 Action 的 Reveiver 都可以接收。
我们可以通过 sendOrderBroadcast(Intent,String),发送有序广播,这种广播根据 Receiver 的优先级,被 Receiver 顺序接收和处理,这里意味着,你可以拦截 Broadcast(通过调用),你可以在 Receiver 之间传递数据。
下面是一个例子,首先定义 Broadcast Receiver:
public class OrderBr1 extends BroadcastReceiver {
public final String TAG = this.getClass().getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TextUtils.equals(action, "com.fx.order")) {
Toast.makeText(context, TAG + "收到广播", Toast.LENGTH_SHORT).show();
}
}
}
然后依次复制,生成 OrderBr2,OrderBr3。代码都是一样的,不过在 OdderBr2 里面,进行广播的拦截,代码如下:
public class OrderBr2 extends BroadcastReceiver {
public final String TAG = this.getClass().getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TextUtils.equals(action, "com.fx.order")) {
Toast.makeText(context, TAG + "收到广播", Toast.LENGTH_SHORT).show();
}
abortBroadcast();// 拦截广播
}
}
然后在 AndroidMainFest 中 receiver 标签的子标签 intent-filter 标签里面,修改 android:priority 属性。
<receiver android:name="br.OrderBr1">
<intent-filter android:priority="100">
<action android:name="com.fx.order"></action>
</intent-filter>
</receiver>
<receiver android:name="br.OrderBr2">
<intent-filter android:priority="99">
<action android:name="com.fx.order"></action>
</intent-filter>
</receiver>
<receiver android:name="br.OrderBr3">
<intent-filter android:priority="98">
<action android:name="com.fx.order"></action>
</intent-filter>
</receiver>
最后,我们在我们的 Activity 里面发送 Broadcast,代码如下:
private void sendOrderBr() {
Intent intent = new Intent();
intent.setAction("com.fx.order");
sendOrderedBroadcast(intent, null);
}
我们可以看到,OrderBr3 是没有接收到广播的。
使用 permission
sendOrderedBroadcast(intent, null)方法里面,第二个参数是一个 permission,这很好理解,我们的应用在使用某些功能或者访问硬件的时候,需要一些 uses-permission 例如下面这个是访问网络状态的 uses-permission。
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
同样 BroadcastReceiver 也可以对 Receiver 所以的应用 进行 permission 过滤,这里的过滤包括发送方和接收方,对发送方过滤,意味着,如果你的应用没有在 androidmanifest 中声明对应的 例如我们发送一条这样的广播,
sendOrderedBroadcast(intent, Manifest.permission.SEND_SMS);
那么只有在 AndroidManiFest 文件中,声明了发送信息的权限,你的 app 才能准确的发送这条广播,同样的道理,想要接收这个 Broadcast 的 receiver 也需要
这里可能比较难理解,我先讲解一下,我们调用这个方法,最后会调用 ContextImpl 类里面的 sendOrderedBroadcast()方法:
@Override
public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
String[] receiverPermissions = receiverPermission == null ? null
: new String[] {receiverPermission};
try {
intent.prepareToLeaveProcess(this);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, receiverPermissions, AppOpsManager.OP_NONE,
null, true, false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
之后会把需要的 permission 写进 Parcel 里面,然后发送出去;那么接收 Broadcast 的代码呢?接收和发送 Broadcast 都是由一个叫做 ActivityManagerService 的类进行代理的,你通过 Activity 或者 Service 发出出去的 Broadcast 都有 ActivityManagerService 进行接收和分发,然后 ActivityManagerService 根据注册的 permission 和优先级,分发 Broadcast。
tip:你也可在 receiver 标签里面,设置 android:permission 属性,对
Local Broadcast
本地广播,在 app 内通信; google 建议在不需要 IPC(跨进程通信)的前提下,使用 Local Broadcast 效率比普通Broadcast 要高。
android 可以通过 support 包轻松简单的发送 local broadcast,先来看下 LocalBroadcastManager 类的介绍和用法:
LocalBroadcastManager 在v4 包里面,根据 api 的介绍“这是一个帮助类,帮助你在你的进程内注册和发送 broadcast”,然后使用 Local Broadcast 有以下几个好处:
- 可以避免广播被其它app 接受到,避免信息泄漏
- 可以避免自己的 Receiver 接收到破坏性的 Broadcast
- 不用进行 ipc,效率远远高于普通的 global Broadcast。
这个类,注册和处理相关的 Broadcast,它和 ActivityManagerService 是独立的,也就是你通过 LocalBroadcastManager 发送 Broadcast 和注册 receiver 是不会通过 ActivityManagerService 的,下面是这个类处理 broadcast 的流程图:
注册的时候, 其实就是将 Receiver 加入到两个HashMap 中,其中一个 mReceivers,记录了某个Broadcast 对应的所有 IntentFilter,这个 HashMap 主要作为锁对象,确保所有的 Receiver 在线程中同步。另一个 HashMap 才是起数据存储的作用,把 action 作为 key,IntentFilter 和 Receiver 值生成的复合对象作为 value 值。
然后发送广播的时候,大概流程如下:
发送广播的时候,会根据Intent 的action 去查找是否有对应的 Receiver,然后匹配 action,scheme,category 等,如果匹配成功,则将对应的 Receiver 加入 一个 ArrayList,同时 Handler 发送消息,通知处理发送的广播,通过遍历 ArrayList,执行符合条件的 Receiver 的onReceive()方法。
具体详细可以参考源码。
goAsync()方法
我们知道,一个 Receiver 如果执行完 onReceive() 方法,则这个组件会被系统回收。如果你在 onReceive()方法里面执行异步操作,很可能因为这个 Receiver 已经被收回了,导致业务出错甚至app直接崩溃。android 在 Broadcast 类里面 提供了 goAsync()方法,提供我们在其它子线程进行耗时操作,避免引起 anr 问题。
public class AsyncBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final PendingResult pendingResult = goAsync();
Thread thread = new Thread(new SleepThread(1000 * 20) {
@Override
public void run() {
super.run();
Log.i("Async", " ->finish");
pendingResult.finish();
}
});
thread.start();
Log.i("Async", " ->onReceive");
}
}
这样需要进行耗时操作就不会引起 anr 问题了。
Sticky Broadcast- 粘性广播
普通发送的广播都是非粘性的,根据 google 官方 api 的解释,所谓粘性广播,定义如下:
Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the Intent you are sending stays around after the broadcast is complete,so that others can quickly retrieve that data through the return value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In all other ways, this behaves the same as{@link #sendBroadcast(Intent)}.
简单来说,这条广播会一直停留在系统里面,所以只要有新的 receiver 注册,马上便可以收到这条广播。不过,你可以通过 removeStickyBroadcast() 移除这条广播。发送粘性广播,需要特定的 user-permission,如下:
<uses-permission android:name="android.permission.BROADCAST_STICKY"></uses-permission>
但是在 api 21 以后,也就是 android 5.0 以后, 出于 安全原因,sticky broadcast 被废弃,不再使用。