一文彻底搞懂Android广播的所有知识 (上)--四大组件系统

266 阅读33分钟

戳蓝字“牛晓伟”关注我哦!

用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章,技术文章也可以有温度。

本文摘要

从本文开始介绍广播相关的内容,本文主要介绍广播机制,广播机制的原理,广播的分类,为啥要有超时机制,为啥要有延迟机制,无序广播的发送接收流程,有序广播的发送接收流程,关于广播的所有知识都可以在本文找到。

注:AMS是ActivityManagerService的简称

本文大纲

本文大纲

本文大纲

1. 广播机制

大家好,我是广播机制,广播机制可以理解为全局的事件通知机制,用大白话讲就是所有的App进程之间或者App进程内都可以通过该机制把事件发送给事件的监听者,这里的事件监听者就是鼎鼎有名的BroadcastReceiver组件,事件的监听者可以有多个。该机制的好处是解除了代码之间的耦合性提升开发效率。BroadcastReceiver其实不需要关心事件是由哪个进程发出来的,它只需要注册自己及对应的广播即可,至于何时有广播它也不需要关心,只需要被动接收广播数据即可,这样大大的降低了代码之间的耦合性。

为了对广播有一个更直观的感受,我特意写了一段伪代码:

//如下代码注册一个广播接收者
IntentFilter intentFilter = new IntentFilter("com.niu.broadcast.demo");
registerReceiver(new BroadcastReceiver(),intentFilter);

private BroadcastReceiver br = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            接收到相应广播并且进行处理的代码······
        }
    };

//如下代码发送 com.niu.broadcast.demo 类型的广播,上面的BroadcastReceiver对象就会收到该广播信息
Intent intent = new Intent("com.niu.broadcast.demo");
sendOrderedBroadcast(intent,null);

上面伪代码,展示了一个广播接收者注册了对应的广播后,发送者发送该广播后,广播接收者就会收到该广播信息。上面的代码是不是简单到令人发指的地步啊。大家肯定能猜出来这肯定是我广播机制作为底层做了很多很多的事情,那就跟随我一步一步来揭开底层的神秘面纱吧。

2. 广播机制原理

在介绍广播机制原理之前,我同样延续以前的由浅入深的老传统,先从最核心的“内核”开始,只有先把机制的“内核”搞清楚,才能对机制有一个更深入的理解。

2.1 “内核”

广播机制的“内核”就是设计模式中的观察者模式,观察者模式又被称为发布--订阅模式,不管是发布--订阅模式还是观察者模式在现实生活中都可以找到相对应的场景,比如微信公众号中关注/订阅了某个公众号,而这个公众号只要发布了消息,订阅者就会收到该消息,当然订阅者可以有很多。我特意绘制了一幅图来展示该设计模式:

观察者模式

观察者模式

图解

Subject的作用是持有很多的Observer,发布者发布消息需要通过它来发布。

Observer的作用就是把自己注册在Subject中,这样当有消息时就能够收到对应消息了,就犹如公众号的订阅者,它接收消息的过程是一个被动的过程,它不关心啥时候来消息,有消息Subject会通知它,没消息“傻傻等着”。

发布者的作用就是发布消息,发布的消息需要经过Subject传递给所有的Observer。

介绍了广播机制的“内核”,那我就来介绍下广播机制的原理。

2.2 原理

还是先请大家来看一幅图,该图展示了广播机制的原理:

原理

原理

图解

因为我广播机制是一个全局的事件通知机制,每个App进程都可以存在一个或者多个广播接收者 (BroadcastReceiver),而广播接收者就是观察者模式中的Observer,只不过广播接收者是存在于各个App进程内。

广播分发中心就是观察者模式中的Subject的作用,但是广播分发中心的功能可是比Subject复杂的多的多的多了。正如它的名字它会把广播分发给广播接收者,广播分发中心是位于systemserver进程,广播分发中心是由AMSBroadcastQueueBroadcastDispatcher等几个关键类组成的。

广播发送者就是观察者模式中的发布者的作用,同样广播发送者可以位于App进程,也可以位于systemserver进程,广播发送者的作用就是发送广播。

上图只是展示了广播机制的关键参与方,以及发送和接收广播的流程。广播机制可是有很多复杂的事情需要考虑比如广播接收者、广播分发中心、广播发送者一般都是位于不同的进程,那这时候需要使用binder通信;又比如广播分发中心是如何鉴别广播接收者的;Android系统每时每刻都会有广播的发送,那该如何保证广播消息的即时响应;发送的广播各种各样,并且广播接收者也多种多样,那如何定义一套统一的接口或机制保证可以在所有发送者和接收者上都能通用等等各种事情。那我就从广播接收者广播发送者广播分发中心再来揭开广播机制的原理吧。

