【Broadcast】sendBroadcast流程(一)

2024-06-06 19:18

本文主要是介绍【Broadcast】sendBroadcast流程(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文基于Androd8.0。

广播发送有很多个函数,此处以最简单的sendBroadcast为例分析:

sendBroadcast实际是调用的ContextImpl的sendBroadcast:

 @Overridepublic void sendBroadcast(Intent intent) {warnIfCallingFromSystemProcess();String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());try {intent.prepareToLeaveProcess(this);ActivityManager.getService().broadcastIntent(mMainThread.getApplicationThread(), intent, resolvedType, null,Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,getUserId());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}
}

其中ActivityManger.getService():

/**
* @hide
*/
public static IActivityManager getService() {return IActivityManagerSingleton.get();
}private static final Singleton<IActivityManager> IActivityManagerSingleton =new Singleton<IActivityManager>() {@Overrideprotected IActivityManager create() {final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);final IActivityManager am = IActivityManager.Stub.asInterface(b);return am;}
};

经过Binder驱动,最后会调用到ActivityManagerService.broadcastIntent。

public final int broadcastIntent(IApplicationThread caller,Intent intent, String resolvedType, IIntentReceiver resultTo,int resultCode, String resultData, Bundle resultExtras,String[] requiredPermissions, int appOp, Bundle bOptions,boolean serialized, boolean sticky, int userId) {enforceNotIsolatedCaller("broadcastIntent");synchronized(this) {intent = verifyBroadcastLocked(intent);final ProcessRecord callerApp = getRecordForAppLocked(caller);final int callingPid = Binder.getCallingPid();final int callingUid = Binder.getCallingUid();final long origId = Binder.clearCallingIdentity();int res = broadcastIntentLocked(callerApp,callerApp != null ? callerApp.info.packageName : null,intent, resolvedType, resultTo, resultCode, resultData, resultExtras,requiredPermissions, appOp, bOptions, serialized, sticky,callingPid, callingUid, userId);Binder.restoreCallingIdentity(origId);return res;}
}

真正做事情的是ActivityManagerService.broadcastIntentLocked:

int broadcastIntentLocked(ProcessRecord callerApp,String callerPackage, Intent intent, String resolvedType,IIntentReceiver resultTo, int resultCode, String resultData,Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {....// By default broadcasts do not go to stopped apps.intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);// If we have not finished booting, don't allow this to launch new processes.if (!mProcessesReady && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);}....// Verify that protected broadcasts are only being sent by system code,// and that system code is only sending protected broadcasts.final String action = intent.getAction();final boolean isProtectedBroadcast;try {isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);} catch (RemoteException e) {Slog.w(TAG, "Remote exception", e);return ActivityManager.BROADCAST_SUCCESS;}final boolean isCallerSystem;switch (UserHandle.getAppId(callingUid)) {case ROOT_UID:case SYSTEM_UID:case PHONE_UID:case BLUETOOTH_UID:case NFC_UID:isCallerSystem = true;break;default:isCallerSystem = (callerApp != null) && callerApp.persistent;break;}//非system app发送protected broadcast,抛出异常if (!isCallerSystem) {if (isProtectedBroadcast) {String msg = "Permission Denial: not allowed to send broadcast "+ action + " from pid="+ callingPid + ", uid=" + callingUid;Slog.w(TAG, msg);throw new SecurityException(msg);} else if (...) {....}}if (action != null) {if (getBackgroundLaunchBroadcasts().contains(action)) {intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);}switch(action) {//处理Package等相关的广播....}}

第一阶段处理一些特殊的广播。

接下来看第二阶段:

// Add to the sticky list if requested.
if (sticky) {//检查是否有发送粘性广播的权限if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,callingPid, callingUid) != PackageManager.PERMISSION_GRANTED) {String msg = "Permission Denial: broadcastIntent() requesting a sticky         broadcast from pid="+ callingPid + ", uid=" + callingUid+ " requires " + android.Manifest.permission.BROADCAST_STICKY;Slog.w(TAG, msg);throw new SecurityException(msg);}if (requiredPermissions != null && requiredPermissions.length > 0) {Slog.w(TAG, "Can't broadcast sticky intent " + intent+ " and enforce permissions " + Arrays.toString(requiredPermissions));return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;}//粘性广播指定发送的组件if (intent.getComponent() != null) {throw new SecurityException("Sticky broadcasts can't target a specific                    component");}//如果广播不是发送给所有人的,则要查看是否和发送给所有人的广播冲突// We use userId directly here, since the "all" target is maintained// as a separate set of sticky broadcasts.if (userId != UserHandle.USER_ALL) {// But first, if this is not a broadcast to all users, then// make sure it doesn't conflict with an existing broadcast to// all users.ArrayMap<String, ArrayList<Intent>> stickies =mStickyBroadcasts.get(UserHandle.USER_ALL);if (stickies != null) {ArrayList<Intent> list = stickies.get(intent.getAction());if (list != null) {int N = list.size();int i;for (i=0; i<N; i++) {if (intent.filterEquals(list.get(i))) {throw new IllegalArgumentException(+ userId + " conflicts with existing global broadcast");}}}}}ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);                if (stickies == null) {stickies = new ArrayMap<>();mStickyBroadcasts.put(userId, stickies);}ArrayList<Intent> list = stickies.get(intent.getAction());if (list == null) {list = new ArrayList<>();stickies.put(intent.getAction(), list);}final int stickiesCount = list.size();int i;for (i = 0; i < stickiesCount; i++) {if (intent.filterEquals(list.get(i))) {// This sticky already exists, replace it.list.set(i, new Intent(intent));break;}}if (i >= stickiesCount) {list.add(new Intent(intent));}
} //end of if(sticky)...// Figure out who all will receive this broadcast.
List receivers = null;//保存匹配该Intent的所有广播接收者,包括静态和动态
List<BroadcastFilter> registeredReceivers = null;//保存符合该Intent的动态receiver
// Need to resolve the intent to interested receivers...
if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)== 0) {//如果没有FLAG_RECEIVER_REGISTERED_ONLY flag,则查询PKMS,//获得那些在AndroidManifest.xml中声明的广播接收者,即静态广播接收者,并保存到receiversreceivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
}
if (intent.getComponent() == null) { //没有指定接收的对象if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {// Query one target user at a time, excluding shell-restricted usersfor (int i = 0; i < users.length; i++) {if (mUserController.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {continue;}List<BroadcastFilter> registeredReceiversForUser =mReceiverResolver.queryIntent(intent,resolvedType, false /*defaultOnly*/, users[i]);if (registeredReceivers == null) {registeredReceivers = registeredReceiversForUser;} else if (registeredReceiversForUser != null) {registeredReceivers.addAll(registeredReceiversForUser);}}} else {//从mReceiver查找符合intent的动态广播接收者registeredReceivers = mReceiverResolver.queryIntent(intent,resolvedType, false /*defaultOnly*/, userId);}}/*判断该广播是否设置了REPLACE_PENDING标签,如果设置了该标签,并且之前的Intent还没有被处理,        则可以使用新的的Intent替换旧的Intent。目的是为了减少不必要的广播发送。*/final boolean replacePending =(intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;//先处理动态注册的接收中者int NR = registeredReceivers != null ? registeredReceivers.size() : 0;//此次发送的广播为非有序广播,且符合该intent的动态receiver个数不为0if (!ordered && NR > 0) {// If we are not serializing this broadcast, then send the// registered receivers separately so they don't wait for the// components to be launched.if (isCallerSystem) {checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,isProtectedBroadcast, registeredReceivers);}//根据FLAG确定是加入到前台广播队列还是后台广播队列final BroadcastQueue queue = broadcastQueueForIntent(intent);//只需创建一个BrodacstRecord,一个BroadcastRecord对象可包括所有的接收者BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,callerPackage, callingPid, callingUid, callerInstantApp, resolvedType,requiredPermissions, appOp, brOptions, registeredReceivers, resultTo,resultCode, resultData, resultExtras, ordered, sticky, false, userId);final boolean replaced = replacePending&& (queue.replaceParallelBroadcastLocked(r) != null);// Note: We assume resultTo is null for non-ordered broadcasts.if (!replaced) {//如果没有被替换,则保存到mParallelBroadcasts中queue.enqueueParallelBroadcastLocked(r);//调度一次广播发送queue.scheduleBroadcastsLocked();}//至此,动态注册的广播接收者已经处理完毕,设置registeredReceivers为nullregisteredReceivers = null;NR = 0;
}

