Android广播机制详解

原创 2016年06月07日 23:16:13

1 广播类型

        从广播的注册方式来分,分为以下2种:

静态注册:通过<receiver></receiver>的形式在AndroidManifest.xml中注册的广播;

动态注册:通过context. registerReceiver在程序中显示注册的广播;

       上面静态广播和动态广播比较明显的3个区别:

(1)静态广播在进程没有运行的时候,也可以收到,这时候会先启动进程,然后处理广播onReceive函数(静态广播可以拉起进程,但是目前很多厂商为了禁止应用自启动,对于广播拉起应用都有一些限制,被禁止自启动的应用AMS直接就把相应的静态注册接收者过滤掉了),动态广播因为是在程序中通过代码显示注册的,因此必须要在进程已经运行的时候才能收到广播。

(2)静态广播处理的时候每次都会创建一个新的广播接收器对象,但是动态广播一般都是同一个广播接收器对象。

(3)针对同样一个非order广播,所有动态注册接收者要先于所有的静态接收者先收到广播,同一个应用内,先注册的接收器先收到广播。

       从广播的发送方式来分,分为以下3种:

普通广播:通过context. sendBroadcast或者context. sendBroadcastAsUser发送给当前系统中所有注册的接受者,也就是只要注册了就会接收到;

有序广播:接收者按照优先级处理广播,并且前面处理广播的接受者可以中止广播的传递,一般通过context. sendOrderedBroadcast或者context. sendOrderedBroadcastAsUser;

粘性广播:可以发送给以后注册的接受者,意思是系统会将前面的粘性广播保存在AMS中,一旦注册了与以保存的性广播符合的广播,在注册结束后会立即收到广播,一般通过context. sendStickyBroadcast或context.sendStickyOrderedBroadcast来发送,从字面上看,可以看出来粘性广播也分为普通粘性广播和有序粘性广播。

      根据广播的类型,一般常用的就是普通广播和粘性广播,网络切换的广播就是一种粘性广播。


2 注册广播

      静态广播直接在AndroidManifest.xml注册一下即可。

      动态广播的注册需要通过context中的接口registerReceiver,最终调用到的是ContextImpl中的registerReceiverInternal函数。


2.1 用户进程ContextImpl. registerReceiverInternal

      这个函数包括几个参数:
                param1:receiver 广播接收器
                param2:userId注册广播应用程序的UserId
                param3:filter广播接收器的接收条件
                param4:broadcastPermission广播接收器的权限信息,广播发送者必须带上这个权限信息,它所发出的广播才能被注册者接收到

                param5:scheduler接收广播的线程的handler

                param6:context


      根据代码,一般receiver、mPackageInfo、context不为null,scheduler是ActivityThread中的Handler,也就是主线程的Handler,接着下面调用到了LoadedApk.getReceiverDispatcher函数,然后调用到AMNative.getDefault().registerReceiver,也就是AMS.registerReceiver函数。


 

      大致的过程就是上面这样,最终在向AMS. registerReceiver注册时实际上传递的是LoadedApk.InnerReceiver.ReceiverDispatcher.InnerReceiver对象,该对象是一个的Binder引用对象,InnerReceiver继承了IIntentReceiver.Stub类,该类显然是AIDL机制中的服务端对象,而实际传递的是引用对象说白了实际上只是一个整型数值,但是这个整型数值能在当前系统中的Binder机制中唯一表示某一个Binder对象,同时AMS也能定位到是哪个进程中的,通过(PID, Binder引用)就能唯一定向到某一个特定的ReceiverDispatcher对象,该对象中保存了实际的广播接收器处理对象,负责处理广播。

2.2 system_server进程AMS. registerReceiver

      这个函数逻辑比较清楚,但是涉及到很多的变量,先理一下里面的几个变量:
                mStickyBroadcasts:(UserId:(String,Intents))存储了当前系统中所有用户的Sticky广播,每个用户用Action存储了所有的Intents;
                mRegisteredReceivers:(IIntentReceiver: BroadcastFilters)每个receiver注册的所有的filters
                mReceiverResolver:保存当前系统注册的所多有的BroadcastFilter
                BroadcastRecord:需要发送的一条广播记录,里面的receivers成员存储了所有需要接收当前广播的接收器
        下面分块来分析AMS.registerReceiver函数。

(1)过滤出与当前注册IntentFilter中的action匹配的所有的Intent


      从mStickyBroadcasts里面找出发送给所有用户和调用者UserId的所有的Stricky广播的Intents,然后和当前注册的IntentFilter中所有的action逐个进行比对,找出符合的所有的intent存储在stickyIntents里面。注意,这里仅仅通过action进行了一次筛选。
