android四大组件之四-BroadCast实现原理分析
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
前言
一开始的目标是解决各种各样的ANR问题的但是我们知道ANR总体上分有四种类型这四种ANR类型有三种是和四大组件相对应的所以如果想了解ANR发生的根因对安卓四大组件的实现原理必须要懂。
所以作者会写一系列的文章来分析四大组建的实现原理同时也会写相应的文章来讲解四种类型的ANR是如何发生的。
本篇文章的配套文章介绍BroadCast中的ANR是如何产生的如下
本篇文章主要会讲以下内容
1.广播的基本介绍以及广播流程中涉及到的核心类介绍
2.动态广播接收者的注册流程
3.APP侧发送广播事件及系统侧接收
4.无序广播的发送流程
5.有序广播的发送流程
6.静态广播的发送流程
7.一些关于广播的扩展性问题
8.总结
PS本文基于android13的源码进行讲解。
一.基本概念和流程
1.1 广播流程核心类介绍
首先我们介绍整个流程中会涉及到的一些核心类。
ContextImpContext的最终实现类activityapplication中的各种功能最终都是委托给其来处理的广播功能自然也不例外。其负责和系统的AMS进行通信。
ActivityManagerService负责所有应用的Activity的流程实际上四大组件都是由AMS负责处理和分发的。至于为什么不单独拆开比如搞一个BroadcastManagerService估计是觉得类似广播功能简单不需要单独设计Service吧而且Service属于后台服务还是越少越好的。
BroadcastReceiver广播接收者
IntentResolver用来记录所广播接收者的对象
BroadCastQueue负责具体广播的分发工作。一种有三种分别对应前台后台离线。
1.2 广播基本流程
动态广播的整套流程相对于事件传播还是算简单的整套流程都在java层执行的不涉及到native流程。
广播首先按照注册方式来区分有动态广播和静态广播两种我们分开来讲
1.2.1 动态广播基本流程
动态广播流程图可以概括成下面这张图
首先是动态广播接收者注册其向AMS注册广播接收器AMS会把其注册信息记录到IntentResolver中。也就是说IntentResolver存放到了所有的动态广播接收器。
然后广播发出者向AMS发出广播这时候AMS首先会通过IntentResolver查找出所有的广播接收者然后会交给BroadcastQueue来执行传播流程。
1.2.2 静态广播基本流程
动态广播流程可以概括成下面这张图
静态广播自然是没有广播接收者注册流程的。
当广播发出者向AMS发出广播后AMS会首先通过PackageManager进行相关广播接收者相关配置的查询得到一个接收者集合receivers集合中元素的类型为ResolveInfo然后遍历集合执行相关广播的传播流程。
这里要额外说明的是静态广播和有序广播实际上是执行了一样的流程所以我们一起放到了第四章来讲解。
二.动态广播接收者注册流程
2.1 APP侧进行注册
广播分为两种动态广播和静态广播。动态广播是首先注册广播接收者。然后再发送广播如果存在对应的广播Action则会把事件传递到广播接收者上面。如果是静态广播走的是另外一个流程我们后面会讲。
整个动态广播接收者的注册的流程图如下我们来一一解释下
上面有说所有的Context类的操作其实最终都会交给ContextImpl来处理所以最终都会调用到ContextImpl的registerReceive方法。最终会调用到registerReceiverInternal()方法在这个方法中通过binder方法registerReceiverWithFeature通知AMS。
2.2 AMS一侧接收
AMS一侧自然接收的方法也是registerReceiverWithFeature。该方法中其实主要做了两件事
首先生成BroadcastFilter类型对象注册到IntentResolver中的mFilters和mActionToFilter中。
BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps);
if (rl.containsFilter(filter)) {
Slog.w(TAG, "Receiver with filter " + filter
+ " already registered for pid " + rl.pid
+ ", callerPackage is " + callerPackage);
} else {
rl.add(bf);
if (!bf.debugCheck()) {
Slog.w(TAG, "==> For Dynamic broadcast");
}
mReceiverResolver.addFilter(bf);
}
其次进行粘性广播对象的注册。高版本的粘性广播谷歌已经废弃了但是server侧的代码还没有删掉所以这里也就不扩展讲解了这里只列一下代码如下
这里也吐槽下谷歌的安卓源码项目总是不断添加新功能废弃的老功能不删导致项目代码越来越大编译后的项目也越来越大。
if (allSticky != null) {
ArrayList receivers = new ArrayList();
receivers.add(bf);
final int stickyCount = allSticky.size();
for (int i = 0; i < stickyCount; i++) {
Intent intent = allSticky.get(i);
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
null, null, -1, -1, false, null, null, null, null, OP_NONE, null,
receivers, null, 0, null, null, false, true, true, -1, false, null,
false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
}
2.3 小结
所以我们可以知道动态广播最终是注册到了mReceiverResolver里面的成员变量中其实现类是IntentResolver。首先注册到IntentResolver.mFilters上然后根据action的类型在具体负责注册到mActionToFilter或者mTypedActionToFilter上。
自然是注册到了mReceiverResolver这个对象上面那么下面讲动态广播传播的流程自然也会使用到这个对象。
三.广播事件发送以及系统侧接收
广播有多种类型无序广播有序广播粘性广播等。因为粘性在安卓的高版本已经废弃所以这里也就不讲了我们这里只讲无序广播和有序广播的流程。
3.1 APP侧发送
无序广播通过的是ContextImpl中sendBroadcast方法而有序广播通过的是sendOrderedBroadcast方法。无论哪种其实调用的都是AMS提供的binder方法broadcastIntentWithFeature只是参数不同而已。其实甚于粘性广播调用的也是这个方法。方法代码如下倒数第3个参数区分是否是有序广播倒数第2的参数区分是否是粘性广播。
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
rd, initialCode, initialData, initialExtras, receiverPermissions,
null /*excludedPermissions=*/, null, appOp, options, true, false,
user.getIdentifier());
3.2 系统侧接收
AMS一侧自然是broadcastIntentWithFeature方法接收的。主要执行的如下的流程
broadcastIntentWithFeature首先会通知到AMS的broadcastIntentLocked方法中。
在该方法中我们上面有讲过动态广播数注册到mReceiverResolver中的。所以这里自然先从mReceiverResolver中查询看是否存在接收者如果存在加入到registeredReceivers集合中。
代码如下
registeredReceivers = mReceiverResolver.queryIntent(intent,
resolvedType, false /*defaultOnly*/, userId);
3.3 系统侧处理广播
broadcastIntentLocked方法是系统册处理广播的核心下面代码的讲解也主要都是在broadcastIntentLocked方法中执行的。
首先会进行一些安全性的检查比如进程是否存在是否有对应广播的权限发送的是否是保护性广播比如开机广播这种就不可能允许任意进程随便发等等如果不具有对应的权限则返回失败。
然后进行粘性广播的逻辑判断粘性广播的逻辑这里就不扩展讲解了。
接下来会去查找静态广播接收者和动态广播接收者。我从代码中可以看到有两个集合分别是receivers和registeredReceivers对应的分别就是查找出来的静态广播接收者和动态广播接收者。
List receivers = null;
List<BroadcastFilter> registeredReceivers = null;
// Need to resolve the intent to interested receivers...
if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
== 0) {
receivers = collectReceiverComponents(
intent, resolvedType, callingUid, users, broadcastAllowList);
}
if (intent.getComponent() == null) {
if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
//这里是特殊逻辑不用关心
} else {
registeredReceivers = mReceiverResolver.queryIntent(intent,
resolvedType, false /*defaultOnly*/, userId);
}
}
由于动态广播和静态广播的处理逻辑是不一样的代码也是分开的所以我们接下来分别来讲这两者的流程。
四.动态广播事件传递流程
4.1 本章概要
本章内容主要都是发生在AMS中的broadcastIntentLocked方法中。
动态广播流程中首先我们要查找动态广播的接受者这个我们放到4.2小节中来讲
然后开启广播的发送流程因为有序广播和无序广播的流程是不一样的所以4.3我们主要讲一下两者的区别
4.4中我们讲一下无序广播的发送流程有序广播的流程我们下一章来讲
最后的4.5小节我们讲一下server侧的广播是如何传递到APP一侧的。
4.2 查找动态广播接收者
这里对应的核心就是上面的queryIntent方法中。queryIntent方法中按照不同类型进行查询
如果resolvedType不为空则从mTypeToFiltermBaseTypeToFiltermTypedActionToFiltermWildTypeToFilter等集合查询。
如果scheme不为空则从mSchemeToFilter集合中查询
如果resolvedType为空则从mActionToFilter集合中查询
最后会把上面查找的所有结果按照优先级进行排序最终进行返回返回值就是registeredReceivers集合。如果同一个广播满足多个条件也只会在集合中存在一份。
4.3 分别开启无序广播和有序广播发送流程
找到了广播接收者之后我们就可以准备开始广播流程了。这里无序广播和有序广播的流程是不一样的。
4.3.1 首先我们来看下无序广播
//代码有删减只保留核心内容
int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
if (!ordered && NR > 0) {
final BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(...);
...
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
我们可以看到首先查找对应的BroadcastQueueBroadcastQueue是负责具体发送任务的。分别前台后台离线三种。这里对应的其实也就是我们发送广播时的前台/后台广播。
然后生成广播对象BroadcastRecord加入到BroadcastQueue中mParallelBroadcasts集合中。
最后通过scheduleBroadcastsLocked开启广播流程。
4.3.2 然后我们来看下有序广播流程
首先把上面动态广播接收者集合registeredReceivers中的对象都加入到receivers中。也就是说后续流程中receivers集合其实是包含了有序广播和静态广播的集合receivers中同时存在两种元素类型BroadcastFilter和ResolveInfo。相关代码如下
void broadcastIntentLocked(){
...
while (ir < NR) {
if (receivers == null) {
receivers = new ArrayList();
}
receivers.add(registeredReceivers.get(ir));
ir++;
}
...
}
然后处理receivers集合中的对象该流程和无序广播的流程基本上是一致的。
首先根据receivers生成广播对象BroadcastRecord然后加入到BroadcastQueue.BroadcastDispatcher中的mOrderedBroadcasts集合中最后通过scheduleBroadcastsLocked方法开启广播流程。
if ((receivers != null && receivers.size() > 0)
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(...);
queue.enqueueOrderedBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
4.3.3 最后我们来看下两者区别
有序的流程前面和无序广播前面的流程是一样的也是说4.2.4之前有序和无序执行的是一样的流程。而区别就是从4.2.4开始的我花了一张简单的流程图来展示一下两者的区别。
有序广播和无序广播的区别就是加入到的BroadcastQueue的集合对象不一样有序广播会把查找到的广播接对象BroadcastRecord加入到BroadcastDispatcher中的mOrderedBroadcasts集合而无序广播则会加入到mParallelBroadcasts中。
但是后续的广播发送流程是一致的两者都会调用scheduleBroadcastsLocked方法开启广播流程该流程中会先处理无序广播的流程然后再执行有序广播的流程最后执行静态广播的流程。
4.4 发送无序广播流程
本章主要讲无序广播的流程有序广播的我们下一章再讲。所以通过scheduleBroadcastsLocked方法开启广播流程后我们接着往下看
1.scheduleBroadcastsLocked中首先判断是否正在发送的流程中。如果正在发送流程中则直接忽略本次操作否则进入发送流程。
public void scheduleBroadcastsLocked() {
if (mBroadcastsScheduled) {
return;
}
mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
mBroadcastsScheduled = true;
}
2.通过handler转发之后切换到主线程执行processNextBroadcast方法。processNextBroadcast方法中加了个锁调用processNextBroadcastLocked方法解决多线程问题。
为什么会有多线程问题实话实说我认为这里是一个源码中疏忽的地方因为经过handler的切换这里一定会是在系统侧的主线程执行就不可能存在多线程的问题。
//线程切换
case BROADCAST_INTENT_MSG: {
processNextBroadcast(true);
//加锁
private void processNextBroadcast(boolean fromMsg) {
synchronized (mService) {
processNextBroadcastLocked(fromMsg, false);
}
}
3.因为无序广播和有序广播其实都通过processNextBroadcastLocked方法来执行发送流程的所以这里讲processNextBroadcastLocked方法的时候先只讲无序广播的部分下一小节再讲有序广播的部分。
该方法中首先遍历mParallelBroadcasts集合针对每个广播事件记录BroadcastRecord通过deliverToRegisteredReceiverLocked方法进行对应的客户端进程mParallelBroadcasts集合中的BroadCastRecord就是我们刚刚添加的。
至此动态广播中的无序广播发送流程已经完成此时的客户端已经成功收到了无序广播。deliverToRegisteredReceiverLocked方法负责把具体的广播事件发送到APP一侧这个流程我们4.5中来讲。
4.5 把广播事件传递给客户端
这一小节其实主要就是针对deliverToRegisteredReceiverLocked方法的讲解。
首先是进行一列的检查如下
1.进行相关的权限检查看发送者具有有接收者所要求的相关权限
if (filter.requiredPermission != null) {
int perm = mService.checkComponentPermission(filter.requiredPermission,
r.callingPid, r.callingUid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
Slog.w(TAG, "Permission Denial: broadcasting "
...
skip = true;
} else {
...
}
}
2.检查接收者是否为空
if (!skip && (filter.receiverList.app == null || filter.receiverList.app.isKilled()
|| filter.receiverList.app.mErrorState.isCrashing())) {
Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
+ " to " + filter.receiverList + ": process gone or crashing");
skip = true;
}
3.进行一系列其他的安全检查
4.检查启动安全模式高版本是严格低版本放的比较松。之前作者就在这里踩过坑。
if (!skip) {
final int allowed = mService.getAppStartModeLOSP(
info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
// We won't allow this receiver to be launched if the app has been
// completely disabled from launches, or it was not explicitly sent
// to it and the app is in a state that should not receive it
// (depending on how getAppStartModeLOSP has determined that).
if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
Slog.w(TAG, "Background execution disabled: receiving "
+ r.intent + " to "
+ component.flattenToShortString());
skip = true;
} else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
|| (r.intent.getComponent() == null
&& r.intent.getPackage() == null
&& ((r.intent.getFlags()
& Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
&& !isSignaturePerm(r.requiredPermissions))) {
mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
component.getPackageName());
Slog.w(TAG, "Background execution not allowed: receiving "
+ r.intent + " to "
+ component.flattenToShortString());
skip = true;
}
}
}
5.调用performReceiveLocked方法完成向APP的发送。
performReceiveLocked方法中首先获取APP侧的Binder对象IApplicationThread然后通过其binder方法scheduleRegisteredReceiver发送给APP一侧或者直接通过binder对象完成跨进程的通信。
void performReceiveLocked(...) throws RemoteException {
if (app != null) {
final IApplicationThread thread = app.getThread();
if (thread != null) {
try {
thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
data, extras, ordered, sticky, sendingUser,
app.mState.getReportedProcState());
...
}else{
receiver.performReceive(intent, resultCode, data, extras, ordered,
sticky, sendingUser);
}
}
6.APP一侧自然是ActivityThread中的scheduleRegisteredReceiver方法完成的接收其逻辑也很简单直接交给binder对象receiver来处理这逻辑就和5中的else逻辑一致了。
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
int resultCode, String dataStr, Bundle extras, boolean ordered,
boolean sticky, int sendingUser, int processState) throws RemoteException {
updateProcessState(processState, false);
receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
sticky, sendingUser);
}
7.receiver是一个binder其server是在LoadedApk.ReceiverDispatcher中的InnerReceiver类。所以最终是InnerReceiver中的performReceive方法收到了这个binder通知。方法如下
@Override
public void performReceive(...) {
final LoadedApk.ReceiverDispatcher rd;
...
if (rd != null) {
rd.performReceive(intent, resultCode, data, extras,
ordered, sticky, sendingUser);
} else {
...
}
}
我们所以看到又调用到了LoadedApk.ReceiverDispatcher中的performReceive方法。
8.LoadedApk.ReceiverDispatcher的performReceive方法如下
public void performReceive(...){
...
if (intent == null || !mActivityThread.post(args.getRunnable())) {
if (mRegistered && ordered) {
IActivityManager mgr = ActivityManager.getService();
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
"Finishing sync broadcast to " + mReceiver);
args.sendFinished(mgr);
}
}
}
我们可以看到通过handler进行一个线程切换切换到主线程执行Args中的Runnable。
9.那么我们最后就来看下这个在主线程中将要被执行的Args中的Runnable代码如下
public final Runnable getRunnable() {
return () -> {
final BroadcastReceiver receiver = mReceiver;
final boolean ordered = mOrdered;
...
final IActivityManager mgr = ActivityManager.getService();
final Intent intent = mCurIntent;
...
try {
ClassLoader cl = mReceiver.getClass().getClassLoader();
intent.setExtrasClassLoader(cl); intent.prepareToEnterProcess(ActivityThread.isProtectedBroadcast(intent),
mContext.getAttributionSource());
setExtrasClassLoader(cl);
receiver.setPendingResult(this);
receiver.onReceive(mContext, intent);
} catch (Exception e) {
...
}
if (receiver.getPendingResult() != null) {
finish();
}
};
}
通过代码我们可以看到主要做了两件事
首先因为其持有BroadcastReceiver对象receiver所以直接通知了BroadcastReceiver的onReceive方法。
然后如果是有序广播还要调用finish方法通知系统已完成此次广播的传递流程。
五.有序广播的传播流程
5.1 有序广播的主要流程
有序广播也是通过scheduleBroadcastsLocked方法开启的流程其前面的流程执行流程和无序广播是一样的区别是从processNextBroadcastLocked方法开始的。
processNextBroadcastLocked中首先处理有序广播 无序广播处理完成后就开始有序广播的处理流程了甚至静态广播的处理流程也在该方法的最后。
有序广播按照我的理解主要分为以下4个阶段
1.找到待发送的有序广播对象
2.执行一系列的安全判断
3.发送前的准备操作
4.执行有序广播的发送
5.2 找到待发送的广播对象
有序广播的发送流程主要是BroadcastDispatcher来负责的。其首先通过getNextBroadcastLocked方法找出下一个该发送哪个广播这个广播对象就是BroadcastRecord。
r = mDispatcher.getNextBroadcastLocked(now);
该方法中只要执行以下的逻辑
public BroadcastRecord getNextBroadcastLocked(final long now) {
//逻辑1
if (mCurrentBroadcast != null) {
return mCurrentBroadcast;
}
final boolean someQueued = !mOrderedBroadcasts.isEmpty();
BroadcastRecord next = null;
//逻辑2
if (!mAlarmBroadcasts.isEmpty()) {
next = popLocked(mAlarmBroadcasts);
if (DEBUG_BROADCAST_DEFERRAL && next != null) {
Slog.i(TAG, "Next broadcast from alarm targets: " + next);
}
}
//逻辑3
if (next == null && !mDeferredBroadcasts.isEmpty()) {
// We're going to deliver either:
// 1. the next "overdue" deferral; or
// 2. the next ordinary ordered broadcast; *or*
// 3. the next not-yet-overdue deferral.
for (int i = 0; i < mDeferredBroadcasts.size(); i++) {
Deferrals d = mDeferredBroadcasts.get(i);
if (now < d.deferUntil && someQueued) {
// stop looking when we haven't hit the next time-out boundary
// but only if we have un-deferred broadcasts waiting,
// otherwise we can deliver whatever deferred broadcast
// is next available.
break;
}
if (d.broadcasts.size() > 0) {
next = d.broadcasts.remove(0);
// apply deferral-interval decay policy and move this uid's
// deferred broadcasts down in the delivery queue accordingly
mDeferredBroadcasts.remove(i); // already 'd'
d.deferredBy = calculateDeferral(d.deferredBy);
d.deferUntil += d.deferredBy;
insertLocked(mDeferredBroadcasts, d);
if (DEBUG_BROADCAST_DEFERRAL) {
Slog.i(TAG, "Next broadcast from deferrals " + next
+ ", deferUntil now " + d.deferUntil);
}
break;
}
}
}
//逻辑4
if (next == null && someQueued) {
next = mOrderedBroadcasts.remove(0);
if (DEBUG_BROADCAST_DEFERRAL) {
Slog.i(TAG, "Next broadcast from main queue: " + next);
}
}
mCurrentBroadcast = next;
return next;
}
1.如果当前的广播还未处理完成则直接返回当前正在处理的广播mCurrentBroadcast。
2.如果有定时广播则从定制广播中取出第一个返回。
3.如果有延迟广播则从延迟广播中取出第一个返回。
4.上面都没有的情况下最后轮到了有序广播。从有序广播的队列mOrderedBroadcasts中取出第一个返回。上面我们有讲到mOrderedBroadcasts集合的加入是在5.3中完成的。
5.3 执行一系列的安全判断
找到了待发送的广播对象后就开始执行一系列的安全检查了。
//流程1
if (r == null) {
return;
}
//流程2
int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
if ((numReceivers > 0) &&
(now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
broadcastTimeoutLocked(false); // forcibly finish this broadcast
forceReceive = true;
r.state = BroadcastRecord.IDLE;
}
//流程3
if (r.state != BroadcastRecord.IDLE) {
return;
}
//流程4
if (r.receivers == null || r.nextReceiver >= numReceivers
|| r.resultAbort || forceReceive) {
...
mDispatcher.retireBroadcastLocked(r);
r = null;
looped = true;
}
1.如果对象为空则直接结束掉当前流程
2.进行相关的超市判断如果超时则把forceReceive设置为true。
3.如果当前广播对象不属于空闲 则说明已经进入到了发送流程中则直接结束掉当前流程。
4.最终进行一系列的安全判断满足下面任意条件则进入到异常场景并进行异常处理。异常处理的最后会把r设置为null进行下一轮循环找到合适的BroadcastRecord对象。
广播对象的接收者为空
广播对象当前执行到的位置已经大于等于接收者的总数
广播对象被放弃
广播对象已经满足超时条件了2中已经进行了判断。
5.4 发送前的准备操作
经过一系列的判断找到了合法的将要被处理的广播对象r则首先进行一些发送前的准备操作。
1.因为这里是有序广播的流程所以首先看当前已经执行到了第几个接收者了。
// Get the next receiver...
int recIdx = r.nextReceiver++;
2.重置发送时间广播的超时是和这个值相关的。
r.receiverTime = SystemClock.uptimeMillis();
3.如果有序广播是第0位则会更新两个时间值如下
if (recIdx == 0) {
r.dispatchTime = r.receiverTime;
r.dispatchClockTime = System.currentTimeMillis();
...
}
更新BroadcastRecord的dispatchTime和dispatchClockTime时间。dispatchTime时间可以理解为开始执行广播流程的时间而上面的receiverTime时间可以理解为通知单个广播接受者使用的时间。
何时超时我们放到广播类型的ANR那篇文章中来讲。
4.发送一个延时handler消息用来监测是否整个广播流程是否超时。
if (! mPendingBroadcastTimeoutMessage) {
setBroadcastTimeoutLocked(timeoutTime);
}
5.5 执行有序广播的发送
好了经过一系列的操作我们终于可以开始发送广播了。
1.取出下一个广播接收者。判断其类型如果是BroadcastFilter类型说明是动态广播接受者则执行发送流程。如果是ResolveInfo类型则说明是静态广播接收者我们第六章来讲。
final BroadcastOptions brOptions = r.options;
final Object nextReceiver = r.receivers.get(recIdx);
if (nextReceiver instanceof BroadcastFilter) {
//动态有序广播流程
deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
return;
}
//静态广播流程
2.执行最终的发送流程。
最终的发送流程方法都在deliverToRegisteredReceiverLocked中这个方法我们在4.3小节中讲了这里就不赘述了。
六.静态广播的传播流程
6.1 静态广播的主要流程
静态广播的整个执行流程其实也是在processNextBroadcastLocked方法中它和无序广播有序广播是一样的只不过在这个方法中最后被执行。
上面说5.5中讲到从BroadcastRecord中的receivers中取出对象如果是BroadcastFilter类型执行动态有序广播流程如果是ResolveInfo类型则会执行静态广播流程了。
这里要注意的是无论静态广播还是有序广播都属于同一个BroadcastRecord对象。也就是说发送一个有序广播其接收者有可能是静态广播接收者也有可能是动态广播接收者而且属于同一批次的接收者。
静态广播主要有如下流程
1.静态广播的准备工作
2.接收者进程存活情况下通知接收者
3.接收者进程不存活情况下则拉起接收者进程
4.权限检查详解。
6.2 静态广播的准备工作
6.2.1构建对象
既然不是BroadcastFilter类型那么一定是ResolveInfo类型所以直接转换为ResolveInfo对象。
然后构建ComponentName对象供后面使用。
ResolveInfo info = (ResolveInfo)nextReceiver;
ComponentName component = new ComponentName(
info.activityInfo.applicationInfo.packageName,
info.activityInfo.name);
6.2.2 权限检查
接下来会执行一系列的权限检查。这一块检查会有很多项目因为不算事是核心流程所以我们放到6.5中专门来讲这里暂且先跳过权且认为权限检查已通过。
boolean skip = false;
//各种判断如果不通过就会把skip改为true
if (skip) {
r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
r.receiver = null;
r.curFilter = null;
r.state = BroadcastRecord.IDLE;
r.manifestSkipCount++;
scheduleBroadcastsLocked();
return;
}
6.2.3 准备工作
最后做一些发送前的准备工作首先更新BroadcastRecord中的一些状态值
r.manifestCount++;
r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
r.state = BroadcastRecord.APP_RECEIVE;
r.curComponent = component;
r.curReceiver = info.activityInfo;
然后修改对应包名的状态使其暂时成为不可杀死应用。
// Broadcast is being executed, its package can't be stopped.
try {
AppGlobals.getPackageManager().setPackageStoppedState(
r.curComponent.getPackageName(), false, r.userId);
}
6.3 接收者进程存活情况下通知接收者
如果APP进程还存活则会通过processCurBroadcastLocked方法执行发送流程发送广播给对应的进程。
if (app != null && app.getThread() != null && !app.isKilled()) {
processCurBroadcastLocked(r, app);
}
我们再来看一下processCurBroadcastLocked方法如下
private final void processCurBroadcastLocked(BroadcastRecord r,
ProcessRecord app) throws RemoteException {
...
//流程1
final IApplicationThread thread = app.getThread();
if (thread == null) {
throw new RemoteException();
}
if (app.isInFullBackup()) {
skipReceiverLocked(r);
return;
}
//流程2
r.receiver = thread.asBinder();
r.curApp = app;
final ProcessReceiverRecord prr = app.mReceivers;
prr.addCurReceiver(r);
app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
mService.updateLruProcessLocked(app, false, null);
...
r.intent.setComponent(r.curComponent);
boolean started = false;
try {
//流程3
thread.scheduleReceiver(new Intent(r.intent), ...);
started = true;
} finally {
...
}
}
主要做了三件事
1.检查对应进程的binder是否存在不存在就无法和APP进行通信自然抛出异常终止流程。
2.更新BroadcastRecord对象的属性值以及BroadcastRecord中Intent的属性值这样最终发送到APP侧后APP可以知道应该启动哪个接收者。
3.通过binder方法scheduleReceiver完成跨进程通信通知APP一侧。
6.4 接收者进程不存活情况下通知接收者
如果APP进程不存活则通过两步操作来完成通知接收者的操作
首先拉起广播接收者所在的进程
然后在拉起后通知APP一侧。
6.4.1 拉起广播接收者所在的进程
//流程1
r.curApp = mService.startProcessLocked(...)
//流程2
if (r.curApp == null) {
finishReceiverLocked(r, r.resultCode, r.resultData,r.resultExtras, r.resultAbort, false);
scheduleBroadcastsLocked();
r.state = BroadcastRecord.IDLE;
return;
}
//流程3
mPendingBroadcast = r;
mPendingBroadcastRecvIndex = recIdx;
主要做了三件事
1.直接通过AMS进行拉起接收者所属进程的操作。
2.如果拉起失败则直接结束掉当前的广播流程
3.拉起成功则修改mPendingBroadcast和mPendingBroadcastRecvIndex为当前广播对象的值。这样下一次执行广播流程就会处理。
6.4.2 PendingBroadcast阻塞和检查
我们回头看一下processNextBroadcastLocked方法发现在完成无序广播的发送后有这样一段代码来处理mPendingBroadcast对象。
//流程1
if (mPendingBroadcast != null) {
boolean isDead;
//流程2
if (mPendingBroadcast.curApp.getPid() > 0) {
synchronized (mService.mPidsSelfLocked) {
ProcessRecord proc = mService.mPidsSelfLocked.get(
mPendingBroadcast.curApp.getPid());
isDead = proc == null || proc.mErrorState.isCrashing();
}
} else {
//流程3
final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
isDead = proc == null || !proc.isPendingStart();
}
if (!isDead) {
//流程4
// It's still alive, so keep waiting
return;
} else {
//流程5
mPendingBroadcast.state = BroadcastRecord.IDLE;
mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
mPendingBroadcast = null;
}
}
1.mPendingBroadcast不为空说明刚刚启动过进程存在静态广播。所以这里推测APP进程启动后应该会通知系统侧进行某个流程置空mPendingBroadcast并完成发送这个我们后面来讲。
2.pid>0只是代表进程创建成功但是并不代表进程存活所以还要再次检查一下。最后更新到isDead这个变量上。
3.如果创建不成功并不代表当前执行时还未成功则从进程列表中查询一边判断进程是否存活更新到isDead上。
4.如果进程存活则直接返回继续等待。也就是说静态广播和有序广播都被阻塞住。
5.如果进程不存活则直接结束流程并且把相关变量进行重置。
这个流程我们可以得出以下的结论
1.mPendingBroadcast存在时有序广播和静态广播是会被阻塞住的。
2.如果mPendingBroadcast所对应的进程启动失败则会立马进行重置避免阻塞。
6.4.3 通知APP一侧
接着我们看一下到底是哪里来处理mPendingBroadcast并完成广播发送的。
这里我们得稍微回顾一下APP的启动流程了如果对APP启动流程感兴趣可以看一下下面的这篇文章。
我们这里稍微讲一下APP进程启动后会调用ActivityThread的main方法main方法中会调用binder方法attachApplication通知系统侧APP进程创建完成这里就会调用到AMS中的attachApplication方法经过加锁后调用到attachApplicationLocked方法看一下这个方法
private boolean attachApplicationLocked(...) {
//逻辑1
thread.bindApplication(...)
//逻辑2
didSomething |= sendPendingBroadcastsLocked(app);
...
}
1.通知APP区创建Application完成初始化操作。
2.去执行pending的广播的发送。
逻辑2中会最终调用到BroadcastQueue中的sendPendingBroadcastsLocked方法我们看下这个方法
public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
boolean didSomething = false;
//逻辑1
final BroadcastRecord br = mPendingBroadcast;
if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
if (br.curApp != app) {
return false;
}
try {
//逻辑2
mPendingBroadcast = null;
//逻辑3
processCurBroadcastLocked(br, app);
didSomething = true;
} catch (Exception e) {
...
}
}
return didSomething;
}
首先获取mPendingBroadcast对象逻辑1
然后把mPendingBroadcast置为null这也正好对应了我们上一小节的推测逻辑2
最终调用processCurBroadcastLocked方法完成广播的发送processCurBroadcastLocked方法我们6.3中已经讲过了逻辑3。
6.5 静态广播权限检查
上面讲到了发送静态广播需要对接收者经验很多的权限检查因为检查条件太多了所以我们挑几个重要的来讲。
1.检查接收者targetSDK版本的问题如果不再发送广播的目标范围内则检查不通过。
ResolveInfo info = (ResolveInfo)nextReceiver;
if (brOptions != null &&
(info.activityInfo.applicationInfo.targetSdkVersion
< brOptions.getMinManifestReceiverApiLevel() ||
info.activityInfo.applicationInfo.targetSdkVersion
> brOptions.getMaxManifestReceiverApiLevel())) {
...
skip = true;
}
2.检查发送进程和接收进程是否存在关联关系。如果两者其一属于系统集成则被允许。
if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
skip = true;
}
3.检查接收者是否声明了export以及是否具体接收者组件所声明的权限。也就是说只有赋予了接收者APP对应的权限才能够接收。
int perm = mService.checkComponentPermission(info.activityInfo.permission,
r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
info.activityInfo.exported);
if (!skip && perm != PackageManager.PERMISSION_GRANTED) {
skip = true;
} else if (!skip && info.activityInfo.permission != null) {
if (opCode != AppOpsManager.OP_NONE &...) {
skip = true;
}
}
4.即时应用是不允许发送的
if (!skip && r.callerInstantApp
&& (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0
&& r.callingUid != info.activityInfo.applicationInfo.uid) {
Slog.w(TAG, "Instant App Denial: receiving "
+ r.intent
+ " to " + component.flattenToShortString()
+ " requires receiver have visibleToInstantApps set"
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")");
skip = true;
}
5.发送者进程如果出现Crash也是不允许发送的
if (r.curApp != null && r.curApp.mErrorState.isCrashing()) {
skip = true;
}
6.这一块按照我的理解是是否暂停了接收者的应用吧应用暂停则不可接收应用暂停时android12.1开始推出的新功能。
isAvailable = AppGlobals.getPackageManager().isPackageAvailable(
info.activityInfo.packageName,UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
if (!isAvailable) {
skip = true;
}
7.检查APP检查模式。基本上是按照APP配置的targetSDK版本来区分的高版本检查很严格低版本松之前作者就在这里踩过坑。
final int allowed = mService.getAppStartModeLOSP(...);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
skip = true;
}else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
|| (r.intent.getComponent() == null
&& r.intent.getPackage() == null
&& ((r.intent.getFlags()
& Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
&& !isSignaturePerm(r.requiredPermissions))) {
skip = true;
}
8.关闭应用类型的广播收到运行状态的限制。
if (!skip && !Intent.ACTION_SHUTDOWN.equals(r.intent.getAction())
&& !mService.mUserController
.isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),0 )) {
skip = true;
}
9.非系统进程还要检查接受者是否有发送者所要求的权限
if (!skip && info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
r.requiredPermissions != null && r.requiredPermissions.length > 0) {
...代码略
skip = true;
}
我们可以看到静态广播的各种限制要比动态广播多得多。这是因为静态广播可以直接唤起一个新的APP应用这个影响面还是很大的。比如我一个APP如果注册了静态广播中的开屏广播那么每次每次亮屏都会启动我的APP实现了一个保活这影响面无疑是很大的所以一定要做一定的限制。目前普通的APP基本上已经无法通过注册静态广播的方式来使用了。
七.关于BroadCast扩展性问题
1.如果两个应用注册同样action的广播接收者会怎样
答首先要去分有序广播还是无序广播。如果无序广播那么所有的的广播接收者都会收到通知。如果是有序广播那么就会按照优先级依次收到通知。
2.广播是否可靠
答不可靠。进程如果不存在或者挂掉直接结束了流程或者发送者进程挂掉也会导致静态广播接收者收不到。
3.动态广播接收者优先级99静态广播接收者优先级100哪个会先收到
答虽然流程上动态广播会优先执行。但是由于有序广播其实每次只处理一个广播对象中广播接收者集合中的一条。所以仍然会按照广播接收者集合中的顺序来执行。相关核心代码如下
final Object nextReceiver = r.receivers.get(recIdx);
4.使用静态广播时Application中有耗时操作会有什么问题
答这要有一个前提条件就是APP进城不存在存在的话就不会走Application流程。
虽然系统侧会连续发送两条任务创建application和发送广播两条任务给APP一侧但是由于都在主线程执行所以执行顺序上并不会出现问题。
但是由于静态广播创建新进程时是阻塞的后面的有序广播时不能发送的因此系统会有ANR超时机制时间过长是会导致ANR的。
5.发送完广播后发送者进程闪退了接收者能否收到
答参照6.5中静态广播严格的检查发送者crash了静态广播应该是收不到的动态可以。
6.前台广播和后台广播有什么区别
答从目前代码看来前台和后台所归属的BroadcastQueue是不同的所以相互之间不会阻塞。即发送前台广播阻塞时并不会影响后台广播的发送。另外前后台广播和后台广播的时间是不一样的分别为10S和60S。
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
static final int BROADCAST_BG_TIMEOUT = 60 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
其它流程基本没有区别。
7.发送无序广播并且注册多个带不同优先级的静态广播接收者优先级是否会生效
答是生效的上面有介绍。静态广播和有序广播走的其实是一个流程。所以对于静态广播接收者哪怕发送的是普通广播类型一样会按照优先级进行分发。
但是如果此时同样注册有动态广播接收者那么一定是动态广播接收者先收到这个广播然后静态广播接收者在按照优先级依次完成接收。
8.abortBroadcast是如何结束掉广播流程的
答abortBroadcast方法会修改BroadcastReceiver中的属性值mAbortBroadcast为true。一次有序广播事件APP完成后都会发送sendFinish方法通知系统侧我们看一下这个方法。
public void sendFinished(IActivityManager am) {
if (mOrderedHint) {
am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
mAbortBroadcast, mFlags);
} else {
am.finishReceiver(mToken, 0, null, null, false, mFlags);
}
}
可以看到如果是有序广播会把mAbortBroadcast这个值传递给系统侧。AMS收到会会转交给处理队列BroadcastQueue中的finishReceiverLocked方法最终更新到广播事件对象BroadcastQueue中的属性值resultAbort上。
if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
r.resultAbort = resultAbort;
}
后续BroadcastQueue队列在处理广播对象时识别到resultAbort=true就会略过。
if (r.receivers == null || r.nextReceiver >= numReceivers
|| r.resultAbort || forceReceive) {
...
r = null
continue;
}
9.有序和无序广播是怎么区分的
答sendOrderedBroadcast和sendBroadcast两个方法其实最终broadcastIntentWithFeature方法中就是一个参数不一样倒数第三个boolean serialized。sendOrderedBroadcast为truesendBroadcast为false。
10.静态广播的接收者对象是否是同一个
答结论有可能和你想象的并不一样。静态广播每次的广播接收者都是新生成的所以不在再静态广播的构造方法中做耗时操作。
receiver = packageInfo.getAppFactory().instantiateReceiver(cl, data.info.name, data.intent);
public @NonNull BroadcastReceiver instantiateReceiver(...)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (BroadcastReceiver) cl.loadClass(className).newInstance();
}
八.总结
讲了这么多每个章节也都包涵很多知识点所以没有点储备有可能很难理解。所以最后来做一个总结
首先我们按照注册方式的不同分成动态广播和静态广播。动态广播最终会把广播接收者注册到IntentResolver中而静态广播没有注册流程只需要在manifest中声明即可。使用时PackageManager会遍历所有APP的manifest进行查找。
然后我们在按照发送方式的不同分成无序广播和有序广播其中静态广播都属于有序广播类型。无序广播是发送方发送广播后所有接收者并行接收的。有序广播是发送方发送广播后依次接收到广播事件优先级高的先收到甚至可以在接收者中结束掉当前广播流程。
有序广播中我们在分出来一种特殊类型就是静态广播。如果接收者进程存在则直接发送给对应的进程如果进程不存在则首先会创建APP进程在APP创建完成通知系统侧后在进行广播事件的发送。
最后用一张图来总结一下方便记录和理解。