【再出发】Android11源码分析:静态广播是如何接收到消息的?

4,193 阅读7分钟

系列文章索引

并发系列:线程锁事

  1. 篇一:为什么CountDownlatch能保证执行顺序?

  2. 篇二:并发容器为什么能实现高效并发?

  3. 篇三:从ReentrientLock看锁的正确使用姿势

新系列:Android11系统源码解析

  1. Android11源码分析:Mac环境如何下载Android源码?

  2. Android11源码分析:应用是如何启动的?

  3. Android11源码分析:Activity是怎么启动的?

  4. Android11源码分析:Service启动流程分析

  5. Android11源码分析:静态广播是如何收到通知的?

  6. Android11源码分析:binder是如何实现跨进程的?(创作中)

  7. 番外篇 - 插件化探索:插件Activity是如何启动的?

  8. Android11源码分析: UI到底为什么会卡顿?

  9. Android11源码分析:SurfaceFlinger是如何对vsync信号进行分发的?(创作中)

经典系列:Android10系统启动流程

  1. 源码下载及编译

  2. Android系统启动流程纵览

  3. init进程源码解析

  4. zygote进程源码解析

  5. systemServer源码解析

前言

经过前面Activity,Service的分析,相信大家一定发现了,四大组件最大的特性就是支持跨进程

作为四大组件之一的广播,使用起来相当方便,可以通过注册广播接收系统消息,也可以方便的跨进程传递消息

今天我们就来分析下,广播是如何实现跨进程传递消息,以及静态广播,动态广播是如何注册和收发消息的

下面,正文开始!

注: 文章分析源码基于android11.0.0-r8,为方便阅读,部分源码有删减

广播的发送

发送广播在使用上非常简单,只需要调用sendBroadcast(intent)即可,我们从这个函数为切入点,来看看广播是怎么发送的

/frameworks/base/core/java/android/app/ContextImpl.java

public void sendBroadcast(Intent intent) {
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        //...略
        ActivityManager.getService().broadcastIntentWithFeature(...);
    }

可以看到这里通过binder调用,执行到了AMSbroadcastIntentWithFeature()函数(在android11的源码中,AMS的registerReceiver()函数已被标记为@deprecated)

在这个函数中,通过binder获取对应的BroadcastFilterList对象(即ReceiverList),并将BroadcastFilter添加到ReceiverList中,再通过broadcastQueueForIntent()函数获取到BroadcastQueue对象,通过queue进行并行广播分发enqueueParallelBroadcastLocked()和串行广播分发scheduleBroadcastsLocked,我们正常使用中发送广播都是串行的

代码如下

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public Intent registerReceiverWithFeature(...){
	//...略
	ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());//通过binder获取对应的BroadcastFilterList
    rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                        userId, receiver); 
  	BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
                    permission, callingUid, userId, instantApp, visibleToInstantApps);
                    
        rl.add(bf);  //添加到BroadcastFilter列表中
        mReceiverResolver.addFilter(bf);  
        BroadcastQueue queue = broadcastQueueForIntent(intent);//分发广播的队列
        BroadcastRecord r = new BroadcastRecord(...);
                    queue.enqueueParallelBroadcastLocked(r); //并行广播分发
                    queue.scheduleBroadcastsLocked(); //串行广播分发                        
                    //...略
    
}

广播分发的核心逻辑在BroadcastQueuescheduleBroadcastsLocked()中,函数中发送消息给BroadcastQueue的hander进行处理,真正的处理函数是processNextBroadcastLocked(),先对并行广播进行处理, 处理结束后在当广播为空时在while循环中获取下一条广播

先对动态广播BroadcastFilter调用deliverToRegisteredReceiverLocked()进行处理

再对静态广播ResolveInfo进行处理,由于静态广播可以在进程未启动的时候被执行,所以当启动静态广播时,如果进程未启动或被kill,需要先调用AMSstartProcessLocked()启动进程,然后把广播添加到mPendingBroadcast中供后续的逻辑处理

如果进程存在,则执行processCurBroadcastLocked()对当前的广播进行处理

由于processNextBroadcastLocked()代码过长,此处不再粘贴具体代码,感兴趣的可以自行查看

对于广播发送的逻辑,到这里就分析完成了

小结

对于广播的发送,涉及的主要类有ResolveInfo(静态广播),BroadcastFilter(动态广播),BroadcastQueue(广播队列),其中主要的处理逻辑在BroadcastQueueprocessNextBroadcastLocked()函数,对并行广播,动态广播,静态广播进行处理和分发。