(2)从stickyIntents里面找出与当前注册的IntentFilter匹配的Intent
      上面根据Action对相应用户的Stricky Intents进行了一次过滤,下面根据IntentFilter.match方法对筛选出来的stickyIntents进行精确的匹配,包括action、type、scheme、data、categories等,将最终的匹配结果存放在allSticky里面,如果调用registerReceiver里面传递的receiver是null,就返回allSticky里面的第一个匹配的Intent,或者返回null。
 

      上面的两部分主要从stricky广播的历史记录中筛选出与当前注册的IntentFilter匹配的历史Intent,将最终的结果存放在allSticky里面,如果注册的广播是一个非stricky广播,一般来说这里面allSticky是null,但是对于网络切换这种发送的时候是以stricky广播形式发送,这里allSticky就不是null。

(3)在注册记录中查找当前的receiver
 

      首先判断当前进程是否还活着,注释里面说的很清楚。然后从当前系统所有已经动态注册的mRegisteredReceivers里面查找当前注册的receiver,返回的是一个ReceiverList,该类继承了ArrayList<BroadcastFilter>,语义就是一个receiver可以对应着一串的BroadcastFilter,所以说对于同一个receiver对象而言,可以多次调用registerReceiver注册不同的广播条件(或者可以认为是广播筛选器)BroadcastFilter,这里面是注册动作中最关键的地方,将receiver以及对应的ReceiverList存储到AMS.mRegisteredReceivers成员里面。

(4)将当前注册的广播筛选器放到ReceiverList里面
      根据当前的IntentFilter创建BroadcastFilter对象,BroadcastFilter继承了IntentFilter,基本上和IntentFilter没有的太大的区别,里面还包含对ReceiverList的引用。

        创建完BroadcastFilter后,将其加到mReceiverResolver里面,内部用的是一个ArraySet,说明重复添加完全相同的BroadcastFilter对象不会多次添加。到这里没有匹配的stricky广播已经注册结束了。


(5)发送匹配筛选器的stricky广播
 
      上面注册结束以后,如果筛选出与当前注册的IntentFilter匹配的stricky广播的Intent,就将所有匹配的Intent逐条发送广播给当前的注册者receiver,可以看到这里的接受者receivers里面就只有当前创建的一个BroadcastFilter,也就是当前的注册者。

      上面就是动态广播注册的整个过程,主要将当前注册的动态广播接收器以及对应的广播筛选器BroadcastFilter添加到AMS. mRegisteredReceivers里面,同时处理了与stricky广播的相关逻辑,从这里可以看到对于注册stricky广播而言,在注册结束以后,系统会立马发送与之匹配的stricky广播。

3发送广播

      一般发送广播都是通过context.sendBroadcast等相关接口进行的,然后Binder远程调用到AMS的相关函数,AMS根据各个应用注册的所有的filters经过筛选以后,将广播发送给指定的进程。

3.1 ContextImpl.sendBroadcast

 
                param1:intent 广播条件和内容的载体
                param2:receiverPermission和前面注册时的broadcastPermission对应
                param3:appOp 操作类型,注释上说不是为了第三方应用设计的,一般不用
      这个函数基本上就是直接调用了AMS.broadcastIntent函数,不同的广播类型:order、stricky,都是通过相应的参数来表征的。

3.2 AMS.broadcastIntent

 
        看一下里面的几个参数:
                caller:发送广播的调用者进程标示
                intent:广播的具体条件、内容都在这里面
                serialized:标示当前广播是否为order广播,true代表order广播
                sticky:标示当前广播是否为stricky广播,true代表stricky广播
        这个函数首先检查一下当前系统是否能够开始发送广播,然后获取调用者进程的相关信息,最终转调broadcastIntentLocked函数。

        broadcastIntentLocked这个函数同样流程比较长,找重点分块分析。

(1)排除Stopped状态的应用
 

        所有的广播Intent在这里都会默认加上这个标记,注释上的意思是所有的广播都不会发送到Stopped状态的应用,应该在发送的时候会检查应用当前的状态。

(2)处理受限广播
 

        受限广播是通过查询PMS得出来的,如果是受限广播,抛出异常然后直接返回ActivityManager.BROADCAST_SUCCESS,不会进行下面的动作。

(3)处理特定系统广播
 

        注释上说的很清楚,对于一些来自于PMS的包状态的变化,AMS需要及时的处理相关的Activity,这里是因为AMS兼顾了所有的4大组件,当包的状态发生变化,AMS作为总管需要第一时间内处理完总管要做的事情,然后将对应的广播再转发给对应的应用。

