Android Broadcast Receive 攻略

761 阅读14分钟

攻略大全

1. 粘贴攻略

1.1 概述

定义:即 广播,是一个全局的监听器,属于Android四大组件之一。Android 广播分为两个角色:广播发送者、广播接收者。

作用:监听 / 接收 应用 App 发出的广播消息,并 做出响应。

应用场景:

  • Android不同组件间的通信(含 :应用内 / 不同应用之间)
  • 多线程通信
  • 与 Android 系统在特定情况下的通信(如:电话呼入时、网络可用时)

1.2 实现原理

1.2.1 采用的模型

Android中的广播使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型。因此,Android将广播的发送者和接收者解耦,使得系统方便集成,更易扩展。

1.2.1 模型讲解

模型中有3个角色:

  • 消息订阅者(广播接收者)
  • 消息发布者(广播发布者)
  • 消息中心(AMS,即Activity Manager Service)

示意图 & 原理如下: 944365-9fca9fd3978cef10.png

1.3 使用流程

944365-7c9ff656ebd1b981.png

1.3.1 自定义广播接收者BroadcastReceiver

  • 继承BroadcastReceivre基类
  • 必须复写抽象方法onReceive()方法

广播接收器接收到相应广播后,会自动回调 onReceive() 方法。

一般情况下,onReceive方法会涉及 与 其他组件之间的交互,如发送Notification、启动Service等。

默认情况下,广播接收器运行在 UI 线程,因此,onReceive()方法不能执行耗时操作,否则将导致ANR。

代码范例:

// 继承BroadcastReceivre基类
public class mBroadcastReceiver extends BroadcastReceiver {

// 复写onReceive()方法
// 接收到广播后,则自动调用该方法
@Override
public void onReceive(Context context, Intent intent) {
//写入接收广播后的操作
}

}

1.3.2 广播接收器注册

注册的方式分为两种:静态注册、动态注册

1.3.2.1 静态注册

  • 注册方式:在AndroidManifest.xml里通过标签声明

  • 属性说明: image.png

  • 注册示例:

//此广播接收者类是mBroadcastReceiver
<android:name=".mBroadcastReceiver" >
//用于接收网络状态改变时发出的广播
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>

当此App首次启动时,系统会自动实例化mBroadcastReceiver类,并注册到系统中。

1.3.2.2 动态注册

  • 注册方式:在代码中调用Context.registerReceiver()方法
  • 代码示例:
// 选择在Activity生命周期方法onResume()中注册
@Override
protected void onResume(){
super.onResume();
// 1. 实例化BroadcastReceiver子类 & IntentFilter
BroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();

// 2. 设置接收广播的类型
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);

// 3. 动态注册:调用Context的registerReceiver()方法
registerReceiver(mBroadcastReceiver, intentFilter);
}

// 注册广播后,要在相应位置记得销毁广播
// 即在onPause() 中unregisterReceiver(mBroadcastReceiver)
// 当此Activity实例化时,会动态将MyBroadcastReceiver注册到系统中
// 当此Activity销毁时,动态注册的MyBroadcastReceiver将不再接收到相应的广播。
@Override
protected void onPause() {
super.onPause();
//销毁在onResume()方法中的广播
unregisterReceiver(mBroadcastReceiver);
}

}

特别注意:

  • 动态广播最好在Activity 的 onResume()注册、onPause()注销。

  • 原因:

    1.对于动态广播,有注册就必然得有注销,否则会导致内存泄露。重复注册、重复注销也不允许。

    2.在onResume()注册、onPause()注销是因为onPause()在App死亡前一定会被执行,从而保证广播在App死亡前一定会被注销,进而防止内存泄露。

1.3.2.3 两种注册方式的区别

944365-8d163ab3ca20de0b.png

1.3.3 广播发送者向AMS发送广播

1.3.3.1 广播的发送

  • 广播 是 用”意图(Intent)“标识
  • 定义广播的本质 = 定义广播所具备的“意图(Intent)”
  • 广播发送 = 广播发送者 将此广播的“意图(Intent)”通过sendBroadcast()方法发送出去

