Android Framework AMS(12)广播组件分析-3(广播发送流程解读)

该系列文章总纲链接:专题总纲目录 Android Framework 总纲


本章关键点总结 & 说明:

说明:本章节主要解读广播组件的广播发送过程。关注思维导图中左上侧部分即可。

有了前面广播组件 注册和注销程分析的基础,基于此,接下来我们来分析广播组件的发送广播流程。

我们先对广播组件的整体设计思路有一个了解,再详细分析其广播发送关键流程。

1 注册/注销/广播发送设计思路解读

1.1 设计思路概要解读

在解读这些关键流程前,我们从设计师的视角先来俯瞰下Android广播组件的几个关键流程,大概的设计思路整理如下(同上一章节,回顾下,有了上下文,我们之后继续分析):

  • 注册流程:注册receiver时,和AMS进行通信,将要注册的receiver和filter按照一定规则放入到AMS的一个表X中,方便查找和删除。
  • 注销流程:注销receiver时,和AMS进行通信,找到对应的表X,从中将receiver从表X中删除。
  • 广播发送流程:A进程 发送广播时,和AMS进行通信,查找表中是否有对应的recevier,如果存在就找到当时注册receiver的进程B,调用B中对应的recevier中的onReceiver方法。

1.2 设计思路详细解读

基于此,我们对这几个流程进行展开说明,进行更详细的解读,具体如下:

@1 注册流程:

  1. 应用层操作:应用通过Context对象(如ActivityService)调用registerReceiver()方法来注册一个BroadcastReceiver
  2. 构建IntentFilter:应用传入一个IntentFilter对象,该对象定义了BroadcastReceiver感兴趣的广播类型(如特定动作、数据类型等)。
  3. 与AMS通信:应用层的注册请求会通过Binder IPC(进程间通信)传递给ActivityManagerService(AMS),AMS是Android系统中负责管理应用生命周期和广播的守护进程。
  4. 存储映射关系:AMS会将BroadcastReceiverIntentFilter的映射关系存储在一个内部的数据结构中,通常是哈希表或类似的数据结构。
  5. 返回Receiver对象:AMS返回一个IIntentReceiver对象(Binder代理),用于后续的注销操作。

@2 注销流程:

  1. 应用层操作:应用调用unregisterReceiver()方法,并传入之前注册的BroadcastReceiver实例。
  2. 与AMS通信:应用层的注销请求同样通过Binder IPC传递给AMS。
  3. 查找映射关系:AMS根据传入的BroadcastReceiver实例查找之前存储的映射关系。
  4. 删除映射关系:如果找到对应的映射关系,AMS会将其从内部数据结构中删除,从而注销BroadcastReceiver
  5. 确认注销:注销操作完成后,AMS会向应用层确认注销成功。

@3 广播发送流程:

  1. 发送广播:当一个应用、系统组件或其他进程需要发送广播时,它会通过ContextsendBroadcast()方法发送广播。
  2. 与AMS通信:发送请求同样通过Binder IPC传递给AMS。
  3. 查找匹配的Receiver:AMS会根据广播的Intent(包含动作、数据等信息)查找内部数据结构中匹配的IntentFilter
  4. 匹配Receiver:AMS会检查哪些注册的BroadcastReceiver对当前广播感兴趣(即IntentFilter匹配)。
  5. 跨进程调用:对于匹配的BroadcastReceiver,AMS会通过Binder代理调用其onReceive()方法。如果BroadcastReceiver位于不同的进程,AMS会负责进程间的通信。
  6. 执行ReceiverBroadcastReceiveronReceive()方法被调用,执行相应的逻辑。

注意:在广播发送流程中,AMS不仅仅是查找表中的BroadcastReceiver,还需要检查这些BroadcastReceiver的权限和安全性,确保只有合适的接收者能够接收到特定的广播。此外,广播的发送和接收过程还涉及到广播的优先级、发送方式(如有序广播或普通广播)等因素。但这些都不是设计的主干,因此我们可以暂时忽略。

本章节,我们关注发送广播发送和处理广播的流程。

2 广播发送流程解读

2.1 发送广播broadcastIntent关键代码解读

AMS.broadcastIntent的代码实现如下:

//AMSpublic final int broadcastIntent(IApplicationThread caller,Intent intent, String resolvedType, IIntentReceiver resultTo,int resultCode, String resultData, Bundle map,String requiredPermission, int appOp, 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, map, requiredPermission, appOp, serialized, sticky,callingPid, callingUid, userId);Binder.restoreCallingIdentity(origId);return res;}}

这里主要是broadcastIntentLocked方法的实现,但该代码过长,因此这里拆分成3个部分进行分析。

2.1.1 broadcastIntentLocked第一部分代码解析

broadcastIntentLocked第一部分代码实现如下所示:

//AMSprivate final int broadcastIntentLocked(ProcessRecord callerApp,String callerPackage, Intent intent, String resolvedType,IIntentReceiver resultTo, int resultCode, String resultData,Bundle map, String requiredPermission, int appOp,boolean ordered, boolean sticky, int callingPid, int callingUid,int userId) {//第一阶段:特殊广播处理阶段intent = new Intent(intent);intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);//...userId = handleIncomingUser(callingPid, callingUid, userId,true, ALLOW_NON_FULL, "broadcast", callerPackage);if (userId != UserHandle.USER_ALL && !isUserRunningLocked(userId, false)) {//...}int callingAppId = UserHandle.getAppId(callingUid);if (callingAppId == Process.SYSTEM_UID || callingAppId == Process.PHONE_UID|| callingAppId == Process.SHELL_UID || callingAppId == Process.BLUETOOTH_UID|| callingAppId == Process.NFC_UID || callingUid == 0) {// Always okay.} else if (callerApp == null || !callerApp.persistent) {try {if (AppGlobals.getPackageManager().isProtectedBroadcast(intent.getAction())) {//...} else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(intent.getAction())) {if (callerApp == null) {//...} else if (intent.getComponent() != null) {//...} else {// Limit broadcast to their own package.intent.setPackage(callerApp.info.packageName);}}} catch (RemoteException e) {//...}}final String action = intent.getAction();if (action != null) {switch (action) {case Intent.ACTION_UID_REMOVED:case Intent.ACTION_PACKAGE_REMOVED://...case Proxy.PROXY_CHANGE_ACTION:ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO);mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy));break;}}//...