(4)处理Stricky广播
 
        Stricky广播检查,主要包括:发送Stricky广播的应用必须在AndroidManifest.xml文件中声明android.permission.BROADCAST_STICKY权限、发送时不能有receiverPermission权限信息以及Intent里面不能指定接受者,也就是说Stricky广播不能指定特定的接受者。
 
        这里的userId应该指的是接收者,这里会对发送的Stricky广播进行检查,如果不是发送给所有用户,那就不能和系统中目前保存的发送给所有用户的Stricky广播冲突,冲突直接抛出异常。
 
        Stricky广播校验结束之后,需要将其保存到AMS的mStickyBroadcasts变量里面,前面分析过mStickyBroadcasts是以用户ID作为key保存的,首先取出当前用户的所有Stricky广播,然后根据当前广播的action保存到action对应的list里面即可,里面还有一个细节,如果当前intent和list中的某个intent用filterEquals比较相等,就直接替换掉以前的,否则直接添加到list的最后即可,到这里有关Stricky广播的主要处理基本上已经结束了。
(5)广播接收用户
 
        当前广播的接收者,注释里面说的很清楚,mStartedUserArray代表的是当前所有已经启动的用户。

(6)过滤出当前广播的接受者

 
        receivers用来保存最终串行化发送的广播接收者,registeredReceivers用来保存已异步方式并行发送的接收者。这里面有一点很重要,前面讨论的所有的都是有关动态广播的,这里的collectReceiverComponents从PMS中查询所有的满足条件的静态接收者。
 

        根据前面注册时候保存的mReceiverResolver,从所有的动态注册者中过滤出满足当前广播条件的接收者。目前来看,rceivers保存的静态广播接收者,registeredReceivers保存的是动态广播的接收者。

(7)处理非order模式的动态接收者
 

        如果当前发送的广播不是order广播,满足条件的动态广播接收者数量大于0,就将当前的(广播Intent, 满足条件的registeredReceivers动态接收者)相关信息形成一个BroadcastRecord,并放到相应的队列里面。然后注意这里放到了BroadcastQueue的mParallelBroadcasts队列里面。处理完非order广播的接收者registeredReceivers后,会将registeredReceivers置为null,下面还会用到这个registeredReceivers。

        简单看一下broadcastQueueForIntent函数:
 
        这个函数通过判断当前的Intent是否包含Intent.FLAG_RECEIVER_FOREGROUND标记来确定返回mFgBroadcastQueue 还是mBgBroadcastQueue,这两个BroadcastQueue目前来看除了超时时间不一样(如果有超时时间),好像没什么不一样,一般我们发送的广播都是不包含这个标记的。

        到这里上面已经处理了非order广播的动态接收者,具体是如何发送到接收者进程的,后面再分析。剩下的就是order广播的动态接收者和静态接收者。

(8)处理order广播的动态接收者 + 静态接收者

 
        上面有一句关键的注释,“Merge into one list”,意思就是将多个链表合并到一个链表里面,这里指的就是前面的registeredReceivers和rceivers,走到这里的话,registeredReceivers不为null说明当前发送的是一个order广播,剩下的就是静态的接收者rceivers,这里说明一个问题:
        如果是order广播,动态接收者和静态的接收者合并到一个队列里面进行处理,也就是说order广播下,所有的接收者(静态和动态)处理方式都是一样的(后面会分析到,都是串行化处理的)。还有就是对于静态的接收者而言,始终是和order广播的处理方式是一样的,也就是说静态的接收者只有order模式(串行化接收)。
        然后继续看里面的注释,对于ACTION_PACKAGE_ADDED广播而言,如果是自己被add了,那么这个广播只能别人收到,自己即使注册了这个静态广播也接收不到,注释上说是担心有些应用一安装就接收自己的PACKAGE_ADDED广播,然后就启动了。简言之,应用永远接收不到自己的PACKAGE_ADDED广播。
        如果是ACTION_PACKAGE_ADDED广播,从静态注册接收者将当前被add的应用排除掉:
 
        代码里面skipPackages直接被过滤掉了。
        下面开始讲剩下的静态接收者和order模式下的动态接收者进行合并:
 
        上面这一段代码是典型的有序链表的合并操作,合并的依据是接收者的priority值,这里需要注意的一点是动态广播接收器注册的时候一般都没有指定priority,默认值是0,具体实现好像有点像归并排序的意思,而且是从后向前进行的归并,因此priority越小,在链表中的位置就越靠前,后面处理的时候也就越先处理。走完这里所有的静态接收者和order模式下的动态接收者都已经被合并到了receivers链表里面。
        另外还有一点需要关注,合并以后在receivers链表里面静态接收者对应ResolveInfo对象,order模式下的动态接收者对应的是BroadcastFilter对象。
 

        如果receivers链表不为null,这时候将当前的intent以及对应的接收者创建一个BroadcastRecord,并将其放到对应的BroadcastQueue里面,注意这里放到mOrderedBroadcasts里面,然后调用scheduleBroadcastsLocked驱动一下广播发送的进度。