3. 广播接收者

广播接收者就是BroadcastReceiver,只要作为一个广播接收者就需要自定义自己的类,这个类需要继承BroadcastReceiver,重写BroadcastReceiver的onReceive方法,在该方法中处理收到的广播信息。

而广播接收者是被划分为动态广播接收者静态广播接收者两种,那我就来介绍下这两种。

3.1 动态广播接收者

动态广播接收者就是指该BroadcastReceiver是需要通过代码的方式进行注册和注销,它的生命周期与所在的组件相同,比如在Activity中注册了该BroadcastReceiver,在Activity销毁的时候就需要注销该BroadcastReceiver。它的特点是灵活。

既然谈到了注册和注销,就像观察者模式中的Observer一样,要想接收消息就需要在Subject中注册自己,而动态广播接收者同样也需要进行注册的流程,那就来谈谈注册。

3.1.1 注册

先看一段注册动态广播接收者的代码:

//"com.niu.broadcast.demo"代表BroadcastReceiver关心的广播
IntentFilter intentFilter = new IntentFilter("com.niu.broadcast.demo");
//XXXBroadcastReceiver代表一个广播接收者类
registerReceiver(new XXXBroadcastReceiver(),intentFilter);

如上代码注册一个BroadcastReceiver是不是很简单,每一个广播都对应自己的action (也就是一个字符串),注册一个BroadcastReceiver就是调用Context对象的registerReceiver方法把它和它关心的广播作为参数即可。

但是你可别被上面的代码欺骗了,真正注册的可不是BroadcastReceiver对象,那就听我来细细介绍注册,要想把BroadcastReceiver的注册讲明白,那需要先把注册到哪注册谁如何注册这几件事情讲明白。

注册到哪

在上面介绍广播机制的原理图中提到过,广播接收者BroadcastReceiver一般都是位于App进程内的,而广播分发中心是位于systemserver进程的,而广播分发中心的首要作用就是收集所有的“BroadcastReceiver” ,因此就是把“BroadcastReceiver”注册到分发到广播分发中心,不知道细心的你们有没有发现BroadcastReceiver可是加了引号,加引号就是代表并不是BroadcastReceiver对象被注册到广播分发中心,那到底是注册谁呢?

注册谁

广播分发中心对于注册的对象可是有两个要求的,这个要求就是具有反向查找的能力唯一性。先来解释下反向查找,说人话就是该对象再次被传递到App进程后可以通过该对象找到对应的BroadcastReceiver对象。而唯一性一个App进程内多次注册同一对象,在广播分发中心依然知道注册的是同一对象

而App进程内的BroadcastReceiver对象,它可是不具备以上两点的,因为它的类型只是一个普通的类而已,而“谁”满足以上两个要求呢?

答案就是Binder对象,还记得在Activity与ActivityRecord“互认”这篇文章中介绍过ActivityRecord与Activity“互认”是使用了Token对象,而Token类继承了Binder类。既然知道了需要注册一个Binder对象,那就来看下是该Binder对象是什么类型的,请看如下代码:(关于为啥是Binder对象可以看Activity与ActivityRecord“互认”此文)

//下面类位于LoadedApk.java
//InnerReceiver类继承了IIntentReceiver.Stub,而IIntentReceiver.Stub继承了Binder类
final static class InnerReceiver extends IIntentReceiver.Stub {
    
    //该方法会进行广播的分发
    public void performReceive(Intent intent, int resultCode, String data,
                    Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
       省略代码······             
    }
}

//IIntentReceiver.Stub是一个抽象类继承了Binder类并且实现了IIntentReceiver接口
abstract class Stub extends android.os.Binder implements android.content.IIntentReceiver{

   @Override public void performReceive(android.content.Intent intent, int resultCode, java.lang.String data, android.os.Bundle extras, boolean ordered, boolean sticky, int sendingUser) throws android.os.RemoteException
    {
    }
}

如上代码,InnerReceiver类继承了Binder类,该类是可以间接的找到BroadcastReceiver对象的,同时该类拥有performReceive方法,该方法会把广播消息分发给BroadcastReceiver对象。也就是InnerReceiver对象是一个匿名的binder服务它会被注册到广播分发中心,而当有广播消息时,InnerReceiver对象会把消息间接的分发到BroadcastReceiver对象

如何注册

如何注册就是调用ActivityManager的registerReceiverWithFeature方法,而该方法是一个binder调用,最终会调到AMS的相应方法,关于如何注册会在下面还会详细介绍。

小结

动态BroadcastReceiver要想接收相应的广播消息,必须先进行注册,调用Context的registerReceiver方法进行注册时,注册时最重要的参数是传递自定义BroadcastReceiver对象相应的广播信息 (广播信息放入IntentFilter对象),当然有一些广播还需要权限。表面上看着是在注册自定义的BroadcastReceiver对象,但其实注册的是InnerReceiver对象。而InnerReceiver对象相应的广播等信息是会注册到广播分发中心的。