动态广播由于需要动态注册,所以不需要考虑进程不存在的情况;

而静态广播是在清单文件中注册,且接收时可能进程还未启动,所以需要先判断进程是否存在,进程不存在时需要先拉起进程再进行处理

广播的注册

说完了广播的发送,我们再来看看广播是如何注册的

静态广播的注册

对于静态广播来说,需要在清单文件注册,而对清单文件进行解析并处理的服务是PackageManagerService(后面简称PMS)

作为系统服务,PMSAMS一样在SystemServer中被启动,PMS会对安装的APK文件进行解析,拿到清单文件后,再对各个标签进行解析,解析处理类为PackageParser,此处我们只关心receiver标签,具体的处理逻辑在parseBaseApplication()函数中,对标签进行解析后,会将解析后的Component对象添加到Packagereceivers列表中,当发送广播时,会从中获取到已添加的静态广播

这也是静态广播能在未启动时接收到消息的最重要的一步,具体代码如下

/frameworks/base/core/java/android/content/pm/PackageParser.java

private boolean parseBaseApplication(Package owner...){
	while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
                if (tagName.equals("receiver")) { //对广播标签进行解析
                //此处的activity不是应用层的Activity对象,而是一个Component对象
                Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
                        true, false); 
                //...略
                hasReceiverOrder |= (a.order != 0);
                owner.receivers.add(a); //加入receiver列表   

    }             
}

动态广播的注册

说完了PMS对清单文件的解析处理,我们再回到动态广播注册流程上来

注册动态广播需要调用ContextImplregisterReceiver()函数,此时需要创建出ReceiverDispatcher对广播进行分发,并调用AMSregisterReceiverWithFeature()函数对广播进行注册

其中ReceiverDispatcher的binder对象也通过参数传递给AMS,便于AMS与ReceiverDispatcher之前跨进程通信

ReceiverDispatcher的binder对象是它的内部类InnerDispatcher,继承了IIntentReceiver.Stub,之后分析中,在AMS中凡是使用IIntentReceiver调用的方法,最终都通过binder调用执行到了InnerDispatcher的函数

再回到AMSregisterReceiverWithFeature()函数上来,函数中通过receiver的binder句柄作为key获取到对应的BroadcastFilterList(第一次时为null,需要创建并添加到集合中),添加完成后调用broadcastQueueForIntent()获取到BroadcastQueue,调用scheduleBroadcastsLocked()执行广播分发流程,这部分我们上文已经做过分析

加餐:为什么广播onReceive耗时不能超过10s?

要解释这个问题,还要从源码上找答案,回到执行广播分发的函数processNextBroadcastLocked()

其中会调用mDispatchergetNextBroadcastLocked()函数获取下一条广播

: 此处的Dispatcher是AMS端分发逻辑的实现,并非应用端的ServiceDispatcher

如果当前广播处理超时,会调用broadcastTimeoutLocked()函数进行处理,而超时的时间为BroadcastConstants.TIMEOUT = 10000mills,也就是10秒

从广播的处理机制来看,消息的处理在客户端是串行处理的,所以各个广播在队列中按照先进先出的原则,依次等待着被处理,如果广播处理时间过长,必然会影响其他广播的receiver的执行

所以,切记广播中也是不能执行过于耗时的操作的哦!

总结

分析完之后,我们再来通过一张图回顾下大体流程

广播作为一套使用binder在系统层进行消息分发的跨进程通信方案,设计上就是供跨进程使用的。但很多情况下,我们会错误的将其作为应用内的消息框架,这也就导致了开发者感觉到的广播收发效率低的问题

针对应用内通信,正确的使用方式是使用LocalBroadcastManager发送本地广播来代替

最后

四大组件的分析目前完成了三个,而剩下的ContentProvider是开发中基本上使用较少的一个类,所以我们就暂时不对其进行分析了

通过这几篇文章的的分析,大家可能也感觉到了,四大组件的调用流程中,提到最多的就是binder调用,接下来我们也将专门对binder进行分析讲解,另外插件化相关的文章也会同步进行

相信有了这几篇文章作为基础,接下来的文章阅读也会轻松很多

今天的文章就写到这里了,我们下期文章再见!

你的点赞是我创作的最大动力,请多多点赞支持哦!