(9)驱动广播的发送

 
        驱动广播发送的时候首先判断mBroadcastsScheduled变量是否为true,为true就直接返回。否则发送一条BROADCAST_INTENT_MSG消息,看一下这个变量的注释:
 

        如果当前有一个BROADCAST_INTENT_MSG消息正在战斗着(正在等待处理该消息),这个变量就会被设置为true,意思就是前面的BROADCAST_INTENT_MSG消息还在等待被处理,这个变量就是true,否则就是false。这个消息会调用到processNextBroadcast函数,该函数是整个广播处理的核心函数,广播发送的过程中反反复复在调用这个函数。

3.3 AMS. processNextBroadcast

        在开始分析这个函数之前,首先明确一点,非order动态接收广播被放到mParallelBroadcasts里面,order动态广播和静态广播被存放在mOrderedBroadcasts里面。这两种BroadcastRecord的处理方式是不一样的。

        mParallelBroadcasts处理很简单,遍历每一个BroadcastRecord,将改广播逐个发送给BroadcastRecord.receivers里面的每一个接收者,只管发送,不关心接收者是如何执行的。
 
      mOrderedBroadcasts的处理就复杂多了,这里面的所有的BroadcastRecord以及每一个接收者BroadcastRecord.receivers都是顺序接收的,前面的处理完以后后面的才能处理。
 
网上找了两张图,大致就是这个意思。具体的流程还是很复杂的。

3.3.1 串行化广播处理

(1)驱动标记设置

        processNextBroadcast方法整个放在一个同步块里面,该方法判断mBroadcastsScheduled标记,前面说到,如果消息队列里面有BROADCAST_INTENT_MSG消息,该标记为true,阻止新的消息加入队列,这里开始处理这个消息的时候,将mBroadcastsScheduled变量设置为false,开始允许新的消息加入。

(2)处理非串行化广播
 
        前面分析到,只有非order模式的动态广播是串行化的发送方式,看上面的代码,总体上是两层循环,处理mParallelBroadcasts中的所有广播,外层循环是遍历mParallelBroadcasts中的所有BroadcastRecord,内存循环是遍历BroadcastRecord中的所有的receivers,也就是挨个将mParallelBroadcasts中的所有的BroadcastRecord发送给相应的接收者,这里可以看到发送串行化广播使用的deliverToRegisteredReceiverLocked方法,函数名称里面也明确的指出这个用来发送动态注册的广播,并且参数target被强制转换成BroadcastFilter类型,前面也分析到,对于BroadcastRecord中的List成员receivers,动态广播该类型对应BroadcastFilter,静态注册成员对应的ResolveInfo类型。
(3)发送动态广播deliverToRegisteredReceiverLocked
 
        发送动态广播之前,进行一些检查,如果当前广播的接收者不满足条件的话,这里的skip变量会被设置为true,也就不会进行实际的广播发送。
        前面分析,动态接收者有order和非order两种模式,最终发送给动态注册接收者的时候都是用的deliverToRegisteredReceiverLocked函数,这里面判断当前如果是order模式,需要记录order广播的相关状态,BroadcastRecord里面的receivers记录了所有需要接收改广播的所有接收者,如果是order广播,所有的接收者需要依次以一种同步的方式发送广播,可以看到order广播在BroadcastRecord保存了几个状态:
                receiver:IBinder类型,代表当前的接收者 
                curFilter:当前正在处理的BroadcastFilter,和上面的receiver是对应好的
                state:CALL_IN_RECEIVE
        然后接着调用performReceiveLocked函数发送动态注册广播,函数调用结束后如果是order广播,会将当前BroadcastRecord的状态设置为CALL_DONE_RECEIVE。
 
        继续看一下performReceiveLocked函数:
 
        一般来说,动态广播注册的时候进程一定是已经启动的,所以这里的app和app.thread应该都不是null,然后调用到注册进程的ActivityThread. scheduleRegisteredReceiver函数,这个是一个Binder调用,注释上面说的很清楚,要用one-way calls像动态的注册进程发起Binder调用,意思就是在Binder调用里面会加上IBinder.FLAG_ONEWAY标记,Binder客户端(动态注册进程)只要一收到Binder调用的命令和数据,立马返回到Binder服务端(AMS进程),是一个异步的调用方式。
        大致流程是:
1) ApplicationThreadNative.ApplicationThreadProxy. scheduleRegisteredReceiver(system_server)
2) Binder驱动 (Binder驱动进程,ONEWAY)
3)ApplicationThreadNative. scheduleRegisteredReceiver(应用进程,Binder线程向主线程发送消息)
4)Binder驱动返回 (Binder驱动进程)
5)ActivityThread. scheduleRegisteredReceiver(应用进程,处理消息)

        具体应用进程是如何处理的,后面再分析,下面继续AMS发送order广播。

3.3.2 处理order广播

        牢记前面的结论,串行化广播都存储在mOrderedBroadcasts里面,并且每个接收者是以同步的方式处理,前面一个接收者处理完并且返回结果以后才会将广播发送给后面的接收者。
(1)逐条处理mOrderedBroadcasts中的BroadcastRecord
 
        短短这么几行的循环,完成的功能还是挺多的:
1) r:目前正在处理的BroadcastRecord,每次都是取mOrderedBroadcasts中最前面的元素,因此每处理完一个BroadcastRecord,都会从mOrderedBroadcasts删除掉;
2) mService.mProcessesReady 代表AMS已经启动注册完成,可以开始接受客户端的请求;
3) r.dispatchTime > 0:说明当前的BroadcastRecord不是第一次被处理,或者说当前的r正在被处理,每个BroadcastRecord里面receivers可能有多个成员,每次出现超时或者处理下一个成员都需要重新调用一次processNextBroadcast函数,继续走到这个循环里面;
4) BroadcastRecord超时:注意这里的超时和我们常说的广播超时ANR不是一个概念,这个BroadcastRecord超时是针对当前BroadcastRecord. receivers里面剩余的所有的成员而言的,比如说当前receivers里面剩余4个广播接收者,那么这个超时的时间:
                2*(4*mTimeoutPeriod)
        至于这个mTimeoutPeriod,对于前台广播mFgBroadcastQueue和mBgBroadcastQueue后台广播时间如下:
 
        也就是对于有4个成员的receivers 后台广播的BroadcastRecord而言超时的时间为:
                2*(4*10*1000)=80 000ms=80s
        当出现这种超时的时候,当前正在处理的广播接收者会出现ANR,并且导致后面尚未接收到广播的收不到当前的广播。broadcastTimeoutLocked函数会将mOrderedBroadcasts中下标为0的应用进程ANR,下面forceReceive设置为true,走到下面的if判断里面会将当前正在处理的BroadcastRecord从mOrderedBroadcasts中remove掉,导致receivers后面的成员没有收到广播,并且将r设置为null,接着就处理broadcastTimeoutLocked里面的下一个广播记录BroadcastRecord。

        例如,receivers里面包含4个成员,但是第1个接收者在80s内都没有处理完,那么这个接收者进程会收到ANR,并且后面的3个广播接收者都收不到当前的广播。

5) 假设是第一次处理一个BroadcastRecord,这时候r从mOrderedBroadcasts里面取出,不是null,直接退出while(r!=null)的循环;
(2)处理BroadcastRecord中当前的receiver
 
1)nextReceiver代表下一个将要处理的接收者,receiverTime代表当前这个接收者开始处理的时间戳,注释里面将的很清楚:当一个接收者开始处理的时候,保证这个接收者有一个超时时间消息,如果需要该消息将在该接收者出现超时的时候将接收者杀掉;
2)recIdx == 0:说明当前的receiver是当前BroadcastRecord中的第一个被处理的接收者,换句话说,这个时候是当前BroadcastRecord开始被处理的时间戳,也就是上面BroadcastRecord超时的起点,可以看到上面超时比较的时候用的就是r.dispatchTime;
3)mPendingBroadcastTimeoutMessage:设置超时消息,一开始该变量是false,发送完超时消息以后就设置为true,该变量严格跟踪receiver开始处理的起点,看一下设置超时的函数setBroadcastTimeoutLocked:
 

        这个函数有一个值得注意的地方,发送消息的时候使用的是sendMessageAtTime函数,该函数在指定的时间点才会将BROADCAST_TIMEOUT_MSG消息发送出去,看一下上面调用这个函数传进来的参数传递的是:r.receiverTime + mTimeoutPeriod,也就是当前开始处理的时间点加上超时时间mTimeoutPeriod,到这里超时时间消息就已经埋好雷了,这个雷就是我们常见的广播ANR。