第一阶段代码主要是对一些特出的广播进行处理,主要是和package相关。

2.1.2 broadcastIntentLocked第二部分代码解析

broadcastIntentLocked第二部分代码实现如下所示:

//AMSprivate final int broadcastIntentLocked(ProcessRecord callerApp,String callerPackage, Intent intent, String resolvedType,IIntentReceiver resultTo, int resultCode, String resultData,Bundle map, String requiredPermission, int appOp,boolean ordered, boolean sticky, int callingPid, int callingUid,int userId) {//...//第二阶段:处理发送stick广播。如果是粘性广播,则进行特殊处理if (sticky) {//...// 获取特定用户ID的粘性广播存储ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);if (stickies == null) {stickies = new ArrayMap<String, ArrayList<Intent>>();mStickyBroadcasts.put(userId, stickies);}// 获取该动作对应的粘性广播列表ArrayList<Intent> list = stickies.get(intent.getAction());if (list == null) {list = new ArrayList<Intent>();stickies.put(intent.getAction(), list);}// 查找是否存在相同的粘性广播,如果存在则替换它int N = list.size();for (i = 0; i < N; i++) {if (intent.filterEquals(list.get(i))) {list.set(i, new Intent(intent));break;}}// 如果没有找到相同的粘性广播,则添加新的粘性广播if (i >= N) {list.add(new Intent(intent));}}// 根据用户ID确定要广播的用户列表int[] users;if (userId == UserHandle.USER_ALL) {users = mStartedUserArray;} else {users = new int[] {userId};}// 收集可以接收此广播的组件List receivers = null;// 查询已注册的接收器List<BroadcastFilter> registeredReceivers = null;if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);}// 如果没有指定组件,查询所有可以接收此广播的接收器if (intent.getComponent() == null) {if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) {//...} else {registeredReceivers = mReceiverResolver.queryIntent(intent,resolvedType, false, userId);}}// 检查是否需要替换挂起的广播final boolean replacePending =(intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;// 计算已注册接收器的数量int NR = registeredReceivers != null ? registeredReceivers.size() : 0;// 如果不需要有序广播且有已注册接收器,则发送并行广播if (!ordered && NR > 0) {final BroadcastQueue queue = broadcastQueueForIntent(intent);BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,callerPackage, callingPid, callingUid, resolvedType, requiredPermission,appOp, registeredReceivers, resultTo, resultCode, resultData, map,ordered, sticky, false, userId);// 如果需要替换挂起的广播,则尝试替换final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);// 如果没有替换成功,则入队并调度广播if (!replaced) {queue.enqueueParallelBroadcastLocked(r);queue.scheduleBroadcastsLocked();}registeredReceivers = null;NR = 0;}//...//...
}

第二阶段代码主要处理与粘性广播相关的逻辑。粘性广播是一种特殊的广播,它可以被保存并在BroadcastReceiver注册后立即发送给它,即使广播在接收器注册前就已经发送。这个阶段会检查传入的广播是否为粘性广播(sticky),如果是,则在mStickyBroadcasts数据结构中更新或添加新的粘性广播。这个过程涉及到根据用户ID和Intent动作查找和替换现有的粘性广播,确保最新的广播被保存。

同时在这里我们着重分析下静态广播接收器的注册和识别过程,毕竟最开始不是动态注册的,我们需要理清楚静态注册的这些receiver是如何和动态注册的receivers合并在一起的。在查询已注册的静态广播接收器时,处理的关键方法为collectReceiverComponents。代码实现如下所示:

//AMSprivate 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) { // 遍历所有用户// 如果调用者是shell,并且用户有限制不允许调试特性,则跳过if (callingUid == Process.SHELL_UID&& getUserManagerLocked().hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {continue;}// 关键代码:查询每个用户可以处理给定Intent的广播接收器List<ResolveInfo> newReceivers = AppGlobals.getPackageManager().queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, user);// 如果用户不是默认用户,并且查询到的接收器不为空if (user != 0 && newReceivers != null) {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--;}}}// 如果查询到的接收器列表不为空,并且大小为0,则设置为nullif (newReceivers != null && newReceivers.size() == 0) {newReceivers = null;}// 合并接收器列表if (receivers == null) {receivers = newReceivers; // 如果receivers为空,则直接赋值} else if (newReceivers != null) {if (!scannedFirstReceivers) {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);}}}// 将新用户的接收器添加到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; // 返回收集到的广播接收器列表}

collectReceiverComponents方法负责收集所有可以处理给定Intent的广播接收器,考虑了用户限制、主用户接收器和单用户接收器。这个方法确保了广播可以被正确地分发到所有符合条件的接收器,无论它们属于哪个用户。这里我们主要关注调用的PMS代码queryIntentReceivers方法,代码实现如下:

//PMS@Overridepublic List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags,int userId) {// 检查用户是否存在,如果不存在返回空列表if (!sUserManager.exists(userId)) return Collections.emptyList();// 获取Intent中指定的组件名称ComponentName comp = intent.getComponent();// 如果Intent中没有指定组件,尝试从Selector中获取if (comp == null) {if (intent.getSelector() != null) {intent = intent.getSelector(); comp = intent.getComponent();}}// 如果Intent中指定了组件if (comp != null) {// 创建一个ResolveInfo列表,最多包含一个元素List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);// 获取对应组件的ActivityInfoActivityInfo ai = getReceiverInfo(comp, flags, userId);// 如果找到了对应的ActivityInfoif (ai != null) {// 创建一个ResolveInfo对象,并填充ActivityInfoResolveInfo ri = new ResolveInfo();ri.activityInfo = ai;// 将ResolveInfo添加到列表中list.add(ri);}// 返回ResolveInfo列表return list;}// 如果Intent中没有指定组件,使用mReceivers进行查询synchronized (mPackages) {// 获取Intent中指定的包名String pkgName = intent.getPackage();// 如果Intent中没有指定包名if (pkgName == null) {// 使用mReceivers查询所有匹配的广播接收器return mReceivers.queryIntent(intent, resolvedType, flags, userId);}// 如果Intent中指定了包名final PackageParser.Package pkg = mPackages.get(pkgName);// 如果找到了对应的Packageif (pkg != null) {// 使用mReceivers查询指定包中匹配的广播接收器return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers,userId);}// 如果没有找到对应的Package,返回nullreturn null;}}

queryIntentReceivers方法是PKMS中用于查询可以处理特定Intent的广播接收器的关键方法。它支持通过组件名称、包名或全局查询来找到匹配的广播接收器,并返回它们的ResolveInfo列表。

2.1.3 broadcastIntentLocked第三部分代码解析

broadcastIntentLocked第三部分代码实现如下所示:

//AMSprivate final int broadcastIntentLocked(ProcessRecord callerApp,String callerPackage, Intent intent, String resolvedType,IIntentReceiver resultTo, int resultCode, String resultData,Bundle map, String requiredPermission, int appOp,boolean ordered, boolean sticky, int callingPid, int callingUid,int userId) {//...// 第三阶段:处理接收者排序和广播分发int ir = 0; // 初始化接收者计数器// 如果有收集到的接收者if (receivers != null) {// 定义一个数组,用于存储需要跳过的包名String skipPackages[] = null;// 根据不同的Intent动作,确定需要跳过的包名if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())|| Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())|| Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {Uri data = intent.getData();if (data != null) {String pkgName = data.getSchemeSpecificPart();if (pkgName != null) {skipPackages = new String[] { pkgName };}}} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);}// 如果有需要跳过的包名if (skipPackages != null && (skipPackages.length > 0)) {// 遍历需要跳过的包名for (String skipPackage : skipPackages) {if (skipPackage != null) {// 从接收者列表中移除对应的接收者int NT = receivers.size();for (int it=0; it<NT; it++) {ResolveInfo curt = (ResolveInfo)receivers.get(it);if (curt.activityInfo.packageName.equals(skipPackage)) {receivers.remove(it);it--;NT--;}}}}}// 计算接收者的数量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) {// 如果已注册接收者的优先级更高或相等,将其插入最终列表receivers.add(it, curr);ir++;curr = null;it++;NT++;} else {// 否则,跳过当前的ResolveInfoit++;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,requiredPermission, appOp, receivers, resultTo, resultCode,resultData, map, ordered, sticky, false, userId);//...// 如果需要替换挂起的有序广播,则尝试替换boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); if (!replaced) {// 如果没有替换成功,将广播加入队列并调度广播queue.enqueueOrderedBroadcastLocked(r);queue.scheduleBroadcastsLocked();}}return ActivityManager.BROADCAST_SUCCESS;}

第三阶段代码负责将已注册的接收器BroadcastFilter和通过Intent解析得到的接收器ResolveInfo合并,并根据它们的优先级进行排序。此外,它还会处理特定情况下需要跳过某些包名的逻辑,例如在应用更新、重启或数据清除时。排序完成后,代码会创建一个BroadcastRecord,包含所有排序后的接收者,并将这个记录发送到相应的BroadcastQueue中,以便进行广播分发。

在AMS的broadcastIntentLocked方法中。第一阶段、二阶段和第三阶段共同构成了广播Intent处理的核心流程。具体解读如下:

  1. 第一阶段主要处理特殊广播。
  2. 第二阶段专注于处理粘性广播,确保即使在接收器注册前发送的广播也能被正确地保存和分发,同时这里对静态接收器也做了相关处理,这样在第三阶段就可以将动态和静态接收器合并在一起。
  3. 第三阶段则负责将所有潜在的接收者(包括已注册和通过Intent解析得到的)合并、排序,并根据它们的优先级进行分发。这三个阶段的设计和实现确保了Android系统中广播机制的灵活性和可靠性,允许应用在正确的时间接收到正确的广播,同时支持广播的持久化和优先级控制。

通过这种方式,Android系统能够高效地管理和分发大量的广播,满足不同应用场景的需求。我们接下来主要关注广播的发送流程中queue的部分实现,也就是broadcastIntentLocked中的如下代码:

//调度发送并行广播
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
//调度发送串行广播
queue.enqueueOrderedBroadcastLocked(r);
queue.scheduleBroadcastsLocked();

enqueueParallelBroadcastLocked、enqueueOrderedBroadcastLocked的实现具体如下:

//BroadcastQueue//并行广播队列public void enqueueParallelBroadcastLocked(BroadcastRecord r) {mParallelBroadcasts.add(r);}//串行广播队列public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {mOrderedBroadcasts.add(r);}

可以看到,对于串行和并行2类广播放在了不同的列表中。这也意味着,每次发送广播,针对并行和串行广播,是做了不同的处理的。

接下来看共同部分:scheduleBroadcastsLocked方法,它的代码如下:

public void scheduleBroadcastsLocked() {if (mBroadcastsScheduled) {return;}mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));mBroadcastsScheduled = true;}//handler消息处理private final class BroadcastHandler extends Handler {public BroadcastHandler(Looper looper) {super(looper, null, true);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case BROADCAST_INTENT_MSG: {processNextBroadcast(true);} break;//...}}};