1.3.3.2 广播的类型

  • 普通广播(Normal Broadcast)
  • 系统广播(System Broadcast)
  • 有序广播(Ordered Broadcast)
  • 粘性广播(Sticky Broadcast)
  • App应用内广播(Local Broadcast)

1. 普通广播(Normal Broadcast) 即 开发者自身定义 intent的广播(最常用)。发送广播使用如下:

Intent intent = new Intent();
//对应BroadcastReceiver中intentFilter的action
intent.setAction(BROADCAST_ACTION);
//发送广播
sendBroadcast(intent);

若发送广播有相应权限,那么广播接收者也需要相应权限

2. 系统广播(System Broadcast)

  • Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播
  • 每个广播都有特定的Intent - Filter(包括具体的action)

当使用系统广播时,只需要在注册广播接收者时定义相关的action即可,并不需要手动发送广播,当系统有相关操作时会自动进行系统广播。

3. 有序广播(Ordered Broadcast) 发送出去的广播被广播接收者按照先后顺序接收,有序是针对广播接收者而言的。

广播接受者接收广播的顺序规则(同时面向静态和动态注册的广播接受者):

  • 按照Priority属性值从大-小排序;
  • Priority属性相同者,动态注册的广播优先;

特点:

  • 接收广播按顺序接收
  • 先接收的广播接收者可以对广播进行截断,即后接收的广播接收者不再接收到此广播;
  • 先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播

有序广播的使用过程与普通广播非常类似,差异仅在于广播的发送方式: sendOrderedBroadcast(intent);

4. App应用内广播(Local Broadcast) Android中的广播可以跨App直接通信(exported对于有intent-filter情况下默认值为true),可能会出现以下问题:

  • 其他App针对性发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收广播并处理;
  • 其他App注册与当前App一致的intent-filter用于接收广播,获取广播具体信息;

即会出现安全性 & 效率性的问题。

解决方案:

  • 使用App应用内广播(Local Broadcast) App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高。

具体使用1 - 将全局广播设置成局部广播

  1. 注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收;
  2. 在广播发送和接收时,增设相应权限permission,用于权限验证;
  3. 发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。通过intent.setPackage(packageName)指定包名。

具体使用2 - 使用封装好的LocalBroadcastManager类

使用方式上与全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将参数的context变成了LocalBroadcastManager的单一实例。 注:对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册,不能静态注册。

//注册应用内广播接收器
//步骤1:实例化BroadcastReceiver子类 & IntentFilter mBroadcastReceiver
mBroadcastReceiver = new mBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();

//步骤2:实例化LocalBroadcastManager的实例
localBroadcastManager = LocalBroadcastManager.getInstance(this);

//步骤3:设置接收广播的类型
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);

//步骤4:调用LocalBroadcastManager单一实例的registerReceiver()方法进行动态注册
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);

//取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);

//发送应用内广播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent);

5. 粘性广播(Sticky Broadcast)

由于在Android5.0 & API 21中已经失效,所以不建议使用,在这里也不作过多的总结。

1.3.4 特别注意

1.3.4.1 关于OnReceive()中的context返回值

对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:

  • 对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
  • 对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context;
  • 对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context。
  • 对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context;

1.3.4.2 十秒限制

这个限制是指普通的广播如果onReceive方法执行时间太长,超过10秒的时候系统会将这个广播置为可以销毁的候选对象,一旦系统资源不够的时候,就会销毁这个广播从而让它无法执行。

因此:

  1. 广播接收者的生命周期是非常短暂的,在接收到广播的时候创建,onReceive()方法结束之后销毁。
  2. 广播接收者中不要做一些耗时的工作,否则会弹出Application No Response错误对话框。
  3. 最好也不要在广播接收者中创建子线程做耗时的工作,因为广播接收者被销毁后进程就成为了空进程,很容易被系统杀掉
  4. 耗时的较长的工作最好放在服务中完成。

1.3.4.3 LocalBroadcastManager

1.LBM的高效原因主要是因为它内部是通过Handler实现的,它的sendBroadcast()方法含义并非和我们平时用的一样,其实是通过handler发送一个Message实现的;