(3)处理order广播的动态接收者
        上面分析的时候对于BroadcastRecord.receivers里面包含两种receiver接收者:order广播下的动态注册and静态接收者,这两种receiver处理的方式是不一样的,对于order广播下的动态注册receiver而言,接收者进程一定是已经启动的,但是对于静态接收者receiver而言,当前的receiver进程可能还没有启动,因此动态和静态的receiver处理的逻辑不一样,需要分开处理,而静态接收者又分为进程已经启动和尚未启动两种情况。
        下面看一下order广播下的动态注册receiver的处理逻辑:
 

        取出当前的receiver对象nextReceiver,如果是一个BroadcastFilter对象,说明是一个动态注册receiver,下面就调用deliverToRegisteredReceiverLocked函数处理,这个函数在前面分析过,最终会调用到ActivityThread.scheduleRegisteredReceiver函数,同时里面的ordered参数true,具体的处理后面分析。

(4)检查静态接收者
 
        注释:这是比较难处理的情形,需要实例化接收者BroadcastReceiver对象,甚至有可能需要启动接收者进程。
        对于一个静态接收者而言,这里的nextReceiver对象是ResolveInfo类型,下面采用变量skip对需要跳过的情形进行过滤,如果需要跳过,改receiver会被直接忽略。
 

        在退出之前会将receiver curFilter 以及 state设置为初始状态,并驱动下一次广播处理,这个时候会接着处理当前BroadcastRecord里面的下一个receiver。

(5)静态接收者进程已经启动
        静态接收者分为:进程尚未启动和进程已经启动两种情形,下面首先处理的是进程已经启动的情况,首先根据包名和应用uid从AMS中查找当前静态接收者进程的ProcessRecord,如果不为null,说明该报名的应用进程目前已经启动。
 
        下面调用processCurBroadcastLocked函数处理应用进程已经启动的情况,另外注意,调用processCurBroadcastLocked函数后,该函数直接return了,这是因为order广播是一种同步的处理方式,processCurBroadcastLocked只是将广播发送到接收者进程,需要一直等待接收者进程处理完广播后返回,AMS才能处理当前BroadcastRecord里面的下一个receiver,所以直接返回就行了,反正需要等待的。
        看一下processCurBroadcastLocked函数:
 
        处理也很直接,将接收者进程的相关信息写入到当前BroadcastRecord中的相关成员中,最主要的是调用ActivityThread. scheduleReceiver函数来处理广播,到这里对于进程已经启动的静态接收者已经发送出去,接下来就等待接收进程的返回。

        理一下当前的状态,AMS设置好ANR超时,正在等待BroadcastRecord中优先级较高的的接收者进程返回。

(6)静态接收者进程尚未启动
 
        如果当前静态接收者进程尚未启动,首先调用AMS.startProcessLocked函数来启动接收者进程,并将当前正在等待进程启动的BroadcastRecord存储到mPendingBroadcast里面,这个就是静态广播拉起应用的原理,如果应用没有启动,注册一个静态广播,例如网络切换,可以将进程拉起来(厂商一般会修改,只允许自己的应用能自启动)。
        到这里,又开始进入等待进程的启动,显然这个广播需要在应用启动完成以后处理,那么应用启动完成是如何通知AMS的呢?这里列出几个关键的函数,具体就不分析了:
1) 应用进程创建
2) ActivityThread.main
3) ActivityThread.attach
4) AMS. attachApplication
5) AMS. attachApplicationLocked
6) ActivityThread.bindApplication
7) AMS. sendPendingBroadcastsLocked
        最终调用了BroadcastQueue. sendPendingBroadcastsLocked函数,看一下这个函数:
 

        前面分析mPendingBroadcast用于存储当前正在等待进程启动的BroadcastRecord,当进程启动完成后,会将mPendingBroadcast设置为null,然后调用processCurBroadcastLocked函数,根据前面的分析,这个函数会调用到ActivityThread.scheduleReceiver函数负责处理广播。