可以看到这里最终是调用到processNextBroadcast方法,接下来我们主要关注该方法的实现。

2.2 processNextBroadcast方法解读

2.2.1 processNextBroadcast第一部分代码解析

processNextBroadcast第一部分代码实现如下所示:

//BroadcastQueuefinal void processNextBroadcast(boolean fromMsg) {synchronized(mService) {//第一阶段:派发广播给接收者BroadcastRecord r; mService.updateCpuStats();// 更新cpu使用情况// 如果这个调用是由消息队列触发的,那么更新调度状态if (fromMsg) {mBroadcastsScheduled = false;}// 处理所有并行广播while (mParallelBroadcasts.size() > 0) {r = mParallelBroadcasts.remove(0); // 从并行广播队列中取出第一个广播记录r.dispatchTime = SystemClock.uptimeMillis(); // 设置广播的分发时间为当前系统uptime毫秒数r.dispatchClockTime = System.currentTimeMillis(); // 设置广播的分发时间为当前系统时间毫秒数final int N = r.receivers.size(); // 获取广播接收器的数量// 遍历所有广播接收器,并将广播分发给它们for (int i = 0; i < N; i++) {Object target = r.receivers.get(i);deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); // 分发广播给注册的接收器}addBroadcastToHistoryLocked(r); // 将广播记录添加到历史记录中,以便未来查询}//...}}

这段代码是Android广播系统中处理并行广播的关键部分,它确保了当有并行广播待处理时,它们能够被及时取出并分发给所有注册的接收器。这个过程体现了Android广播机制的并行处理能力,允许多个广播同时被分发,提高了系统的响应速度和效率。

2.2.2 processNextBroadcast第二部分代码解析

processNextBroadcast第二部分代码实现如下所示:

//BroadcastQueuefinal void processNextBroadcast(boolean fromMsg) {synchronized(mService) {//...// 第二阶段:处理有序广播if (mPendingBroadcast != null) {boolean isDead;synchronized (mService.mPidsSelfLocked) {ProcessRecord proc = mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.pid);isDead = proc == null || proc.crashing;}if (!isDead) {// 如果当前处理的广播对应的进程还活着,继续等待return;} else {mPendingBroadcast.state = BroadcastRecord.IDLE;mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;mPendingBroadcast = null;}}boolean looped = false;do {if (mOrderedBroadcasts.size() == 0) {mService.scheduleAppGcsLocked(); // 调度应用的垃圾回收if (looped) {mService.updateOomAdjLocked(); // 更新OOM调整}return; // 如果有序广播队列为空,返回}BroadcastRecord r = mOrderedBroadcasts.get(0); // 获取队列中的第一个广播记录boolean forceReceive = false; // 是否强制接收广播int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;if (mService.mProcessesReady && r.dispatchTime > 0) {long now = SystemClock.uptimeMillis();if ((numReceivers > 0) &&(now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {broadcastTimeoutLocked(false); // 超时处理,强制完成广播forceReceive = true;r.state = BroadcastRecord.IDLE;}}if (r.state != BroadcastRecord.IDLE) {return; // 如果当前广播状态不是IDLE,返回}// 如果没有更多的接收器,或者接收器中止了广播,或者需要强制接收if (r.receivers == null || r.nextReceiver >= numReceivers|| r.resultAbort || forceReceive) {if (r.resultTo != null) {try {// 执行结果接收器的回调performReceiveLocked(r.callerApp, r.resultTo,new Intent(r.intent), r.resultCode,r.resultData, r.resultExtras, false, false, r.userId);r.resultTo = null;} catch (RemoteException e) {//...}}cancelBroadcastTimeoutLocked(); // 取消广播超时// 将广播记录添加到历史记录,并继续处理下一个广播addBroadcastToHistoryLocked(r);mOrderedBroadcasts.remove(0);r = null;looped = true;continue;}} while (r == null); // 如果当前广播记录为null,继续循环//...}}

这段代码是Android广播系统中处理有序广播的关键部分,它确保了有序广播按照正确的顺序被分发给接收器,并且在必要时处理超时和强制接收的情况。这个过程确保了广播的执行顺序和接收器之间的依赖关系得到正确处理。

2.2.3 processNextBroadcast第三部分代码解析

processNextBroadcast第三部分代码实现如下所示:

//BroadcastQueuefinal void processNextBroadcast(boolean fromMsg) {synchronized(mService) {// ...// 第三阶段:处理有序广播的下一个接收者int recIdx = r.nextReceiver++; // 获取下一个接收者的索引并递增索引r.receiverTime = SystemClock.uptimeMillis(); // 设置接收时间if (recIdx == 0) {r.dispatchTime = r.receiverTime; // 如果是第一个接收者,设置分发时间r.dispatchClockTime = System.currentTimeMillis(); // 设置分发的系统时间}if (!mPendingBroadcastTimeoutMessage) {long timeoutTime = r.receiverTime + mTimeoutPeriod; // 设置超时时间setBroadcastTimeoutLocked(timeoutTime); // 设置广播超时}Object nextReceiver = r.receivers.get(recIdx); // 获取下一个接收者if (nextReceiver instanceof BroadcastFilter) {BroadcastFilter filter = (BroadcastFilter)nextReceiver; // 转换为BroadcastFilterdeliverToRegisteredReceiverLocked(r, filter, r.ordered); // 分发广播给注册的接收器if (r.receiver == null || !r.ordered) {scheduleBroadcastsLocked(); // 调度广播}return;}ResolveInfo info = (ResolveInfo)nextReceiver; // 转换为ResolveInfoComponentName component = new ComponentName(info.activityInfo.packageName, info.activityInfo.name); // 获取组件名称// 检查权限和AppOps,如果不符合条件,则跳过当前接收者boolean skip = checkReceiverPermission(r, info); // 检查接收者权限if (skip) {r.receiver = null;r.curFilter = null;r.state = BroadcastRecord.IDLE; // 设置状态为IDLEscheduleBroadcastsLocked(); // 调度广播return;}// 如果接收者是一个单例,并且调用者有权限,那么更新接收者的ActivityInfoboolean isSingleton = false; // 标记是否为单例try {isSingleton = mService.isSingleton(info.activityInfo.processName, info.activityInfo.applicationInfo, info.activityInfo.name, info.activityInfo.flags);} catch (SecurityException e) {// ...}if ((info.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {// 检查是否有跨用户交互权限if (ActivityManager.checkUidPermission(android.Manifest.permission.INTERACT_ACROSS_USERS, info.activityInfo.applicationInfo.uid) != PackageManager.PERMISSION_GRANTED) {skip = true;}}if (r.curApp != null && r.curApp.crashing) {// 如果当前应用正在崩溃,跳过skip = true;}if (!skip) {// 检查应用是否可用boolean isAvailable = false;try {isAvailable = AppGlobals.getPackageManager().isPackageAvailable(info.activityInfo.packageName, UserHandle.getUserId(info.activityInfo.applicationInfo.uid));} catch (Exception e) {// ...}if (!isAvailable) {skip = true;}}if (skip) {// 如果需要跳过,清理状态并调度下一个广播r.receiver = null;r.curFilter = null;r.state = BroadcastRecord.IDLE;scheduleBroadcastsLocked();return;}// 如果接收者处理成功,更新状态并继续处理r.state = BroadcastRecord.APP_RECEIVE; // 设置状态为APP_RECEIVE// ...// 启动进程并分发广播// ...}}

这段代码是Android广播系统中处理有序广播的下一个接收者的关键部分,它负责检查接收者的权限、AppOps、单例状态和应用可用性,如果所有检查通过,则更新广播记录的状态并继续分发广播。这个过程确保了只有符合条件的接收者才能接收到广播,维护了广播的安全性和正确性。

2.3 基于onReceive回调的分析

这里基于processNextBroadcast第一部分代码中关键方法deliverToRegisteredReceiverLocked的实现进行分析。该方法在processNextBroadcast中的上下文概要解读如下:

//BroadcastQueuefinal void processNextBroadcast(boolean fromMsg) {synchronized(mService) {//第一阶段:派发广播给接收者//...// 处理所有并行广播while (mParallelBroadcasts.size() > 0) {//...final int N = r.receivers.size(); // 获取广播接收器的数量// 遍历所有广播接收器,并将广播分发给它们for (int i = 0; i < N; i++) {Object target = r.receivers.get(i);deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); // 分发广播给注册的接收器}addBroadcastToHistoryLocked(r); // 将广播记录添加到历史记录中,以便未来查询}//...}}

该方法的目的是派发广播给接收者。代码实现如下:

//BroadcastQueueprivate final void deliverToRegisteredReceiverLocked(BroadcastRecord r,BroadcastFilter filter, boolean ordered) {boolean skip = false; // 标记是否跳过当前接收器//权限检查...// 检查Intent Firewall是否允许广播if (!skip) {skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,r.callingPid, r.resolvedType, filter.receiverList.uid);}// 如果接收器所在的应用不存在或者正在崩溃,则跳过if (filter.receiverList.app == null || filter.receiverList.app.crashing) {skip = true;}// 如果所有检查都通过,则分发广播if (!skip) {if (ordered) {// 对于有序广播,设置当前接收器和状态r.receiver = filter.receiverList.receiver.asBinder();r.curFilter = filter;filter.receiverList.curBroadcast = r;r.state = BroadcastRecord.CALL_IN_RECEIVE;if (filter.receiverList.app != null) {r.curApp = filter.receiverList.app;filter.receiverList.app.curReceiver = r;mService.updateOomAdjLocked(r.curApp); // 更新OOM调整}}try {// 这里会调用执行接收器的onReceive方法performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,new Intent(r.intent), r.resultCode, r.resultData,r.resultExtras, r.ordered, r.initialSticky, r.userId);if (ordered) {r.state = BroadcastRecord.CALL_DONE_RECEIVE; // 设置状态为CALL_DONE_RECEIVE}} catch (RemoteException e) {//...}}}

这段代码负责将广播分发给已注册的接收器,但分发前,它会进行一系列的权限和状态检查,以确保只有符合条件的接收器才能处理广播。如果检查都通过,它会调用performReceiveLocked方法来执行接收器的onReceive方法,完成广播的分发。这个过程确保了广播的安全性和正确性,防止了不符合条件的接收器处理广播,从而保护了系统的稳定性和应用的数据安全。接下来就以onReceive的回调为目标继续分析。

2.3.1 从performReceiveLocked到onReceive方法调用的分析

performReceiveLocked的代码实现如下:

//BroadcastQueueprivate static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,Intent intent, int resultCode, String data, Bundle extras,boolean ordered, boolean sticky, int sendingUser) throws RemoteException {// 如果app不为空,说明接收器是注册在某个应用进程中的if (app != null) {// 如果app的线程不为空,说明应用进程是活跃的if (app.thread != null) {// 通过应用进程的线程代理,调度接收器的onReceive调用app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,data, extras, ordered, sticky, sendingUser, app.repProcState);} else {// ...}} else {// 如果app为空,说明接收器不是注册在某个应用进程中,直接调用接收器的performReceive方法receiver.performReceive(intent, resultCode, data, extras, ordered,sticky, sendingUser);}}