第二阶段的工作有两项:

  1. 查询满足条件的动态receiver和静态receiver
  2. 当本地广播不为ordered时,需尽快发送该广播。另,非ordered的广播都被AMS保存在mParallelBroadcasts中。
// Merge into one list.
int ir = 0;
if (receivers != null) {String skipPackages[] = null;//处理PACKAGE_ADDED、PACKAGE_RESTARTED、PACKAGE_DATA_CLEARED的intent,系统不希望有些应用//一安装就启动。//这类程序的工作原理是:在改程序内部监听PACKAGE_ADDED广播,如果系统没有防备,则PKMS安装完程序 //后所发送的PACKAGE_ADDED消息将触发该应用的启动......//处理ACTION_EXTERNAL_APPLICATIONS_AVAILBLE广播......//将动态注册的接收者registeredReceiver的元素合并到receivers中去//处理完毕后,所有的接收者(无论是动态还是静态)都存储到receivers中了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对象,它的receivers中包括所有的接收者BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,callerPackage, callingPid, callingUid, callerInstantApp,         resolvedType, requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,resultData, resultExtras, ordered, sticky, false, userId);final BroadcastRecord oldRecord =replacePending ? queue.replaceOrderedBroadcastLocked(r) : null;if (oldRecord != null) {// Replaced, fire the result-to receiver.if (oldRecord.resultTo != null) {final BroadcastQueue oldQueue = broadcastQueueForIntent(oldRecord.intent);try {oldQueue.performReceiveLocked(oldRecord.callerApp, oldRecord.resultTo,oldRecord.intent,Activity.RESULT_CANCELED, null, null,false, false, oldRecord.userId);} catch (RemoteException e) {Slog.w(TAG, "Failure ["+ queue.mQueueName + "] sending broadcast result of "+ intent, e);}}} else {//如果没被替换,将该条广播记录到mOrderedBroadcasts中queue.enqueueOrderedBroadcastLocked(r);//调度AMS进行广播发送工作queue.scheduleBroadcastsLocked();}
} else {// There was nobody interested in the broadcast, but we still want to record// that it happened.if (intent.getComponent() == null && intent.getPackage() == null&& (intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {// This was an implicit broadcast... let's record it for posterity.addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);}
}return ActivityManager.BROADCAST_SUCCESS;
}  //end of broadcastIntentLocked()       

      由以上分析可得,AMS将动态receiver和静态receiver都合并到了receivers中了。如果本次广播不是ordered,那么表明动态注册者已经在第二阶段中被处理了。因此在合并时,将不会有动态receiver被加到receivers中。最终所创建的广播记录存在mOrderedBroadcasts中,也即,不管是否为串行发送,静态receiver接收到的广播记录都保存在mOrderedBroadcasts中。

为何不将它们保存到mParallelBroadcasts中呢?

      结合code发现,mParallelBroadcasts中的BroadcastRecord所包含的都是动态注册的广播接收者信息,这是因为动态接收者所在的进程是已经存在的,而静态广播接收者就不能保证它已经和一个进程绑定在一起了。如果静态注册的广播接收者所在进程还未启动,则AMS还需要进行创建应用进程,初始化Android运行环境等一系列操作。

      且后面,mParallelBroadcasts中的成员是在一个循环中被逐条处理的,如果将静态广播接收者也保存到mParallelBroadcasts中,会有什么后果呢?假设这些静态接收者所在的进程全部未创建和启动,那么AMS将在循环中创建多个进程!系统压力一下就上去了。所以,静态接收者对应的广播记录都被放到了mOrderedBroadcasts中,AMS在处理这类广播信息时,一个进程一个进程地处理,只有处理完一个接收者才继续下一个接收者。这样的好处是避免了惊群效应的出现,坏处是延时较长。