(7)order模式广播超时机制
        AMS在处理order模式广播接收者时,会为每一个order模式广播处理设置超时时间,并且超时时间是各个接收者之间相互独立,前面分析超时通过setBroadcastTimeoutLocked函数建立超时时间点消息的,本以为每次处理完以后,调用cancelBroadcastTimeoutLocked函数取消当前接收者的超时消息,但是实际上用了一种更加高效的方法处理了超时机制,在每个order模式receiver开始处理的时候设置超时消息,BroadcastRecord.receiverTime记录了当前receiver开始处理的时间点,看一下超时处理函数:
 
        这里判断当前时间now和当前receiver超时时间点之间的关系,对于真实的超时receiver而言,这里timeoutTime < now,继续往下处理超时,包括弹出ANR的弹框。举个例子说一下timeoutTime > now的情况:
        假设receiverA在100s的时候开始处理,超时时间为10s,那么receiverA的超时时间点就是110s,但是receiverA在105s的时候已经处理完了,于是在105s的时候开始receiverB,但是并没有取消receiverA的超时消息,也就是在110s的时候仍然会走到这里的broadcastTimeoutLocked函数,receiverB开始处理,这时候r.receiverTime就是105s,对于receiverB而言超时时间应该是115s,假设receiverB需要在112s才能处理完,在110s的时候broadcastTimeoutLocked函数处理的时候timeoutTime=115s,now=110s,这时候不会进行实际的超时处理,因为还没有到真实的超时时间,所以重新设置超时时间点在115s。

        就这样根据当前BroadcastRecord.receiverTime的时间反复调整,注释上也说的很清楚:因为没有在每个广播处理完之后取消或者重置超时时间,从而导致提前检测到超时消息。取而代之,设置一个初始超时时间点,然后每次出现超时事件的时候根据需要进行处理或者调整超时机制。

4应用进程处理广播

4.1 scheduleRegisteredReceiver处理非串行化动态广播

 
        对于非串行化的动态广播,ordered变量是false,根据前面的分析,这里的receiver对应的是LoadedApk.InnerReceiver.ReceiverDispatcher. InnerReceiver对象,下面调用了InnerReceiver.performReceive函数:
 
        接下来调用了ReceiverDispatcher.performReceive函数:
 
        上面将广播的参数封装在一个Args对象里面,然后通过post到主线程的消息队列里面,下面就看一下Args.run是怎么处理的:
 
        这里的mReceiver就是调用regisiterReceiver时传进来的BroadcastReceiver对象,这里调用了我们写的onReceive函数,然后调用了BroadcastReceiver.PendingResult.finish函数:
 
        这里的mType在创建Args对象的时候传进来的,看一下下面的构造函数,对于动态注册的广播接收器,参数mRegistered为true,mType就是TYPE_REGISTERED,对于非order广播而言,下面的ordered变量是false,这样BroadcastReceiver.PendingResult中mOrderedHint为false,mType是TYPE_REGISTERED;
 

        参考上面的finish函数,if和else if里面的条件都不满足,因此对于非order的动态广播而言,finish函数就啥也不做。

4.2 scheduleRegisteredReceiver处理串行化动态广播

        前面分析到,对于接收者进程而言,order和非order广播都调scheduleRegisteredReceiver函数处理,只是上面处理的时候BroadcastReceiver.PendingResult中mOrderedHint为true,mType是TYPE_REGISTERED,这时候在finish函数的时候,会调用sendFinished函数:
 
        对于order广播而言,走的是if(mOrderedHint)这个分支,里面有的参数mAbortBroadcast代表当前的高优先级的广播接收者是否需要disable低优先级的广播接收者接收当前的广播。
 
        首先辨别出当前receiver所在的BroadcastRecord属于前台广播还是后台广播,然后在对应的BroadcastQueue中找出对应的BroadcastRecord,里面的finishReceiverLocked函数在前面介绍过,主要是重新设置BroadcastRecord里面一些状态变量,以便于BroadcastRecord将广播发送给下一个接收者。尤其的,如果前面的mAbortBroadcast设置为true,那么BroadcastRecord的成员resultAbort会设置成true。
        接下来调用BroadcastQueue.processNextBroadcast函数继续驱动里面的mParallelBroadcasts和mOrderedBroadcasts中的BroadcastRecord处理下一个接收者,里面有这样一个判断:
 

        如果r.resultAbort为true,会停止处理当前正在发送的BroadcastRecord,这样优先级比较低的接收者也就收不到这个广播了。

4.3 scheduleReceiver处理静态广播

        ActivityThread. scheduleReceiver处理应用进程中接收到的静态广播消息,实际处理该广播的是ActivityThread.handleReceiver函数。处理主要包含3大步骤:
1) 创建BroadcastReveiver对象
 

        首先从AMS传递的intent中获取当前处理该广播的组件名称,然后通过反射创建一个BroadcastReveiver对象,从这里可以看出来,静态广播处理的时候,每次都会创建一个新的BroadcastReveiver对象;