如果分析过之前文章中的组件相关代码,这里的app.thread.scheduleRegisteredReceiver实际上就是调用的ActivityThread中的scheduleRegisteredReceiver方法,代码实现如下:

//ActivityThread//ApplicationThreadpublic 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);}

这里的receiver实际上是LoadedApk.ReceiverDispatcher.InnerReceiver类型的,解读如下:

IIntentReceiver是一个接口,用于接收广播。当BroadcastReceiver是动态注册的,即在代码中通过Context.registerReceiver()方法注册的,那么IIntentReceiver的实现类是LoadedApk.ReceiverDispatcher.InnerReceiver。这个实现类持有一个对LoadedApk.ReceiverDispatcher的弱引用,而ReceiverDispatcher持有一个对BroadcastReceiver的强引用。

因此这里我们先看LoadedApk.ReceiverDispatcher.InnerReceiver的实现,代码如下:

//LoadedApk//ReceiverDispatcherfinal static class InnerReceiver extends IIntentReceiver.Stub {// 弱引用,用于引用ReceiverDispatcher,防止内存泄漏final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;// 强引用,用于在需要时保持ReceiverDispatcher的活跃状态final LoadedApk.ReceiverDispatcher mStrongRef;InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd); // 创建弱引用mStrongRef = strong ? rd : null; // 如果strong为true,则创建强引用}// 实现IIntentReceiver接口的performReceive方法,该方法在收到广播时被调用public void performReceive(Intent intent, int resultCode, String data,Bundle extras, boolean ordered, boolean sticky, int sendingUser) {LoadedApk.ReceiverDispatcher rd = mDispatcher.get(); // 通过弱引用获取ReceiverDispatcherif (rd != null) {// 如果ReceiverDispatcher仍然存在,调用其performReceive方法处理广播rd.performReceive(intent, resultCode, data, extras,ordered, sticky, sendingUser);} else {// 如果ReceiverDispatcher不存在,说明它已经被垃圾回收IActivityManager mgr = ActivityManagerNative.getDefault(); // 获取ActivityManager服务try {if (extras != null) {extras.setAllowFds(false); // 确保Bundle中不包含文件描述符}// 通知ActivityManager广播已经处理完成mgr.finishReceiver(this, resultCode, data, extras, false);} catch (RemoteException e) {//...}}}}

这里的performReceive实际上是调用到LoadedApk.ReceiverDispatcher的performReceive方法,代码实现如下:

//LoadedApk//ReceiverDispatcherpublic void performReceive(Intent intent, int resultCode, String data,Bundle extras, boolean ordered, boolean sticky, int sendingUser) {// 创建一个Args对象,封装了广播的所有参数Args args = new Args(intent, resultCode, data, extras, ordered,sticky, sendingUser);// 尝试将Args对象投递到主线程的消息队列中// 这个步骤是关键,因为它决定了是否在主线程中执行onReceive方法if (!mActivityThread.post(args)) {// 如果post失败,说明主线程(ActivityThread)无法处理这个Args对象// 可能是因为主线程已经停止或者不存在了if (mRegistered && ordered) {// 获取ActivityManager服务的实例IActivityManager mgr = ActivityManagerNative.getDefault();// 通知ActivityManager广播接收已经完成args.sendFinished(mgr);}}}

performReceive方法负责将广播分发给BroadcastReceiveronReceive方法。如果BroadcastReceiver是动态注册的,那么它的onReceive方法将在应用的主线程中执行。如果主线程无法处理广播,那么对于有序广播,需要通知ActivityManager广播接收已经完成,以确保系统资源的正确释放和应用状态的更新。这个过程确保了广播的可靠性和应用响应的正确性。

这里我们关注mActivityThread.post(args)方法,这里直接调用了args.run方法,代码实现如下:

//LoadedApk//ReceiverDispatcher//Argspublic void run() {final BroadcastReceiver receiver = mReceiver; // 获取BroadcastReceiver实例final boolean ordered = mOrdered; // 是否为有序广播final IActivityManager mgr = ActivityManagerNative.getDefault(); // 获取ActivityManager服务final Intent intent = mCurIntent; // 获取当前的Intent对象mCurIntent = null; // 清空当前Intent对象,避免重复使用// 如果receiver为null或者已经被遗忘(即BroadcastReceiver已经被unregister),则结束if (receiver == null || mForgotten) {if (mRegistered && ordered) {sendFinished(mgr); // 通知ActivityManager广播接收完成}return;}try {// 获取BroadcastReceiver的类加载器ClassLoader cl = receiver.getClass().getClassLoader();// 为Intent设置类加载器,以便正确解析其中的序列化对象intent.setExtrasClassLoader(cl);// 为Args对象设置类加载器setExtrasClassLoader(cl);// 设置BroadcastReceiver的PendingResult,以便它可以异步处理广播receiver.setPendingResult(this);// 调用BroadcastReceiver的onReceive方法receiver.onReceive(mContext, intent);} catch (Exception e) {//...}// 如果BroadcastReceiver有PendingResult,调用finish方法处理结果if (receiver.getPendingResult() != null) {finish();}}

run的这段代码它负责在主线程中执行BroadcastReceiveronReceive方法。当然,这个过程包括获取BroadcastReceiver实例、设置类加载器、调用onReceive方法,并处理可能的异常。如果BroadcastReceiver有异步结果,它会在finish方法中被处理。这个过程确保了广播的可靠性和应用响应的正确性,同时也允许BroadcastReceiver异步处理广播。

2.3.2 onReceive后finish处理逻辑解读(额外解读)

最后,如果BroadcastReceiver有PendingResult,调用finish方法处理结果,finish的代码实现如下所示:

//BroadcastReceiver//PendingResultpublic final void finish() {// 如果PendingResult的类型是TYPE_COMPONENT,即广播是通过组件(如Activity或Service)发送的if (mType == TYPE_COMPONENT) {final IActivityManager mgr = ActivityManagerNative.getDefault(); // 获取ActivityManager服务的实例// 检查是否有其他挂起的工作(例如其他广播或任务)if (QueuedWork.hasPendingWork()) {// 如果有其他挂起的工作,使用单线程执行器来异步发送完成通知QueuedWork.singleThreadExecutor().execute(new Runnable() {@Overridepublic void run() {sendFinished(mgr); // 通知ActivityManager广播处理完成}});} else {// 如果没有其他挂起的工作,直接发送完成通知sendFinished(mgr);}} else if (mOrderedHint && mType != TYPE_UNREGISTERED) {// 如果这是一个有序广播,并且类型不是未注册的final IActivityManager mgr = ActivityManagerNative.getDefault(); // 获取ActivityManager服务的实例sendFinished(mgr); // 通知ActivityManager广播处理完成}}

PendingResult.finish方法确保了系统能够知道广播已经被接收器处理完毕。根据广播类型和系统状态,它可能会异步或同步地通知AMS。这里最后都会调用到sendFinished方法。该方法代码实现如下:

//BroadcastReceiver//PendingResultpublic void sendFinished(IActivityManager am) {synchronized (this) {//...mFinished = true; // 标记广播为已处理完成try {// 如果有结果额外数据,并且包含了文件描述符,则不允许它们被传递if (mResultExtras != null) {mResultExtras.setAllowFds(false);}// 如果这是一个有序广播if (mOrderedHint) {// 通知ActivityManager广播接收完成,并传递结果码、数据、额外数据和是否中止广播am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,mAbortBroadcast);} else {// 如果不是有序广播,传递默认值am.finishReceiver(mToken, 0, null, null, false);}} catch (RemoteException ex) {//...}}}

sendFinished方法用于通知系统广播接收器已经完成处理。它确保了即使在多线程环境下也能安全地更新广播状态,主要是与AMS通信以完成广播的生命周期。这里am.finishReceiver最终调用的是AMS.finishReceiver,代码实现如下:

//AMSpublic void finishReceiver(IBinder who, int resultCode, String resultData,Bundle resultExtras, boolean resultAbort) {final long origId = Binder.clearCallingIdentity();try {boolean doNext = false; // 标记是否需要处理下一个广播BroadcastRecord r; // 用于存储当前广播记录synchronized(this) {// 根据传入的who(IBinder)找到对应的BroadcastRecordr = broadcastRecordForReceiverLocked(who);if (r != null) {// 如果找到了对应的广播记录,调用finishReceiverLocked完成当前广播接收// 并更新doNext标记是否需要处理下一个广播doNext = r.queue.finishReceiverLocked(r, resultCode,resultData, resultExtras, resultAbort, true);}}// 如果需要处理下一个广播,调用processNextBroadcast处理if (doNext) {r.queue.processNextBroadcast(false);}// 修剪应用程序,优化内存使用trimApplications();} finally {Binder.restoreCallingIdentity(origId);}}

finishReceiver方法负责更新广播状态、如果需要则处理下一个广播,处理方式和之前的processNextBroadcast。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/465672.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MongoDB笔记02-MongoDB基本常用命令

文章目录 一、前言二、数据库操作2.1 选择和创建数据库2.2 数据库的删除 3 集合操作3.1 集合的显式创建3.2 集合的隐式创建3.3 集合的删除 四、文档基本CRUD4.1 文档的插入4.1.1 单个文档插入4.1.2 批量插入 4.2 文档的基本查询4.2.1 查询所有4.2.2 投影查询&#xff08;Projec…

MySQL基础-单表查询

语法 select [distinct] 列名1&#xff0c;列名2 as 别名... from数据表名 where组前筛选 group by分组字段 having组后筛选 order by排序的列 [asc | desc] limit 起始索引&#xff0c;数据条数 测试数据 # 建测试表 create table products (id int primary key a…

【pycharm jupyter】远程开发 启动报错

报错信息 upyter server process exited with code 1 ServerApp] A _jupyter_server_extension_points function was not found in jupyter_lsp. Instead, a _jupyter_server_extension_paths function was found and will be used for now. This function name will be depre…

CPU Study - Instructions Fetch

参考来源&#xff1a;《超标量处理器设计》—— 姚永斌 N-Way CPU 取指问题 如果CPU可以在每个周期内同时解码N条指令&#xff0c;则此类CPU为N-Way超标量处理器。 N-Way超标量处理器需要每个周期从I-Cache中至少取得N条指令&#xff0c;这N条指令成为一组Fetch Group。 为了…

掌握 PyQt5:从零开始的桌面应用开发