3.2 静态广播接收者

静态广播接受者就是指该BroadcastReceiver是只在AndroidManifest文件中声明即可,如下例子:

     <receiver android:name=".MyReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="com.niu.broadcast.demo"/>
            </intent-filter>
      </receiver>

静态广播接收者没有动态广播接收者灵活,但是它也有它自己的优势,那就是不需要App进程“活”着,啥意思呢?就是说静态广播接收者即使对应的App进程没有存活,也依然会被系统拉活,进而接收广播消息。而动态广播接收者对应的App进程必须存活。同时也正因为此特性,并不是所有的App都可以使用此特性,因为如果使用了此特性就可以悄悄的把死掉的App进程拉活,因此一般只有系统App才可以使用此特性。

3.3 小结

广播接收者就是一个BroadcastReceiver类,而广播接收者又可以分为动态广播接收者静态广播接收者,不管是什么类型的广播接收者,要想收到广播,就需要注册自己感兴趣的广播,当然可以注册多个广播 ,而每一个广播都有对应的action,而这个action就是一个字符串,它当作广播的key值,因此注册多个广播就是把广播的action存放在IntentFilter对象中或者配置在intent-filter标签中。

4. 广播发送者

广播有接收者,那自然也有发送者,发送一个广播非常简单,如下例子:

//如下代码发送action为com.niu.broadcast.demo 类型的广播
Intent intent = new Intent("com.niu.broadcast.demo");
//下面代码为intent增加参数
intent.putExtra("xxxx","xxxxxx");
sendBroadcast(intent);

如上代码,发送一个action为com.niu.broadcast.demo的广播,并且还携带了参数。不管是广播对应的action信息还是参数都存放在Intent对象中,而Intent对象会通过binder通信到达AMS,而AMS开始执行广播分发的过程,在下面会介绍广播分发的过程。

其实广播发送者发送的广播是可以按是否有序按是否是系统按是否是后台进行划分的,那我就依据以上划分类来介绍下不同类型的广播。

4.1 按是否有序划分

广播按是否有序划分为有序广播无序广播,为了展示它俩的区别,我特意绘制了一幅图:

image

image

图解

左图展示的是有序广播的发送流程,广播分发中心会先把广播发送给第一个接收者,第一个接收者处理完毕后会给广播分发中心一个反馈;接着把广播发送给第二个接收者,同样第二个接收者处理完毕也会给广播分发中心一个反馈。依照此顺序直达发完为止。

有图展示的是无序广播的发送流程,广播分发中心会把广播分发给所有的接收者,而不需要等待接收者的反馈。

由上可知,广播发送者发送的有序广播无序广播的区别在于广播接收者接收广播的顺序,前者是一个串行的概念,广播接收者需要一个一个的接收,只有前一个接收者处理完毕后,才会把广播发送给下一个接收者;而后者是一个并行的概念,广播接收者可以基本同时接收广播。

正因为有序广播是按照串行来发送广播给接收者的,因此会出现一个问题就是一个接收者处理广播时间耗时,那就会影响后面的接收者,因此针对有序广播增加了超时延迟等机制,后面会介绍到。而无需广播不需要这些机制。

广播发送者可以调用Context的sendBroadcastsendOrderedBroadcast方法来发送无序广播和有序广播。

4.2 按是否是系统划分

广播按是否是系统划分为系统广播普通广播。而该划分是针对广播发送者的,

系统广播就是只有系统的一些App或者systemserver进程才能发送的广播,比如android.intent.action.SCREEN_ON、android.intent.action.SCREEN_OFF等。

普通广播就是普通App发送的广播,普通App可是不能发送系统定义的广播,否则会抛异常。

4.3 按是否是后台划分

广播按是否是后台划分为后台广播前台广播,这里的前台、后台可不是大家想的App处于前后台的概念,这里的前台、后台指的是时间。

前台广播一个广播接收者处理广播的时间最多10s,否则按超时处理。后台广播一个广播接收者处理广播的时间是60s,否则同样按超时处理。

上面介绍过只有有序广播超时机制,可以认为前台广播和后台广播是针对有序广播的再次划分。

4.4 小结

广播根据以下进行划分:

  1. 按是否有序划分为:有序广播和无序广播
  2. 按是否是系统划分为:系统广播和普通广播
  3. 按是否是后台划分为:后台广播和前台广播,不管是后台广播还是前台广播都是针对有序广播的再次划分,不会存在无序的前台或后台广播

当然上面的广播之间还可以进行组合,比如系统有序后台广播、普通无序前台广播、系统无序广播、普通无序广播