2) 执行onReceive函数
 

        这里的receiver就是上面第一步创建的BroadcastReveiver对象,看一下这里首先尝试创建一个Application对象,但是如果进程已经启动,Application对象已经创建,那么直接返回。下面调用BroadcastReveiver. onReceive函数,看一下这里传进去的Context类型,是一个奇怪的context.getReceiverRestrictedContext()对象,这里创建ReceiverRestrictedContext对象,并且mBase成员就是Application;

3) 向AMS发送处理结束消息
 

        静态广播是一种有序广播,处理结束后,需要通知AMS继续处理下一个广播接收者或者是下一条BroadcastRecord。


(文中用到的3个图摘自 http://blog.csdn.net/windskier/article/details/7251742,在此谢过 )

5避免广播ANR

         首先明确AMS只会对order模式的广播设置超时时间,而order模式有两种情形:

1) 发送者以sendOrder***方法发送广播,这时候无论是动态注册还是静态注册,AMS都会以order模式处理所有的接收者,也就是所有的接收者以同步方式处理,一般比较少;

2) 静态注册广播接收器;

        以上两种情况是可能出现广播ANR的情形。

        根据上面ANR超时的机制来看,只要保证order模式下BroadcastReveiver的处理能够尽快的返回。结合广播的超时机制,可以有以下两种避免超时的思路:

1) BroadcastReveiver.onReceive函数尽快返回

        如果需要完成一项比较耗时的工作,可以通过发送Intent给Service,由Service来完成,如果用子线程的方法来做,BroadcastReceiver的生命周期很短(在onReceive()执行后BroadcastReceiver的实例就会被销毁),子线程可能还没有结束BroadcastReceiver就先结束了。如果BroadcastReceiver结束了,它的宿主进程还在运行,那么子线程还会继续执行。但宿主进程此时很容易在系统需要内在时被优先杀死,因为它属于空进程(没有任何活动组件的进程),进程的优先级很低。

      进程优先级官方文档(需要翻墙):

              https://developer.android.com/guide/components/processes-and-threads.html?hl=zh-cn

2) 采用没有超时时间限制的非order模式的动态注册广播


相关文章推荐

Android系统中的广播(Broadcast)机制简要介绍和学习计划

在Android系统中,广播(Broadcast)是在组件之间传播数据(Intent)的一种机制;这些组件甚至是可以位于不同的进程中,这样它就像Binder机制一样,起到进程间通信的作用;本文通过一个...

老罗:Android系统中的广播(Broadcast)机制简要介绍和学习计划

博客源址:Android系统中的广播(Broadcast)机制简要介绍和学习计划 博客时间:2011-08-31 01:12        在Android系统中,广播(Broadcast)是...

Android 广播机制 详解

文章转自:http://www.cnblogs.com/TerryBlog/archive/2010/08/16/1801016.html 从现实生活中理解广播机制 一听到广播...

Android Broadcast广播机制分析

基于Android 6.0的源码剖析, 分析android广播的发送与接收流程。 一、概述 广播(Broadcast)机制用于进程/线程间通信,广播分为广播发送和广播接收两个过程,其中广播接收...

Android广播机制详解

概述在 Android 里面有各种各样的广播,比如电池的使用状态,电话的接收和短信的接收都会产生一个广播,应用程序开发者也可以监听这些广播并做出程序逻辑的处理。下面是一张粗略的图来帮助大家理解广播的运...

Android广播机制

1.Android广播机制概述 Android广播分为两个方面:广播发送者和广播接收者,通常情况下,BroadcastReceiver指的就是广播接收者(广播接收器)。广播作为Android组件...
  • dxpqxb
  • dxpqxb
  • 2016年12月01日 11:09
  • 393

Android广播机制——广播的发送

基于Android 7.0源码,分析Android广播机制的发送过程。 按照广播的类型,可以分为普通广播、有序广播和sticky广播。...

Android广播发送机制剖析【android广播系列二】

上篇博客大致说了说广播的注册机制,动态注册和静态注册广播的原理还不一样,动态广播最后HashMap中了,最后放到mReceiverResolver中,以后当ActivityManagerService...

Android 4.4 Kitkat Phone工作流程浅析(九)__状态通知流程分析

当手机Modem状态改变后会将状态变化信息通知到上层,通过《Android 4.4 Kitkat Phone工作流程浅析(八)__Phone状态分析》和《Android 4.4 Kitkat Phon...

Android Intent的用法及其传取值

原创文章,转载请注明出处! 用法: 1、启动一个Activity   (1)Activity.startActivity(Intent intent);  //启动一个Activity   (2)Ac...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android广播机制详解
举报原因:
原因补充:

(最多只允许输入30个字)