这篇关于【Broadcast】sendBroadcast流程(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1036997

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

kubelet组件的启动流程源码分析

概述 摘要: 本文将总结kubelet的作用以及原理,在有一定基础认识的前提下,通过阅读kubelet源码,对kubelet组件的启动流程进行分析。 正文 kubelet的作用 这里对kubelet的作用做一个简单总结。 节点管理 节点的注册 节点状态更新 容器管理(pod生命周期管理) 监听apiserver的容器事件 容器的创建、删除(CRI) 容器的网络的创建与删除

火语言RPA流程组件介绍--浏览网页

🚩【组件功能】:浏览器打开指定网址或本地html文件 配置预览 配置说明 网址URL 支持T或# 默认FLOW输入项 输入需要打开的网址URL 超时时间 支持T或# 打开网页超时时间 执行后后等待时间(ms) 支持T或# 当前组件执行完成后继续等待的时间 UserAgent 支持T或# User Agent中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器

UMI复现代码运行逻辑全流程(一)——eval_real.py(尚在更新)

一、文件夹功能解析 全文件夹如下 其中,核心文件作用为: diffusion_policy:扩散策略核心文件夹,包含了众多模型及基础库 example:标定及配置文件 scripts/scripts_real:测试脚本文件,区别在于前者倾向于单体运行,后者为整体运行 scripts_slam_pipeline:orb_slam3运行全部文件 umi:核心交互文件夹,作用在于构建真

C++/《C/C++程序编译流程》

程序的基本流程如图:   1.预处理        预处理相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有宏定义,没有条件编译指令,没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。 读取C/C++源程序,对其中的伪指令(以#开头的指令)进行处理将所有的“#define”删除,并且展开所有的宏定义处理所有的条件编译指令,如:“#if”、“

笔记本电脑的具体选购步骤流程

2.1 笔记本电脑的具体选购步骤流程   关于笔记本电脑的选购,一直是热点话题。不管是新手还是老前辈,选购前,总是要先上网查一查,汇总一些信息或经验。因为选购一台笔记本电脑,从它的配置、外观到做工等很多方面都需要考量,所以挑一台自己喜欢的、适合自己的笔记本电脑也是一件很费脑筋的事情。本节将一些选购笔记本电脑的经验进行了总结,供广大读者选购笔记本电脑时参考。   笔记本电脑选购流程如下

基于微信小程序与嵌入式系统的智能小车开发(详细流程)

一、项目概述 本项目旨在开发一款智能小车,结合微信小程序与嵌入式系统,提供实时图像处理与控制功能。用户可以通过微信小程序远程操控小车,并实时接收摄像头采集的图像。该项目解决了传统遥控小车在图像反馈和控制延迟方面的问题,提升了小车的智能化水平,适用于教育、科研和娱乐等多个领域。 二、系统架构 1. 系统架构设计 本项目的系统架构主要分为以下几个部分: 微信小程序:负责用户界面、控制指令的

MySQL B+树查询数据全流程

MySQL B+树查询数据全流程 一、引言 在 MySQL 数据库中,B+树是一种常用的数据结构,用于高效地存储和查询数据。了解 B+树中查询数据的全过程,对于深入理解 MySQL 的工作原理和优化查询性能至关重要。 二、B+树的结构特点 B+树是一种平衡的多路查找树,具有以下结构特点: 每个节点可以存储多个关键字和对应的指针。非叶子节点的关键字起到索引的作用,用于引导查询到相应的子节点