5. 广播分发中心

广播分发中心可是广播机制中最为重要的部分,广播分发中心是由AMSBroadcastQueueBroadcastDispatcher等几个关键类组成的,动态广播接收者都是注册在广播分发中心的,广播也是由广播分发中心发送给所有的广播接收者的。因此没有广播分发中心也就没有广播机制,广播分发中心设计的好坏直接决定了广播分发的效率。而广播分发中心又可以分为注册模块分发广播模块这俩模块,那就从这两个模块入手来揭开广播分发中心的原理吧。

5.1 注册模块

前面提到过动态BroadcastReceiver要想接收广播的话,需要进行注册,注册模块所做的事情就是提供动态BroadcastReceiver注册的能力,以及保存注册信息保存下来的注册信息供发送广播时使用。那我就来介绍下注册模块是都提供了哪些注册能力,注册信息都有哪些,以及如何保存注册信息的。那就先从注册开始吧。

5.1.1 注册

注册是注册模块的首要功能,而动态BroadcastReceiver要想注册的话就需要调用ActivityManager的registerReceiverWithFeature方法,而该方法是一个binder调用,最终调用到AMS的registerReceiverWithFeature方法,下面列出了该方法的主要参数:

参数说明
caller:IApplicationThreadAMS通过caller把消息发送给App进程
callerPackage:String注册广播接收器的包名
receiver:IIntentReceiverreceiver它被注册到广播分发中心
filter:IntentFilterfilter存放了注册的广播
requiredPermission:String有些广播是需要权限的

其中receiver是类型为IIntentReceiver的匿名binder服务,在App进程中它是Binder类型的,而被传递到AMS后,它变为BinderProxy类型 (在此文中介绍了IBinder对象在进程之间传递过程中的互相转换),receiver就犹如App进程中BroadcastReceiver的一个“代理人”。

注册BroadcastReceiver时,还需要传递该BroadcastReceiver“关心”的广播,而这些广播信息是存放在类型为IntentFilter的filter对象中的。而有些广播是需要权限的,相应的权限是放在requiredPermission中的。

5.1.2 一条注册信息的存储

既然注册了,那接下来的事情就是把这条注册信息用一个类的实例封存起来,也就是把注册信息保存在某个类的实例中,而这个类就是ReceiverList类,下面是该类的类图:

image

image

图解
ReceiverList

该类的主要作用就是把注册的信息都存储起来,下面列出了该类的几个关键属性:

属性说明
receiver:IIntentReceiverreceiver可以理解为是BroadcastReceiver的“代理人”,receiver如果是App进程传递过来它的类型是BinderProxy;如果是systemserver进程传递过来它的类型是Binder
app:ProcessRecordapp对应App进程的进程信息,如进程id、进程状态等
pid:intpid代表App进程的进程id
uid:intuid对应App的唯一id

大家是不是会疑惑,在注册BroadcastReceiver的时候,还传递了BroadcastReceiver“关心”的广播信息 (广播信息存放于IntentFilter对象),还有权限等信息,而这些信息是没有在上面属性中体现的,那这些信息是存放于何处呢?

这么重要的信息怎么能不介绍呢,那就听我细细介绍,咱们继续看上面的类图,其中ReceiverList类继承ArrayList类,并且泛型参数是BroadcastFilter类,那ReceiverList类为啥要继承ArrayList呢?其主要原因是同一个自定义的BroadcastReceiver对象是可以被注册多次的,只不过每次的IntentFilter对象是不一样的,为了有利于大家理解,我特意写了一段伪代码:

//XXXBroadcastReceiver代表一个广播接收者类
XXXBroadcastReceiver receiver = new XXXBroadcastReceiver();

//action为"com.niu.broadcast.demo"的广播,放入intentFilter
IntentFilter intentFilter = new IntentFilter("com.niu.broadcast.demo");
//action为Intent.ACTION_SCREEN_ON的广播
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
//调用registerReceiver方法进行注册
registerReceiver(receiver,intentFilter);

//action为Intent.ACTION_SCREEN_OFF的广播,放入intentFilter
IntentFilter intentFilter1 = new IntentFilter(Intent.ACTION_SCREEN_OFF);
//调用registerReceiver方法进行注册
registerReceiver(receiver,intentFilter1);

如上代码,receiver注册了两次,第一次与该receiver一起注册的广播是com.niu.broadcast.demo、Intent.ACTION_SCREEN_ON,它们被放在intentFilter对象;第二次该与该receiver一起的广播是Intent.ACTION_SCREEN_OFF,它被放在intentFilter1对象。

根据以上信息可以得出ReceiverList类为啥要继承ArrayList,因为一个自定义的BroadcastReceiver对象会存在多个IntentFilter对象,而BroadcastFilter类的作用就是存放广播相关信息。