2.通过Handler实现广播的发送与系统通过binder实现对比显得更加高效,同时使用Handler实现,别的应用无法向我们的应用发送广播,而我们的应用内发送的广播也不会离开我们的应用;

3.LBM内部协作主要是靠mReceivers和mActions这两个Map集合,还有一个List集合mPendingBroadcasts,这个集合用于存储待接收的广播对象。

2. 造火箭攻略

2.1 流程简述

2.2 流程图示

注册过程时序图: image.png

3. 拧螺丝攻略

3.1 广播的注册过程

3.1.1 Contex.registerReceiver()

广播的注册分为两种,分别是静态注册和动态注册。

静态注册在应用安装时由PackageManagerService来完成注册过程。

广播的动态注册时序如下: image.png

registerReceiver方法在ContextWrapper中的实现代码如下所示: image.png mBase具体指向就是ContextImpl,ContextImpl的registerReceiver方法有很多重载的方法最终会调用registerReceiverInternal方法: image.png image.png

  • 在注释1处判断如果LoadedApk类型的mPackageInfo不等于null,并且context不等于null就调用注释2处的代码,通过mPackageInfo的getReceiverDispatcher方法获取rd对象,否则就调用注释3处的代码来创建rd对象。

  • 注释2和注释3处的代码的目的都是要获取IIntentReceiver类型的rd对象,IIntentReceiver是一个Binder接口,用于广播的跨进程的通信。

  • IIntentReceiver在LoadedApk.ReceiverDispatcher.InnerReceiver中实现,如下所示:image.png

  • 在注释4处调用了IActivityManager的registerReceiver方法,最终会调用AMS的registerReceiver方法,并将IIntentReceiver类型的rd传进去,这里之所以不直接传入BroadcastReceiver而是传入IIntentReceiver,是因为注册广播是一个跨进程过程,需要具有跨进程的通信功能的IIntentReceiver。

3.1.2 AMS.registerReceiver()

3.1.2.1 registerReceiver方法的part1

registerReceiver方法的part1如下所示: image.png image.png

  • 在注释1处通过getRecordForAppLocked方法得到ProcessRecord类型的callerApp对象,它用于描述请求AMS注册广播接收者的的应用程序进程。
  • 在注释2处根据传入的IntentFilter类型filter得到actions列表,根据actions列表和userIds(userIds可以理解为应用程序的uid)得到所有的粘性广播的intent,并在注释3处传入到stickyIntents中。
  • 接下来从stickyIntents中找到匹配传入的参数filter的粘性广播的intent。
  • 在注释4处将这些intent存入到allSticky列表中,从这里可以看出粘性广播是存储在AMS中的。

3.1.2.2 registerReceiver方法的part2

查看AMS的registerReceiver方法的剩余内容,如下所示: image.png image.png

  • 在注释1处获取ReceiverList列表,如果为空则在注释2处创建,ReceiverList继承自ArrayList,用来存储广播接收者。
  • 在注释3处创建BroadcastFilter并传入此前创建的ReceiverList,BroadcastFilter用来描述注册的广播接收者,并在注释4处通过add方法将自身添加到ReceiverList中。
  • 在注释5处将BroadcastFilter添加到IntentResolver类型的mReceiverResolver中,这样当AMS接收到广播时就可以从mReceiverResolver中找到对应的广播接收者了,从而达到了注册广播的目的。

3.2 广播的发送和接收过程

3.2.1 广播的发送:ContextImpl到AMS的调用过程

广播可以发送多种类型,包括无序广播(普通广播)、有序广播和粘性广播,这里以无序广播为例来讲解广播的发送过程。

要发送无序广播需要调用sendBroadcast方法,它同样在ContextWrapper中实现,ContextImpl到AMS的调用过程的时序图,如图所示: image.png

查看ContextWrapper的sendBroadcast方法,如下所示: image.png 接着来看ContextImpl中的sendBroadcast方法,如下所示:

image.png 又是熟悉的代码,最终会调用AMS的broadcastIntent方法:

image.png 我们先来查看注释1处的verifyBroadcastLocked方法:

image.png

