zl程序教程

您现在的位置是:首页 >  移动开发

当前栏目

说说Android的广播(3) - 什么样的广播是并发的?

Android并发 什么样 广播
2023-09-14 09:02:06 时间
什么样的广播是并发的?

现在让我们开始破解Android中的一个trick,普通广播都是并发的吗?
带着这个问题,我们来看ActivityManagerService.broadcastIntentLocked中的实现逻辑。

broadcastIntentLocked中的细节很多,我们放到后面讲,我们先只把跟并发和串行队列有关的部分专门提炼出来。

receivers和registeredReceivers

画重点了,画重点了啊!下面我们要学习两个重要的概念,一个是receivers,另一个是registeredReceivers。这两个东西的区别真正决定了它是能并发接收到广播还是串行地接收广播。

通俗地讲,registeredReceivers就是通过Java代码在运行时注册的receiver,而receivers是连通过AndroidManifest.xml中注册的也算上。
如果想只发给动态注册的BroadcastReceiver,可以设置有个FLAG_RECEIVER_REGISTERED_ONLY属性。

在处理普通广播处理的流程时,就先定义了两个List,一个是receivers,另一个是registeredReceivers.

16820 // Figure out who all will receive this broadcast.

16821 List receivers = null;

16822 List BroadcastFilter registeredReceivers = null;

我们分别看看,它们都是在哪里被赋值的:

如刚才所说,只有在不设FLAG_RECEIVER_REGISTERED_ONLY的情况下,才需要查完整的receiver列表。

16823 // Need to resolve the intent to interested receivers...

16824 if ((intent.getFlags() Intent.FLAG_RECEIVER_REGISTERED_ONLY)

16825 == 0) {

16826 receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);

16827 }

如果intent.getComponent() == null,则通过queryIntent方法来查询registeredReceivers。

16828 if (intent.getComponent() == null) {

16847 registeredReceivers = mReceiverResolver.queryIntent(intent,

16848 resolvedType, false, userId);

16850 }

好,两个receiver相关的列表都被赋值好了。下面揭晓奇迹的时候来了,究竟是哪个被翻牌子了呢?

16858 int NR = registeredReceivers != null ? registeredReceivers.size() : 0;

16859 if (!ordered NR 0) {

16860 // If we are not serializing this broadcast, then send the

16861 // registered receivers separately so they dont wait for the

16862 // components to be launched.

16863 final BroadcastQueue queue = broadcastQueueForIntent(intent);

16864 BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,

16865 callerPackage, callingPid, callingUid, resolvedType, requiredPermissions,

16866 appOp, brOptions, registeredReceivers, resultTo, resultCode, resultData,

16867 resultExtras, ordered, sticky, false, userId);

16868 if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);

16869 final boolean replaced = replacePending queue.replaceParallelBroadcastLocked(r);

16870 if (!replaced) {

16871 queue.enqueueParallelBroadcastLocked(r);

16872 queue.scheduleBroadcastsLocked();

16873 }

16874 registeredReceivers = null;

16875 NR = 0;

16876 }

没错,是非ordered的registered receivers被并发执行了。

那么receivers是如何被处理的呢?一句话,被当成ordered处理了.
我们看代码:

16950 if ((receivers != null receivers.size() 0)

16951 || resultTo != null) {

16952 BroadcastQueue queue = broadcastQueueForIntent(intent);

16953 BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,

16954 callerPackage, callingPid, callingUid, resolvedType,

16955 requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,

16956 resultData, resultExtras, ordered, sticky, false, userId);

16965 queue.enqueueOrderedBroadcastLocked(r);

16966 queue.scheduleBroadcastsLocked();

16968 }
ActivityManagerService.broadcastIntentLocked完整流程分析

下面我们就要正式开始看后面的处理逻辑了。先给大家来看一张时序图,整体上先有一个印象:

sequenceDiagram;

 Activity - ContextWrapper : sendBroadcast()

 ContextWrapper - ContextImpl : sendBroadcast()

 ContextImpl - ActivityManagerService: broadcastIntent()

 ActivityManagerService - ActivityManagerService : broadcastIntentLocked()

 ActivityManagerService - ActivityManagerService : collectReceiverComponents()

 ActivityManagerService - BroadcastQueue : scheduleBroadcastsLocked()

 BroadcastQueue - BroadcastQueue : processNextBroadcast()

 ActivityManagerService - ActivityManagerService : deliverToRegisteredReceiverLocked()

 ActivityManagerService - ActivityManagerService : performReceiveLocked()

 ActivityManagerService - ApplicationThreadProxy : scheduleRegisteredReceiver()

 ApplicationThreadProxy - InnerReceiver : performReceive()

 InnerReceiver - ReceiverDispatcher : performReceive()

 ReceiverDispatcher - BroadcastReceiver : onReceive()

上面我们是跳着看的,大家缺少一个完整的印象,让我们耐着性子把整个流程过一下吧。

private final int broadcastIntentLocked(ProcessRecord callerApp,

 String callerPackage, Intent intent, String resolvedType,

 IIntentReceiver resultTo, int resultCode, String resultData,

 Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle options,

 boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {

 intent = new Intent(intent);

 // By default broadcasts do not go to stopped apps.

 intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);

 // If we have not finished booting, dont allow this to launch new processes.

 if (!mProcessesReady (intent.getFlags() Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {

 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);

 if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,

 (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent

 + " ordered=" + ordered + " userid=" + userId);

 if ((resultTo != null) !ordered) {

 Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");

 userId = handleIncomingUser(callingPid, callingUid, userId,

 true, ALLOW_NON_FULL, "broadcast", callerPackage);

...

中间是一堆判断权限之类的操作,我们就略过不看了。

...

 final String action = intent.getAction();

 if (action != null) {

 switch (action) {

 case Intent.ACTION_UID_REMOVED:

 case Intent.ACTION_PACKAGE_REMOVED:

 case Intent.ACTION_PACKAGE_CHANGED:

 case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:

 case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:

 case Intent.ACTION_TIMEZONE_CHANGED:

 // If this is the time zone changed action, queue up a message that will reset

 // the timezone of all currently running processes. This message will get

 // queued up before the broadcast happens.

 mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);

 break;

 case Intent.ACTION_TIME_CHANGED:

 // If the user set the time, let all running processes know.

 final int is24Hour =

 intent.getBooleanExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, false) ? 1

 : 0;

 mHandler.sendMessage(mHandler.obtainMessage(UPDATE_TIME, is24Hour, 0));

 BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();

 synchronized (stats) {

 stats.noteCurrentTimeChangedLocked();

 break;

 case Intent.ACTION_CLEAR_DNS_CACHE:

 mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);

 break;

 case Proxy.PROXY_CHANGE_ACTION:

 ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO);

 mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy));

 break;

...

上面是一些特殊的广播的处理。
下面终于开始比较核心的逻辑了,去查找一下接收这个广播的接收者都是些什么吧。

 // Figure out who all will receive this broadcast.

 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);

 }

FLAG_RECEIVER_REGISTERED_ONLY这个属性用于说明是否是支持在代码中动态注册的接收者,如果不是的话,那么要通过collectReceiverComponents方法去查一下哪些应用在AndroidManifest.xml中也注册了监听。

这么有趣的函数,我们跟起去看看吧。

ActivityManagerService.collectReceiverComponents

进入之前,我们先看看几个数据类的定义:
ResolveInfo类用来存储AndroidManifest.xml中 intent 标签中对应的信息。

public class ResolveInfo implements Parcelable;

我们正式开始进入collectReceiverComponents:

private List ResolveInfo collectReceiverComponents(Intent intent, String resolvedType,int callingUid, int[] users) {

 List ResolveInfo receivers = null;

 try {

 HashSet ComponentName singleUserReceivers = null;

 boolean scannedFirstReceivers = false;

 for (int user : users) {

 // Skip users that have Shell restrictions

 if (callingUid == Process.SHELL_UID

 getUserManagerLocked().hasUserRestriction(

 UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {

 continue;

 }

下面调用到PMS去查询:

 List ResolveInfo newReceivers = AppGlobals.getPackageManager()

 .queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, user);

PMS中的这段代码是这样的,我们先不再往下细分析了,后面会讲注册的时候再提到。

public List ResolveInfo queryIntentReceivers(Intent intent, String resolvedType, int flags,

 int userId) {

 if (!sUserManager.exists(userId)) return Collections.emptyList();

 ComponentName comp = intent.getComponent();

 if (comp == null) {

 if (intent.getSelector() != null) {

 intent = intent.getSelector();

 comp = intent.getComponent();

 if (comp != null) {

 List ResolveInfo list = new ArrayList ResolveInfo 

 ActivityInfo ai = getReceiverInfo(comp, flags, userId);

 if (ai != null) {

 ResolveInfo ri = new ResolveInfo();

 ri.activityInfo = ai;

 list.add(ri);

 return list;

 // reader

 synchronized (mPackages) {

 String pkgName = intent.getPackage();

 if (pkgName == null) {

 return mReceivers.queryIntent(intent, resolvedType, flags, userId);

 final PackageParser.Package pkg = mPackages.get(pkgName);

 if (pkg != null) {

 return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers,

 userId);

 return null;

}

我们回到collectReceiverComponents中继续往下看:

 if (user != UserHandle.USER_OWNER newReceivers != null) {

 // If this is not the primary user, we need to check for

 // any receivers that should be filtered out.

 for (int i=0; i newReceivers.size(); i++) {

 ResolveInfo ri = newReceivers.get(i);

 if ((ri.activityInfo.flags ActivityInfo.FLAG_PRIMARY_USER_ONLY) != 0) {

 newReceivers.remove(i);

 i--;

 if (newReceivers != null newReceivers.size() == 0) {

 newReceivers = null;

 if (receivers == null) {

 receivers = newReceivers;

 } else if (newReceivers != null) {

 // We need to concatenate the additional receivers

 // found with what we have do far. This would be easy,

 // but we also need to de-dup any receivers that are

 // singleUser.

 if (!scannedFirstReceivers) {

 // Collect any single user receivers we had already retrieved.

 scannedFirstReceivers = true;

 for (int i=0; i receivers.size(); i++) {

 ResolveInfo ri = receivers.get(i);

 if ((ri.activityInfo.flags ActivityInfo.FLAG_SINGLE_USER) != 0) {

 ComponentName cn = new ComponentName(

 ri.activityInfo.packageName, ri.activityInfo.name);

 if (singleUserReceivers == null) {

 singleUserReceivers = new HashSet ComponentName 

 singleUserReceivers.add(cn);

 // Add the new results to the existing results, tracking

 // and de-dupping single user receivers.

 for (int i=0; i newReceivers.size(); i++) {

 ResolveInfo ri = newReceivers.get(i);

 if ((ri.activityInfo.flags ActivityInfo.FLAG_SINGLE_USER) != 0) {

 ComponentName cn = new ComponentName(

 ri.activityInfo.packageName, ri.activityInfo.name);

 if (singleUserReceivers == null) {

 singleUserReceivers = new HashSet ComponentName 

 if (!singleUserReceivers.contains(cn)) {

 singleUserReceivers.add(cn);

 receivers.add(ri);

 } else {

 receivers.add(ri);

 } catch (RemoteException ex) {

 // pm is in same process, this will never happen.

 return receivers;

}

下面,我们回到broadcastIntentLocked中继续往下看:

 if (intent.getComponent() == null) {

 if (userId == UserHandle.USER_ALL callingUid == Process.SHELL_UID) {

 // Query one target user at a time, excluding shell-restricted users

 UserManagerService ums = getUserManagerLocked();

 for (int i = 0; i users.length; i++) {

 if (ums.hasUserRestriction(

 UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {

 continue;

 List BroadcastFilter registeredReceiversForUser =

 mReceiverResolver.queryIntent(intent,

 resolvedType, false, users[i]);

 if (registeredReceivers == null) {

 registeredReceivers = registeredReceiversForUser;

 } else if (registeredReceiversForUser != null) {

 registeredReceivers.addAll(registeredReceiversForUser);

 } else {

 registeredReceivers = mReceiverResolver.queryIntent(intent,

 resolvedType, false, userId);

 final boolean replacePending =

 (intent.getFlags() Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;

终于快到我们最关心的逻辑部分了,大家仔细看啦!

 if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueing broadcast: " + intent.getAction()

 + " replacePending=" + replacePending);

 int NR = registeredReceivers != null ? registeredReceivers.size() : 0;

 if (!ordered NR 0) {

 // If we are not serializing this broadcast, then send the

 // registered receivers separately so they dont wait for the

 // components to be launched.

如果不是ordered,且注册这个事件的receiver队列不为空,我们就开始处理普通消息了!

 final BroadcastQueue queue = broadcastQueueForIntent(intent);

 BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,

 callerPackage, callingPid, callingUid, resolvedType, requiredPermissions,

 appOp, brOptions, registeredReceivers, resultTo, resultCode, resultData,

 resultExtras, ordered, sticky, false, userId);

 if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);

 final boolean replaced = replacePending queue.replaceParallelBroadcastLocked(r);

下面我们就将普通消息加入到并行消息队列里,enqueueParallelBroadcastLocked和scheduleBroadcastsLocked我们前面预备知识都讲过了,这里不再重复了。

 if (!replaced) {

 queue.enqueueParallelBroadcastLocked(r);

 queue.scheduleBroadcastsLocked();

 registeredReceivers = null;

 NR = 0;

 }

以上,并发消息处理正式结束。下面开始的是串行消息的处理,中间我们略去一段对于PACKAGE_ADDED消息发给新安装的应用这一段支线任务。

另外,有序队列中还有一件额外的事情需要做,有序么,要排序。

 // Merge into one list.

 int ir = 0;

 if (receivers != null) {

 int NT = receivers != null ? receivers.size() : 0;

 int it = 0;

 ResolveInfo curt = null;

 BroadcastFilter curr = null;

 while (it NT ir NR) {

 if (curt == null) {

 curt = (ResolveInfo)receivers.get(it);

 if (curr == null) {

 curr = registeredReceivers.get(ir);

 if (curr.getPriority() = curt.priority) {

 // Insert this broadcast record into the final list.

 receivers.add(it, curr);

 ir++;

 curr = null;

 it++;

 NT++;

 } else {

 // Skip to the next ResolveInfo in the final list.

 it++;

 curt = null;

 while (ir NR) {

 if (receivers == null) {

 receivers = new ArrayList();

 receivers.add(registeredReceivers.get(ir));

 ir++;

 }

最后是加入到串行队列并发送消息的过程,跟并发队列除了队列不同,其余都是一样的。

 if ((receivers != null receivers.size() 0)

 || resultTo != null) {

 BroadcastQueue queue = broadcastQueueForIntent(intent);

 BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,

 callerPackage, callingPid, callingUid, resolvedType,

 requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,

 resultData, resultExtras, ordered, sticky, false, userId);

 if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r

 + ": prev had " + queue.mOrderedBroadcasts.size());

 if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,

 "Enqueueing broadcast " + r.intent.getAction());

 boolean replaced = replacePending queue.replaceOrderedBroadcastLocked(r);

 if (!replaced) {

 queue.enqueueOrderedBroadcastLocked(r);

 queue.scheduleBroadcastsLocked();

 return ActivityManager.BROADCAST_SUCCESS;

}

Android Service全屏广播 产品需要一个全屏广播,不管用户在那个界面每隔一段时间都会弹出一个滚动的文字,而且这个不是用推送来做的,后台返回一组数据,然后客户端自己进行处理!如果有更好的方法或者建议都可以跟我说一下,万分感谢,第一次做这个也不知道用什么合适!
一:普通广播 普通广播是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条消息,他们接收的先后是随机的。
终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~ 1. 简介 通常我们在使用Android广播的时候都会直接将广播注册到系统的AMS当中,由于AMS任务繁忙,一般可能不会立即能处理到我们发出的广播,如果我们使用广播是在应用内的单个进程中使用,则完全可以采用LocalBroadcastManager来处理。
lusing 刘子瑛,阿里系统框架专家。工作十余年,一直对新编程语言、新开发方法、数学与算法相关和并发等相关领域保持浓厚的兴趣。乐于通过技术分享促进新技术。