BroadcastFilter

还是看上面类图,BroadcastFilter继承了IntentFilter类,该类存储了广播相关的信息,下面罗列了它的几个关键属性:

属性说明
receiverList:ReceiverListreceiverList存储了类型为IIntentReceiver的receiver以及进程相关信息
packageName:String包名信息

而广播的action信息存储在它的父类的mActions属性中,如下:

属性说明
mActions:ArrayListmActions存储了所有的action

也就是说一个BroadcastFilter对象是可以包含多个广播的,而通过BroadcastFilter对象的receiverList属性找到ReceiverList对象。

小结

一条注册信息是被存放于ReceiverList对象,其中它的receiver属性是IIntentReceiver类型的,同时receiver也是一个IBinder对象,它是远端BroadcastReceiver对象的“代理人”。而广播信息存放于BroadcastFilter对象,该对象可是被存放于ReceiverList对象中,一个ReceiverList对象可以包含多个BroadcastFilter对象。

5.1.3 所有注册信息的存储

注册模块可不是仅仅存在一条注册信息,它可是存在非常非常多的注册信息的,一条注册信息是对应一个ReceiverList对象,那非常非常多的注册信息该咋存储呢?

答案是用HashMap<IBinder, ReceiverList>来存储,它的key值是IBinder对象,它的value值就是ReceiverList对象,为啥使用IBinder对象作为key值,难道不会重复吗?

首先IBinder对象就是类型为IIntentReceiver的receiver对象,receiver就是远端BroadcastReceiver对象的“代理人”。再来解释下为啥使用IBinder对象作为key值,在此文中介绍过IBinder对象的特性,同一IBinder对象被多次传递到别的进程,传递后拿到的IBinder对象还是一个,因此基于此点使用类型为IIntentReceiver的receiver对象作为key值。

如下代码是存储所有注册信息的代码:

//ActivityManagerService
final HashMap<IBinderReceiverList> mRegisteredReceivers = new HashMap<>();

5.1.4 如何快速检索注册信息

先说下为啥要检索注册信息,广播发送者在发送广播时,是需要根据广播的action从所有注册信息中把对该广播有“兴趣”的注册信息检索出来,进而才可以把广播信息发送给它们。

注册模块既然把所有的注册信息存储在了HashMap<IBinder, ReceiverList>结构中,但是该结果对于根据广播信息检索注册信息却是不利的。为啥这么说呢?

上面提到过每个广播都有自己的action,这个action就是一个字符串,它作为广播的key值,因此根据action从HashMap<IBinder, ReceiverList>结构中检索注册信息是不是很麻烦啊,因为HashMap<IBinder, ReceiverList>结构它的key值是IBinder对象。

那该如何加快检索注册信息呢?

答案是使用ArrayMap<String, BroadcastFilter[]>结构,它的key值是广播的action,而value值就是BroadcastFilter[],这样就可以根据广播的action很快的找到BroadcastFilter[]。

如下是相关代码:

//IntentResolver.java
private final ArrayMap<String, F[]> mActionToFilter = new ArrayMap<String, F[]>();

在注册时,会把BroadcastFilter对象添加到ArrayMap<String, BroadcastFilter[]>结构中,如下代码:

//ActivityManagerService
  
public Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
            String callerFeatureId, String receiverId, IIntentReceiver receiver,
            IntentFilter filter, String permission, int userId, int flags) {
   
   省略代码······
   //构建BroadcastFilter对象
   BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
                    receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
                    exported);
   //rl是ReceiverList对象
   if (rl.containsFilter(filter)) {
        //rl中已经存在filter,则啥也不做
   } else {
        //rl中不存在filter
        rl.add(bf);
        //把bf加入ArrayMap<String, F[]> mActionToFilter 中
        mReceiverResolver.addFilter(getPackageManagerInternal().snapshot(), bf);
  }
  省略代码······
}

5.1.5 小结

我用如下图来做个小结吧

image

image

5.2 分发广播模块

分发广播模块的作用就是把广播分发给它们的接收者,它主要是由BroadcastQueue类和BroadcastDispatcher类组成的,它的作用是不是非常的简单啊,但是要想把分发工作做好可不是那么容易的,要关注的点非常多。

5.2.1 队列

首先大家可以思考一个问题:如果让你来做分发广播模块,首先第一步应该想到啥?

答案是队列,因为分发广播模块分发的可是所有App进程及systemserver进程发送的广播,这广播的数量可是相当大的,这么多数量的广播当然得需要放到一个队列中。而队列是被分为有序广播队列无序广播队列,还记得在介绍广播按是否有序被分为有序广播无序广播吗,而有序广播队列就是用来存放有序广播的,无序广播队列自然用来存放无序广播了。这里的队列就是ArrayList类。