PyQT5——图形化界面 文章目录 PyQT5——图形化界面集成化图形界面工具为什么使用 \$ProjectFileDir$?示例场景其他 Varaiablespyuic参数解释整体含义示例使用PyQt5和pyuic 创建pyqt5的程序创建一个窗口app.exec\_()和sys.exit(app.exec_())的区别1. app.exec_()2. sys.exit(a…

论文阅读笔记:Image Processing GNN: Breaking Rigidity in Super-Resolution

论文阅读笔记&#xff1a;Image Processing GNN: Breaking Rigidity in Super-Resolution 1 背景2 创新点3 方法4 模块4.1 以往SR模型的刚性4.2 图构建4.2.1 度灵活性4.2.2 像素节点灵活性4.2.3 空间灵活性 4.3 图聚合4.4 多尺度图聚合模块MGB4.5 图聚合层GAL 5 效果5.1 和SOTA…

PMP–一、二、三模、冲刺–分类–7.成本管理–技巧–挣值分析

文章目录 技巧一模7.成本管理--4.控制成本--数据分析--挣值分析--进度绩效指数&#xff08;SPI&#xff09;是测量进度效率的一种指标&#xff0c;表示为挣值与计划价值之比&#xff0c;反映了项目团队完成工作的效率。 当 SPI小于 1.0 时&#xff0c;说明已完成的工作量未达到…

保姆级教程!!教你通过【Pycharm远程】连接服务器运行项目代码

小罗碎碎念 这篇文章主要解决一个问题——我有服务器&#xff0c;但是不知道怎么拿来写代码&#xff0c;跑深度学习项目。确实&#xff0c;玩深度学习的成本比较高&#xff0c;无论是前期的学习成本&#xff0c;还是你需要具备的硬件成本&#xff0c;都是拦路虎。小罗没有办法…

成绩管理系统软件体系结构设计

成绩管理系统软件体系结构设计 文档简介 1.1 目的 1.2 范围 1.3 定义、首字母缩写词和缩略语 1.4参考资料 1.5 概述体系结构表示方式软件体系结构的目标和约束 3.1 结构清晰 3.2 支持外包开发 3.3 可扩展性 3.4 系统安全性 3.5 可移植性 4体系结构模式逻辑视图进程视图…

单臂路由实现不同VLAN之间设备通信

转载请注明出处 本实验为单臂路由配置&#xff0c;目的为让不同VLAN之间的设备能够互相通信。 1.首先&#xff0c;按照要求配置两个pc的ip地址&#xff0c;以pc0为例子&#xff1a; 2在交换机创建vlan10和vlan20 3.划分vlan&#xff0c;pc0为vlan10的设备&#xff0c;pc1为vla…

机器学习(三)——决策树(附核心思想、重要算法、概念(信息熵、基尼指数、剪枝处理)及Python源码)

目录 关于1 基本流程2 划分属性的选择2.1 方法一&#xff1a;依据信息增益选择2.2 方法二&#xff1a;依据增益率选择2.3 方法三&#xff1a;依据基尼指数选择 3 剪枝处理&#xff1a;防止过拟合3.1 预剪枝3.2 后剪枝 4 连续与缺失值4.1 连续值处理4.2 缺失值处理 5 多变量决策…

Ubuntu和Debian系列的Release默认shell解释器变更

Debian 12 Bookworm 和 Ubuntu 24.04 中默认的 shell 解释器已经由 bash 变更为了 dash 。 这个变化对于我们直接在 CLI 上执行 Linux command 无影响&#xff0c;但对于执行shell解释性程序有影响&#xff0c;已知 bash 中的 变量正规表达式 &#xff08;如 ${GIT_COMMIT:0:8…

ReLU6替换ReLU为什么可以增强硬件效率?

ReLU6&#xff08;Rectified Linear Unit 6&#xff09;是ReLU的一种变体&#xff0c;它在ReLU的基础上增加了一个上限值6&#xff0c;即输出范围被限制在[0, 6]之间。 这种变化在硬件实现中可以带来以下几个方面的效率提升&#xff1a; 1. 数据表示的简化 ReLU的输出范围是[…

vscode在windows和linux如何使用cmake构建项目并make生成可执行文件,两者有什么区别

vscode在windows和linux如何使用cmake构建项目并make生成可执行文件&#xff0c;两者有什么区别 windows默认使用的是最新的visual studio&#xff0c;而linux默认就是cmake 文章目录 vscode在windows和linux如何使用cmake构建项目并make生成可执行文件&#xff0c;两者有什么…

Spirngboot集成Knife4j spirngboot版本2.7.17 Knife4j版本4.0.0

Knife4j是什么&#xff1f;有什么作用&#xff1f; ‌Knife4j‌是一个基于Swagger的Java RESTful API文档工具&#xff0c;旨在帮助开发者轻松生成和维护API文档。它继承并增强了Swagger的功能&#xff0c;简化了使用流程&#xff0c;并提供了一系列增强功能&#xff0c;如接口…

ROS2humble版本使用colcon构建包

colcon与与catkin相比&#xff0c;没有 devel 目录。 创建工作空间 首先&#xff0c;创建一个目录 ( ros2_example_ws ) 来包含我们的工作区: mkdir -p ~/ros2_example_ws/src cd ~/ros2_example_ws 此时&#xff0c;工作区包含一个空目录 src : . └── src1 directory, …

GY-56 (VL53L0X) 激光测距

文章目录 一、GY-56 简介二、引脚功能三、通信协议1.串口协议&#xff1a; 当 GY-56 PS 焊点开放时候使用(默认)&#xff08;1&#xff09;串口通信参数&#xff08;默认波特率值 9600bps&#xff09;&#xff08;2&#xff09;模块输出格式&#xff0c;每帧包含 8-13 个字节&a…

C语言 | Leetcode C语言题解之第541题反转字符串II

题目&#xff1a; 题解&#xff1a; void swap(char* a, char* b) {char tmp *a;*a *b, *b tmp; }void reverse(char* l, char* r) {while (l < r) {swap(l, --r);} }int min(int a, int b) {return a < b ? a : b; }char* reverseStr(char* s, int k) {int n strl…

提升网站安全性 HTTPS的重要性与应用指南

内容概要 在如今数字化快速发展的时代&#xff0c;网站安全显得尤为重要。许多用户在访问网站时&#xff0c;尤其是涉及个人信息或金融交易时&#xff0c;对数据传输的安全性有着高度的关注。HTTPS&#xff08;超文本传输安全协议&#xff09;正是为了满足这种需求而诞生的。通…

Transformer究竟是什么?预训练又指什么?BERT

目录 Transformer究竟是什么? 预训练又指什么? BERT的影响力 Transformer究竟是什么? Transformer是一种基于自注意力机制(Self-Attention Mechanism)的神经网络架构,它最初是为解决机器翻译等序列到序列(Seq2Seq)任务而设计的。与传统的循环神经网络(RNN)或卷…