image.png verifyBroadcastLocked 方法主要验证广播是否合法:

  • 在注释1处验证intent是否不为null并且有文件描述符。
  • 注释2处获得intent中的fag。
  • 注释3处如果系统正在启动过程中,判断如果flag设置为FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT(启动检查时只接受动态注册的广播接收者)则不做处理,如果不是则在注释4处判断如果flag没有设置为FLAG_RECEIVER_REGISTERED_ONLY(只接受动态注册的广播接收者)则会抛出异常。

我们再回到broadcastIntent方法,在注释2处调用了broadcastIntentLocked方法,代码如下所示: image.png 这里省略了很多代码,前面的工作主要是将动态注册的广播接收者和静态注册的广播接收者按照优先级高低不同存储在不同的列表中,再将这两个列表合并到receivers列表中,这样receivers列表包含了所有的广播接收者(无序广播和有序广播)。

  • 在注释1处创建BroadcastRecord对象并将receivers传进去.

  • 在注释2处调用BroadcastQueue的scheduleBroadcastsLocked方法。

3.2.2 AMS 到BroadcastReceiver的调用过程

AMS到BroadcastReceiver的调用过程的时序图如下所示。 image.png

BroadcastQueue的scheduleBroadcastsLocked方法的代码如下所示:

image.png image.png 在注释1处向BroadcastHandler类型的mHandler对象发送了BROADCAST_INTENT_MSG类型的消息,这个消息在BroadcastHandler的handleMessage方法中进行处理,如下所示:

image.png 在handleMessage方法中调用了processNextBroadcast方法,方法对无序广播和有序广播分别进行处理,旨在将广播发送给广播接收者,下面给出processNextBroadcast方法中对无序广播的处理部分:

image.png

image.png

  • 从前面BroadcastHandler方法中我们得知传入的参数fromMsg的值为true,因此在注释1处将mBroadcastsScheduled设置为fase,表示对于此前发来的BROADCAST_INTENT_MSG类型的消息已经处理了。

  • 注释2处的mParallelBroadcasts列表用来存储无序广播,通过while循环将mParallelBroadcasts 列表中的无序广播发送给对应的广播接收者。

  • 在注释3处获取每一个mParallelBroadcasts列表中存储的BroadcastRecord类型的r对象。

  • 在注释4处将这些r对象描述的广播发送给对应的广播接收者,deliverToRegisteredReceiverLocked方法如下所示:

image.png

image.png 这里省去了大部分的代码,这些代码是用来检查广播发送者和广播接收者的权限的。如果通过了权限的检查,则会调用注释1处的performReceiveLocked方法:

image.png

在注释1和注释2处的代码表示如果广播接收者所在的应用程序进程存在并且正在运行,则执行注释3处的代码,表示用广播接收者所在的应用程序进程来接收广播,这里app.thread指的是ApplicationThread,我们来查看ApplicationThread 的scheduleRegisteredReceiver 方法,代码如下所示:

image.png 在scheduleRegisteredReceiver 方法中调用了IIntentReceiver类型的对象receiver的performReceive方法,IIntentReceiver在前面提到过,用于广播的跨进程的通信,它的具体实现为LoadedApk.ReceiverDispatcher.InnerReceiver,代码如下所示:

image.png IIntentReceiver和IActivityManager 一样,都使用了AIDL来实现进程间通信。InnerReceiver继承自IIntentReceiver.Stub,是Binder通信的服务器端,IIntentReceiver则是Binder 通信的客户端、InnerReceiver 在本地的代理,它的具体的实现就是InnerReceiver。在InnerReceiver的performReceive方法的注释1处调用了ReceiverDispatcher类型的rd对象的performReceive方法,如下所示:

image.png 在注释1处将广播的intent等信息封装为Args对象,在注释2处调用mActivityThread的post方法并传入了Args对象。这个mActivityThread是一个Handler对象,具体指向的就是H,注释2处的代码就是将Args对象的getRunnable方法通过H发送到线程的消息队列中,Args的getRunnable方法如下所示:

image.png

image.png 在注释1处执行了BroadcastReceiver类型的receiver对象的onReceive方法,这样注册的广播接收者就收到了广播并得到了intent。

4. 复制攻略

4.1 《Android开发艺术探索》

4.2 《Andriod进阶解密》