下面是相关代码:

//BroadcastQueue.java
//无序广播队列
final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();

//BroadcastDispatcher.java
//有序广播队列
private final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();

从上面代码中大家看到不管是有序还是无序队列中都存放的是BroadcastRecord类,那它的作用是啥呢?

5.2.2 队列元素

BroadcastRecord作为队列的元素,这个类的作用主要是用来记录一个广播及其他信息,先看下该类的主要属性:

属性说明
intent:Intent广播发送者发送广播时的intent信息,广播信息以及参数都在intent对象中
callerApp:ProcessRecord广播发送者的进程信息,比如pid、进程状态等
callerPackage:String广播发送者的包名
callingPid:int广播发送者的进程id
callerPackage:String广播发送者的包名
callingPid:int广播发送者的进程id
ordered:boolean广播是否是有序广播
sticky:boolean广播是否是粘性广播
receivers:List广播的接收者信息,接收者既包含动态广播接收者也包含静态广播接收者
delivery:int[]对应receivers,每个广播接收者的状态
duration:long[]对应receivers,每个广播接收者从接收到处理完广播所花的时间
deferred:boolean该BroadcastRecord是否被延迟
enqueueTime:long该BroadcastRecord对象入队列的时间,所有的BroadcastRecord是需要放入队列中,才能被发送
dispatchTime:long开始分发该广播的时间
timeoutExempt:boolean是否豁免超时机制
nextReceiver:int下一个广播接收者的索引值,因为所有的广播接收者都是放在List中的
anrCount:int发生anr的次数
curFilter:BroadcastFilter当前的动态广播接收者
curApp:ProcessRecord当前广播接收者对应的进程信息

上面列举了BroadcastRecord的关键属性,其中一部分是广播发送者相关信息如callerApp、callerPackage等;一部分是广播的信息存放于intent属性中,广播的action以及广播携带的数据都放在intent中,ordered则代表是否是有序广播;而所有的广播接收者是存放在receivers属性,该属性是List类型,receivers既包含动态广播接收者也包含静态广播接收者,动态广播接收者对应一个BroadcastFilter对象 (上面介绍过),静态广播接收者对应ResolveInfo对象;一部分是时间相关的信息如enqueueTime入队时间,dispatchTime则代表广播开始分发时间等。

也就是一个被发送的广播就会对应一个BroadcastRecord对象广播信息及广播接收者信息,接收者状态,时间等等信息都会被记录在BroadcastRecord对象中。有了这两个基础,那就跟随我看下分发广播的流程吧。

5.2.3 分发广播流程

分发广播流程其实就是一个消费者/生产者模式,广播发送者就相当于生产者通过AMS把构造的BroadcastRecord对象添加到有序广播队列或者无序广播队列,分发广播过程就相当于消费者,从无序广播队列有序广播队列中把BroadcastRecord对象取出来进行分发,我特意绘制了一幅分发广播的过程图:

image

image

其实整个分发广播的过程要远复杂于上图,我绘制这么一幅简单图的原因是我不希望一上来就给大家带来一种恐惧感,觉得这东西好难,而是希望从简单的开始,步步剖析。

如上图,分发广播的流程是会进行多轮而每一轮都是先分发所有无序广播再接着分发有序广播,此轮分发完毕接着继续进行下一轮。那就从这两个分发流程开始介绍吧。

分发所有无序广播

分发所有无序广播就是把无序广播队列里的所有广播都分发完毕,上面提到过BroadcastQueue对象的mParallelBroadcasts属性就是无序广播队列。分发无序广播有一个前提就是receiver必须是动态广播接收者,也就是receiver必须是BroadcastFilter对象。为啥有此规定呢?

其原因是无序广播的分发它是一个不需要等待的分发过程,只要把无序广播分发给接收者即可,完全不需要关心接收者如何处理广播,以及需要多久来处理广播。而静态广播接收者有可能存在对应的App进程还没启动的情况,如果App进程没启动则需要等待App进程启动,这可是一个等待过程。

我也同样特意绘制了一幅图,展示了分发所有无序广播的过程:

image

image

图解
  1. 判断无序广播队列是否为空,不为空则进入下面第2步;否则不执行任何操作。
  2. 无序广播队列 (BroadcastQueue对象的mParallelBroadcasts属性)把index = 0位置的BroadcastRecord对象取出来,并且遍历它的所有receivers,依次调用receiver对应的IApplicationThread对象的scheduleRegisteredReceiver方法,会把receiver、intent (广播信息包含在intent中) 等信息发送给对应进程,对应进程说到这些信息后最终传递给BroadcastReceiver对象的onReceive方法。
  3. 重复上面第2步流程,直到取完所有的BroadcastRecord为止

下面是相关代码,请自行取阅:

//BroadcastQueue.java
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
   省略代码······
   
   //如果mParallelBroadcasts中有元素,则进入下面逻辑
   while (mParallelBroadcasts.size() > 0) {
            //获取第一个BroadcastRecord
            r = mParallelBroadcasts.remove(0);
            r.dispatchTime = SystemClock.uptimeMillis();
            r.dispatchRealTime = SystemClock.elapsedRealtime();
            r.dispatchClockTime = System.currentTimeMillis();
            
            省略代码·····

            final int N = r.receivers.size();
            //遍历所有的receivers
            for (int i=0; i < N; i++) {
                //从receivers中获取receiver
                Object target = r.receivers.get(i);
                //把广播分发给receiver
                deliverToRegisteredReceiverLocked(r,
                        (BroadcastFilter) target, false, i);
            }
            //加入历史记录
            addBroadcastToHistoryLocked(r);
   }
   省略代码······
  
}
分发有序广播

image

image

如上图有序广播要求只有前一个广播接收者处理完毕才能把广播分发给下一个广播接收者 (如果存在下个广播接收者的话),正因为这个原因,因此分发有序广播的过程可是要比分发无序广播的过程复杂多了。 那就来看下复杂都体现在哪些方面。

取BroadcastRecord

在分发无序广播时,从无序广播队列中把索引0处的BroadcastRecord取出来即可 (前提是无序广播队列不为空),而分发有序广播时,可不是简简单单的从有序广播队列中取出BroadcastRecord就可以了,请先看下面代码:

//BroadcastDispatcher
public BroadcastRecord getNextBroadcastLocked(final long now) {
        if (mCurrentBroadcast != null) {//当前的BroadcastRecord不为null,则直接返回
            return mCurrentBroadcast;
        }

        final boolean someQueued = !mOrderedBroadcasts.isEmpty();//有序广播不为空

        BroadcastRecord next = null;

        省略代码······

        //没有取出上面这种类型广播,若alarm类型不为空,则取出alarm类型
        if (next == null && !mAlarmBroadcasts.isEmpty()) {
            next = popLocked(mAlarmBroadcasts);
            if (DEBUG_BROADCAST_DEFERRAL && next != null) {
                Slog.i(TAG, "Next broadcast from alarm targets: " + next);
            }
        }
        
        //若还是没有取出上面这种类型广播,并且mDeferredBroadcasts不为空,则尝试从mDeferredBroadcasts中取
        if (next == null && !mDeferredBroadcasts.isEmpty()) {
            省略代码······
        }

        //若还是没有取出,并且有序广播不为空,则取有序广播
        if (next == null && someQueued) {
            next = mOrderedBroadcasts.remove(0);
            if (DEBUG_BROADCAST_DEFERRAL) {
                Slog.i(TAG, "Next broadcast from main queue: " + next);
            }
        }
        
        //赋值给mCurrentBroadcast
        mCurrentBroadcast = next;
        return next;
    }

如上代码,分发有序广播时都需要调用上面的方法获取BroadcastRecord,取BroadcastRecord的顺序如下:

  1. 先判断mCurrentBroadcast是否为null,不为null则说明当前正在处理的BroadcastRecord还没处理完,并且把它返回
  2. 从alarm类型中取BroadcastRecord,取到赋值给mCurrentBroadcast,并且返回
  3. 从延迟队列mDeferredBroadcasts中取,取到赋值给mCurrentBroadcast,并且返回 (关于延迟队列会在下面介绍)
  4. 如果有序广播队列不为空,则从索引0处取出BroadcastRecord,取到赋值给mCurrentBroadcast,并且返回
分发

在一轮分发无序广播时,有就把所有的无序广播都分发,无则不做任何处理。而在一轮分发有序广播时可就复杂了,分发有序广播会处于分发广播给动态广播接收者分发广播给静态广播接收者这两者之一,那就介绍下这两者。

分发广播给动态广播接收者

此过程与无序广播时的过程基本是一致的,除了会启动超时延迟机制外。

分发广播给静态广播接收者

因为有序广播的接收者可以是静态广播接收者,分发广播给静态广播接收者的过程可是要麻烦的很多,如果静态广播接收者的对应进程已经启动了,则把广播分发给即可;如果进程没有启动,则需要等待该进程启动,等进程启动完成再把广播分发给它。同样也会启动超时延迟机制。

如下是相关代码:

//BroadcastQueue.java
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
   省略代码······
  
   //等待 过程
   if (mPendingBroadcast != null) {
      boolean isDead;
      //若进程还依然活着,则继续等待
      if (!isDead) {
                // It's still alive, so keep waiting
                return;
      } else {
        //进程已经死掉了,不会需要处理该BroadcastRecord,mPendingBroadcast置为null
        mPendingBroadcast.state = BroadcastRecord.IDLE;
        mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
        mPendingBroadcast = null;
      }
    }
    //取BroadcastRecord
    do {
       final long now = SystemClock.uptimeMillis();
       // 获取下一个BroadcastRecord
       r = mDispatcher.getNextBroadcastLocked(now);
       省略代码······
    }while (r == null);
    省略代码······
  
    //取接收者
    final Object nextReceiver = r.receivers.get(recIdx);
    
    //接收者为动态广播接收者,则分发广播给接收者
    if (nextReceiver instanceof BroadcastFilter) {
      省略代码······
    }
    
    //把广播分发给静态广播接收者
    ResolveInfo info = (ResolveInfo)nextReceiver;
    省略代码······
}
超时机制

正因为有序广播要求只有前一个广播接收者处理完毕才能把广播分发给下一个广播接收者,这样的要求会带来一个很严重的问题:如果一个接收者处理广播花费了很久的时间,那就会导致后面的接收者有序广播队列中的其他BroadcastRecord分发延迟,那针对此情况就有了超时机制

超时机制在把广播分发给每一个接收者之前会启动,当接收者处理完毕广播后,需要发送一个处理完毕的反馈给广播分发中心,这时候取消超时机制。如果在规定的超时时间内,没有收到该接收者的反馈,则认为是超时对应的App进程就会报ANR (Application not response)。关于超时机制在后面还会详细介绍。

延迟机制

对于一些接收者处理广播的速度慢时,会把该接收者对应的uid等信息加入延迟队列,当在规定的延迟时间内,再次给该接受者发送广播时,延迟它的分发时间。可以理解为是对该接受者的惩罚措施吧。关于延迟机制在后面还会介绍,下面是延迟队列的相关代码:

//BroadcastDispatcher.java
//延迟队列
private final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();

5.2.3 分发广播模块分类

尤其是有序广播的分发会由于某个接收者处理耗时导致其他的接收者以及广播分发延迟的情况,针对此情况对分发广播模块进行了分类做到“专车专用”的作用,也就是特定的分发广播模块只处理特定的广播的分发,而分发广播模块被划分为前台广播分发模块后台广播分发模块offload分发广播模块

前台广播分发模块

前台广播分发模块只处理前台广播的分发,在上面介绍广播可以按是否是后台划分为前台广播后台广播

后台广播分发模块

后台广播分发模块则只处理后台广播的分发,广播发送者在默认发送广播的情况下发送的就是后台广播。

offload分发广播模块

offload分发广播模块主要处理boot complete广播的分发,也就是系统启动时候的广播的分发都由它处理,而App进程是不能使用它的。

如下是相关代码:

//ActivityManagerService
public ActivityManagerService(Context systemContext, ActivityTaskManagerService atm) {
  省略代码·····
  mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
                "foreground", foreConstants, false);
  mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
                "background", backConstants, true);
  mBgOffloadBroadcastQueue = new BroadcastQueue(this, mHandler,
                "offload_bg", offloadConstants, true);
  mFgOffloadBroadcastQueue = new BroadcastQueue(this, mHandler,
                "offload_fg", foreConstants, true);
  mBroadcastQueues[0] = mFgBroadcastQueue;
  mBroadcastQueues[1] = mBgBroadcastQueue;
  mBroadcastQueues[2] = mBgOffloadBroadcastQueue;
  mBroadcastQueues[3] = mFgOffloadBroadcastQueue;
  省略代码······
}

5.3 小结

广播分发中心分为注册模块分发广播模块

注册模块所做的事情就是提供动态BroadcastReceiver注册的能力,以及保存注册信息保存下来的注册信息供发送广播时使用

分发广播模块所做的事情就是一轮一轮的把广播发送给接收者,把广播发送给接收者又分为发送所有无序广播发送有序广播,发送所有无序广播就是如果无序广播队列不为空,把所有的无序广播发送出去。而发送有序广播时,因为有序广播要求只有前一个广播接收者处理完毕才能把广播分发给下一个广播接收者,也正因为有如此要求会导致一个问题:如果一个接收者处理广播花费了很久的时间,那就会导致后面的接收者有序广播队列中的其他BroadcastRecord分发延迟,为了解决此问题设计了超时机制延迟机制,对分发广播模块进行分类,做到专模块负责专广播的作用。

题外话:即使有了上面的这些机制,在实际项目中我也遇到过由于某个接收者的耗时,导致其他接收者及广播被延迟几十毫秒甚至一分钟的情况出现。

由于篇幅有限,此文分为上、下两篇。

欢迎关注我的公众号牛晓伟(搜索或者点击牛晓伟链接)