Android Framework AMS(11)广播组件分析-2(注册/注销流程解读)

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


本章关键点总结 & 说明:

说明:本章节主要解读广播组件的动态注册/注销过程及静态注册过程。关注思维导图中左上侧部分即可。

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

我们先对广播组件的整体设计思路有一个了解,再详细分析其接收器注册/注销关键流程。

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 AMS.registerReceiver动态注册流程解读

准备一个IIntentReceiver对象,调用AMS的registerReceiver方法。代码实现如下:

//AMS
    public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {
        // 确保调用者不是隔离的进程
        enforceNotIsolatedCaller("registerReceiver");
        
        // 获取调用者的UID和PID
        int callingUid;
        int callingPid;
        synchronized(this) {
            ProcessRecord callerApp = null;
            if (caller != null) {
                // 如果caller非空,则获取其对应的ProcessRecord
                callerApp = getRecordForAppLocked(caller);
                // ...
                callingUid = callerApp.info.uid;
                callingPid = callerApp.pid;
            } else {
                // 如果caller为空,则使用Binder框架获取调用者的包名、UID和PID
                callerPackage = null;
                callingUid = Binder.getCallingUid();
                callingPid = Binder.getCallingPid();
            }

            // 处理传入的用户ID
            userId = this.handleIncomingUser(callingPid, callingUid, userId,
                    true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);

            // 查找匹配的粘性广播
            // 初始化一个列表,用于存储找到的粘性广播
            List allSticky = null;

            // 获取IntentFilter的action迭代器
            Iterator actions = filter.actionsIterator();
            if (actions != null) {
                // 如果存在action,则遍历每一个action
                while (actions.hasNext()) {
                    String action = (String)actions.next();
                    // 对于每个action,尝试获取匹配该action的粘性广播
                    // getStickiesLocked方法会根据action和IntentFilter来查找粘性广播
                    allSticky = getStickiesLocked(action, filter, allSticky, UserHandle.USER_ALL);
                    // 再次查找,这次是针对发送广播的调用者的用户ID
                    allSticky = getStickiesLocked(action, filter, allSticky, UserHandle.getUserId(callingUid));
                }
            } else {
                // 如果没有特定的action,说明广播接收器对所有动作都感兴趣
                // 首先尝试获取针对所有用户的粘性广播
                allSticky = getStickiesLocked(null, filter, allSticky, UserHandle.USER_ALL);
                // 然后尝试获取针对调用者用户ID的粘性广播
                allSticky = getStickiesLocked(null, filter, allSticky, UserHandle.getUserId(callingUid));
            }

            // 如果有匹配的粘性广播,则取出第一个Intent
            Intent sticky = allSticky != null ? (Intent)allSticky.get(0) : null;

            if (receiver == null) {
                return sticky;
            }

            // 获取或创建ReceiverList对象
            //这里出现了一个关键成员变量:mRegisteredReceivers
            ReceiverList rl = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());
            if (rl == null) {
                rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                        userId, receiver);
                // 如果ReceiverList中的app字段(即ProcessRecord)不为null,
                // 说明这个接收器属于一个应用进程,那么将ReceiverList添加到该进程的接收器列表中。
                if (rl.app != null) {
                    rl.app.receivers.add(rl);
                } else {
                    // 如果app为null,说明这个接收器不属于任何应用进程(可能是一个隐藏的API使用情况或者是系统服务),
                    // 那么需要将ReceiverList与receiver的Binder对象关联起来,以便在接收器进程死亡时能够收到通知。
                    try {
                        receiver.asBinder().linkToDeath(rl, 0);
                    } catch (RemoteException e) {
                        return sticky;
                    }
                    rl.linkedToDeath = true;
                }
                // 将新创建的ReceiverList对象放入mRegisteredReceivers映射中,以便将来能够找到并管理这个接收器。
                mRegisteredReceivers.put(receiver.asBinder(), rl);
            }
            //...

            // 创建BroadcastFilter对象并添加到ReceiverList和mReceiverResolver中
            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,permission, callingUid, userId);
            rl.add(bf);
            //这里出现了另一个关键成员变量:mReceiverResolver
            mReceiverResolver.addFilter(bf);

            // 如果有匹配的粘性广播,则将它们分发给新的接收器,需要立即调度广播并发送
            if (allSticky != null) {
                ArrayList receivers = new ArrayList();
                receivers.add(bf);

                int N = allSticky.size();
                // 遍历列表中的每一个粘性广播
                for (int i=0; i<N; i++) {
                    Intent intent = (Intent)allSticky.get(i);
                    // 为当前的Intent找到对应的BroadcastQueue。BroadcastQueue是广播分发的队列,它根据Intent的属性(如用户ID)来确定
                    BroadcastQueue queue = broadcastQueueForIntent(intent);
                    // 创建一个新的BroadcastRecord对象,它代表了即将被分发的广播。BroadcastRecord包含了广播的所有相关信息,如:
                    /* queue:广播队列。
                     * intent:广播的Intent。
                     * receivers:接收器列表,这里只包含了新注册的接收器。
                     * AppOpsManager.OP_NONE:应用操作码,这里使用OP_NONE表示没有特定的操作。
                     * 其他参数根据需要进行设置,有些参数在这里设置为null或特定的值。
                     */
                    BroadcastRecord r = new BroadcastRecord(queue, intent, null,
                            null, -1, -1, null, null, AppOpsManager.OP_NONE, receivers, null, 0,
                            null, null, false, true, true, -1);
                    // 将新的BroadcastRecord加入到BroadcastQueue中。这个方法会在广播队列的锁定状态下将广播加入队列,以便稍后处理。
                    queue.enqueueParallelBroadcastLocked(r);
                    // 在广播队列的锁定状态下检查并处理队列中的广播,确保它们按照正确的顺序和时机被分发。
                    queue.scheduleBroadcastsLocked();
                }
            }

            // 返回匹配的粘性广播
            return sticky;
        }
    }

我们可以将这段代码分成以下几个阶段来理解,分别解读如下:

@1 权限和调用者验证及用户ID处理阶段

  • 确保调用者不是隔离的进程:通过enforceNotIsolatedCaller方法确保调用registerReceiver的进程不是隔离进程。
  • 获取调用者的UID和PID:根据传入的IApplicationThread(caller)和String(callerPackage),获取调用者的ProcessRecord,进而获取UID和PID。如果callernull,则通过Binder框架直接获取。
  • 处理传入的用户ID:通过handleIncomingUser方法处理传入的用户ID,确保操作在正确的用户上下文中进行。

@2 粘性广播查找阶段

  • 查找匹配的粘性广播:遍历IntentFilter中的动作,查找与这些动作匹配的粘性广播。粘性广播是一种特殊的广播,它可以被保存并在BroadcastReceiver注册后立即发送给它。

@3 广播接收器注册阶段:关键变量mRegisteredReceivers相关操作

  • 获取或创建ReceiverList对象:通过mRegisteredReceivers(一个关键成员变量)获取与receiver关联的ReceiverList,如果不存在则创建一个新的ReceiverList
  • 关联Binder死亡通知:如果ReceiverList中的app字段为null,则将ReceiverListreceiver的Binder对象关联起来,以便在接收器进程死亡时能够收到通知。

@4 广播过滤器创建和关联:关键变量mReceiverResolver相关操作

  • 创建BroadcastFilter对象:创建一个新的BroadcastFilter对象,它包含了IntentFilterReceiverList、权限等信息。
  • 添加到ReceiverList和mReceiverResolver:将BroadcastFilter添加到ReceiverListmReceiverResolver中。mReceiverResolver是另一个关键成员变量,它用于存储和解析广播过滤器。

@5 粘性广播分发阶段

  • 分发粘性广播:如果有匹配的粘性广播,创建BroadcastRecord并将其加入到对应的BroadcastQueue中,以便稍后处理。
  • 返回匹配的粘性广播:返回找到的第一个匹配的粘性广播,如果没有则返回null

整体来看,这段代码处理了从验证调用者权限、查找粘性广播、注册广播接收器、创建广播过滤器到分发粘性广播的完整流程。这些步骤确保了BroadcastReceiver能够正确注册,并且能够接收到它感兴趣的广播。

2.2 AMS.unregisterReceiver动态注销流程解读

调用AMS的unregisterReceiver方法。代码实现如下:

//AMS
    public void unregisterReceiver(IIntentReceiver receiver) {
        final long origId = Binder.clearCallingIdentity();
        try {
            boolean doTrim = false; // 标记是否需要修剪应用程序工作集

            synchronized(this) {
                // 从mRegisteredReceivers映射中获取与传入receiver关联的ReceiverList对象
                ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
                if (rl != null) {
                    // 如果当前ReceiverList有正在处理的广播
                    if (rl.curBroadcast != null) {
                        // 获取当前正在处理的广播记录
                        BroadcastRecord r = rl.curBroadcast;
                        // 完成当前接收器的广播处理,并返回是否需要处理下一个广播
                        final boolean doNext = finishReceiverLocked(
                                receiver.asBinder(), r.resultCode, r.resultData,
                                r.resultExtras, r.resultAbort);
                        // 如果需要处理下一个广播
                        if (doNext) {
                            doTrim = true; // 设置标记为true
                            // 处理下一个广播
                            r.queue.processNextBroadcast(false);
                        }
                    }

                    // 如果ReceiverList关联了应用进程
                    if (rl.app != null) {
                        // 从应用进程的接收器列表中移除当前ReceiverList
                        rl.app.receivers.remove(rl);
                    }
                    // 从mRegisteredReceivers映射中移除ReceiverList
                    removeReceiverLocked(rl);
                    // 如果ReceiverList关联了Binder死亡通知
                    if (rl.linkedToDeath) {
                        rl.linkedToDeath = false;
                        rl.receiver.asBinder().unlinkToDeath(rl, 0);
                    }
                }
            }
            // 如果完成了广播处理,可能需要修剪应用程序工作集
            if (doTrim) {
                trimApplications();
                return;
            }

        } finally {
            // 恢复调用者的身份信息
            Binder.restoreCallingIdentity(origId);
        }
    }

这段代码的关键逻辑包括:

  1. 广播处理完成:如果ReceiverList中还有正在处理的广播,会调用finishReceiverLocked方法来完成当前接收器的广播处理,并根据返回值决定是否需要处理下一个广播。
  2. 移除ReceiverList:从mRegisteredReceivers映射中移除对应的ReceiverList对象,这意味着该接收器不再接收任何广播。
  3. 取消Binder死亡通知:如果ReceiverList关联了Binder死亡通知,需要取消这个关联,以防止在接收器进程死亡时AMS接收到不必要的通知。
  4. 修剪应用程序工作集:如果完成了广播处理,可能需要调用trimApplications方法来修剪应用程序工作集,释放一些资源。

其中removeReceiverLocked的功能实现,即从系统中移除一个ReceiverList对象的具体实现如下:

//AMS
    void removeReceiverLocked(ReceiverList rl) {
        // 从mRegisteredReceivers映射中移除与rl关联的Binder对象
        mRegisteredReceivers.remove(rl.receiver.asBinder());
        
        // 获取ReceiverList中的BroadcastFilter数量
        int N = rl.size();
        // 遍历ReceiverList中的每个BroadcastFilter
        for (int i = 0; i < N; i++) {
            // 从mReceiverResolver中移除每个BroadcastFilter
            mReceiverResolver.removeFilter(rl.get(i));
        }
    }

这段代码的关键点在于:

  1. 移除Binder引用:从mRegisteredReceivers映射中移除与ReceiverList关联的Binder引用,这意味着该ReceiverList不再被AMS跟踪和管理。
  2. 遍历BroadcastFilter:遍历ReceiverList中的每个BroadcastFilter对象,这些对象代表了与ReceiverList关联的广播过滤器。
  3. 移除BroadcastFilter:从mReceiverResolver中移除每个BroadcastFilter对象,mReceiverResolver是AMS中用于解析和分发广播的关键组件。

这个方法确保了一个ReceiverList及其关联的所有BroadcastFilter从AMS中被彻底移除,这是广播接收器注销流程中的重要一步。

总之,整个过程就是将对应的receiver安全地删除掉。

2.3 静态注册receiver流程解读

静态 receiver 是android应用在 AndroidManifest.xml 文件中声明的 receiver,像这样:

<receiver
    android:name=".YourBroadcastReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="your.action.YOUR_ACTION" />
    </intent-filter>
</receiver>

它们的信息会在系统启动时,由PMS解析并记录下来。当 AMS 调用 PMS 的接口来查询 “和 intent 匹配的组件” 时,PMS 内部就会去查询当初记录下来的数据,并把结果返回 AMS。

这里PMS在初始化时其中一部分是通过scanDirLI相关方法来初始化成员变量mReceivers的,关于PMS的初始化部分内容,想有更多了解可参考文章:

Android Framework 包管理子系统(01)PackageManagerService启动分析

这里主要看以scanDirLI为入口,分析mReceivers的初始化部分逻辑,代码实现如下所示:

//PMS
    //关键流程:step1
    private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
        final File[] files = dir.listFiles();
        //...
        for (File file : files) {
            final boolean isPackage = (isApkFile(file) || file.isDirectory())
                    && !PackageInstallerService.isStageName(file.getName());
            if (!isPackage) {
                // Ignore entries which are not packages
                continue;
            }
            try {
                scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
                        scanFlags, currentTime, null);
            } catch (PackageManagerException e) {
                //...
            }
        }
    }
    //关键流程:step2
    private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
            long currentTime, UserHandle user) throws PackageManagerException {
        //...
        PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
                | SCAN_UPDATE_SIGNATURE, currentTime, user);
        //...
        return scannedPkg;
    }
    //关键流程:step3
    private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,
            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
        boolean success = false;
        try {
            final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,
                    currentTime, user);
            success = true;
            return res;
        } finally {
            //...
        }
    }
    //关键流程:step4
    private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
        //...
        synchronized (mPackages) {
            //...
            int N = pkg.receivers.size(); // 获取应用包中广播接收器的数量
            StringBuilder r = null; // 用于存储接收器名称的StringBuilder
            for (int i = 0; i < N; i++) { // 遍历应用包中的所有广播接收器
                // 获取广播接收器的Activity对象
                PackageParser.Activity a = pkg.receivers.get(i);
                a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        a.info.processName, pkg.applicationInfo.uid);
                // 将广播接收器添加到mReceivers中
                mReceivers.addActivity(a, "receiver");
                // 如果设置了PARSE_CHATTY标志,记录广播接收器的名称
                if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) {
                    if (r == null) {
                        r = new StringBuilder(256);
                    } else {
                        r.append(' ');
                    }
                    r.append(a.info.name);
                }
            }
            //...
        }
        //...
        //返回处理后的应用包对象
        return pkg;
    }

对于静态注册的解析,实际上到此就结束了。当发送广播时会通过PMS的queryIntentReceivers方法来查询可以处理给定Intent的广播接收器(BroadcastReceiver)。接下来我们来查看该方法的实现,代码如下:

//PMS
    @Override
    public 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);
            // 获取对应组件的ActivityInfo
            ActivityInfo ai = getReceiverInfo(comp, flags, userId);
            // 如果找到了对应的ActivityInfo
            if (ai != null) {
                // 创建一个ResolveInfo对象,并填充ActivityInfo
                ResolveInfo 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);
            // 如果找到了对应的Package
            if (pkg != null) {
                // 使用mReceivers查询指定包中匹配的广播接收器
                return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers,userId);
            }
            // 如果没有找到对应的Package,返回null
            return null;
        }
    }

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

总体来说,静态接收器注册后,信息是放在PMS中的,而当发送广播时,会通过PMS的receiver查询方法queryIntentReceivers方法来获取对应的接收器相关信息。

3 注册/注销的关键表解读

针对广播组件的所谓注册表,我们主要关注2个关键的成员变量:mRegisteredReceivers和mReceiverResolver。接下来对其进行重点详细解读。

3.1 mRegisteredReceivers解读

mRegisteredReceivers是Android系统中ActivityManagerService(AMS)的一个关键成员变量,其设计目的和意义主要体现在以下几个方面:

  • 记录已注册的广播接收器mRegisteredReceivers用于记录所有已经被注册给广播的IIntentReceiver。它作为一个HashMap,以IIntentReceiver的Binder代理对象为键,以ReceiverList对象为值,存储了所有动态注册的广播接收器的信息。
  • 快速查找和分发广播:当有广播发送时,AMS需要能够快速找到所有匹配的广播接收器。mRegisteredReceivers使得AMS能够根据接收器的Binder代理对象快速定位到对应的ReceiverList,进而将广播分发给正确的接收器。
  • 管理多个广播接收器:一个应用可能注册了多个广播接收器,mRegisteredReceivers通过ReceiverList来管理这些接收器。ReceiverList表示一个注册了一个或多个广播的receiver,这样当收到广播时,AMS可以根据传入的receiver在mRegisteredReceivers快速找到对应的ReceiverList
  • 进程管理和生命周期关联ReceiverList中包含了与特定BroadcastReceiver关联的进程信息(如ProcessRecord),这使得当广播需要被分发时,系统能够知道该广播接收器所在的进程状态,以及是否需要启动进程来处理广播。
  • Binder死亡通知:如果ReceiverList中的app字段为null,说明这个接收器不属于任何应用进程,那么需要将ReceiverList与receiver的Binder对象关联起来,以便在接收器进程死亡时能够收到通知。
  • 动态注册与注销mRegisteredReceivers在动态注册和注销BroadcastReceiver时起到了关键作用。在注册时,它保存了ReceiverList以便将来匹配和分发广播;在注销时,它会移除对应的ReceiverList,确保不再向已注销的接收器分发广播。

可以看出,mRegisteredReceivers的设计目的是为了高效地管理动态注册的广播接收器,确保广播可以准确地分发到正确的接收器,并且能够处理接收器的生命周期事件。这种设计使得Android的广播机制更加灵活和高效。

3.2 mReceiverResolver解读

mReceiverResolver 在Android系统中的设计目的是为了高效地解析和分发广播。它是一个IntentResolver对象,专门用来处理广播意图(Intent)的解析工作。以下是mReceiverResolver的主要作用和设计意义:

  • 解析IntentmReceiverResolver负责解析传入的广播Intent,根据Intent中的动作(Action)、数据(Data)、类型(Type)等信息,找到与之匹配的BroadcastReceiver。这是通过维护一个BroadcastFilter列表来实现的,每个BroadcastFilter都包含了一个IntentFilter,用于匹配特定的Intent。
  • 维护BroadcastFilter列表mReceiverResolver中保存了所有动态注册的广播接收器对应的BroadcastFilter。当应用调用registerReceiver方法时,会创建一个BroadcastFilter对象,并将其添加到mReceiverResolver中。
  • 快速匹配:在广播发送时,mReceiverResolver可以根据Intent快速匹配到对应的BroadcastFilter,进而找到对应的ReceiverList,这个列表中包含了所有对该Intent感兴趣的BroadcastReceiver
  • 广播分发:找到匹配的BroadcastFilter后,mReceiverResolver会将广播分发给对应的BroadcastReceiver。这个过程是广播发送流程中的关键步骤,确保了正确的接收器能够接收到正确的广播。
  • 动态注册与注销mReceiverResolver在动态注册和注销BroadcastReceiver时也起到了关键作用。在注册时,它保存了BroadcastFilter以便将来匹配和分发广播;在注销时,它会移除对应的BroadcastFilter,确保不再向已注销的接收器分发广播。

可以看出,mReceiverResolver是Android系统中广播机制的核心组件之一,它通过维护一个BroadcastFilter列表来实现对广播Intent的快速解析和分发,从而保证了广播机制的高效性和准确性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Llamaindex RAG 实践

大模型支持的最强大的应用程序之一是复杂的问答聊天机器人。这些应用程序可以回答有关特定源信息的问题。这些应用程序使用一种称为检索增强生成 &#xff08;RAG&#xff09; 的技术。 1. 什么是RAG&#xff1f; 当你需要给模型注入新的知识时&#xff0c;有两种方法&#xf…

C#基础入门--类的建立与使用

上周刚开C#&#xff0c;这门课&#xff0c;第一节课就感觉不对劲了&#xff0c;感觉跟java很像(上图C#&#xff0c;下图java),进来页面都差不多&#xff1a; 这里介绍以下我C#的第一个程序&#xff0c;以类的思想定义一个student类&#xff0c;用户输入类中的属性信息后&#x…

LangChain Ollama实战文献检索助手(二)少样本提示FewShotPromptTemplate示例选择器

本期是用样例来提示大模型生成我们想要的答案。即在输入中给定提示的样例&#xff0c;以及提示模板&#xff0c;然后匹配较相关的样例进行文献综述。 创建示例样本FewShotPromptTemplate 这里我用GTP-o1生成了几个回答&#xff0c;作为样本 samples [{"theme": &…

GNN系统学习:简单图论、环境配置、PyG中图与图数据集的表示和使用

Reference datawhale开源学习资料 开篇 1.1 为什么要在图上进行深度学习&#xff1f; 在过去的深度学习应用中&#xff0c;我们接触的数据形式主要是这四种&#xff1a;矩阵、张量、序列&#xff08;sequence&#xff09;和时间序列&#xff08;time series&#xff09;。然…

嵌入式面试八股文(六)·ROM和RAM的区别、GPIO的八种工作模式、串行通讯和并行通讯的区别、同步串行和异步串行的区别

目录 1. ROM和RAM的区别 2. GPIO的八种工作模式 3. 串行通讯和并行通讯的区别 3.1 串行通讯 3.2 并行通讯 3.3 对比 4. 同步串行和异步串行的区别 4.1 时钟信号 4.2 数据传输效率 4.3 应用场景 4.4 硬件复杂性 1. ROM和RAM的区别 ROM&#xff08;Read-O…

批量缓存模版

批量缓存模版 缓存通常有两种使用方式&#xff0c;一种是Cache-Aside&#xff0c;一种是cache-through。也就是旁路缓存和缓存即数据源。 一般一种用于读&#xff0c;另一种用于读写。参考后台服务架构高性能设计之道。 最典型的Cache-Aside的样例&#xff1a; //读操作 da…

Vue3学习笔记(上)

Vue3学习笔记&#xff08;上&#xff09; Vue3的优势&#xff1a; 更容易维护&#xff1a; 组合式API更好的TypeScript支持 更快的速度&#xff1a; 重写diff算法模板编译优化更高效的组件初始化 更小的体积&#xff1a; 良好的TreeShaking按需引入 更优的数据响应式&#xf…

SPIRE: Semantic Prompt-Driven Image Restoration 论文阅读笔记

这是一篇港科大学生在google research 实习期间发在ECCV2024的语义引导生成式修复的文章&#xff0c;港科大陈启峰也挂了名字。从首页图看效果确实很惊艳&#xff0c;尤其是第三行能用文本调控修复结果牌上的字。不过看起来更倾向于生成&#xff0c;对原图内容并不是很复原&…

Knowledge Graph-Enhanced Large Language Models via Path Selection

研究背景 研究问题&#xff1a;这篇文章要解决的问题是大型语言模型&#xff08;LLMs&#xff09;在生成输出时存在的事实不准确性&#xff0c;即所谓的幻觉问题。尽管LLMs在各种实际应用中表现出色&#xff0c;但当遇到超出训练语料库范围的新知识时&#xff0c;它们通常会生…

常见计算机网络知识整理(未完,整理中。。。)

TCP和UDP区别 TCP是面向连接的协议&#xff0c;发送数据前要先建立连接&#xff1b;UDP是无连接的协议&#xff0c;发送数据前不需要建立连接&#xff0c;是没有可靠性&#xff1b; TCP只支持点对点通信&#xff0c;UDP支持一对一、一对多、多对一、多对多&#xff1b; TCP是…

HashMap(深入源码追踪)

一篇让你搞懂HashMap的几个最重要的知识点,往源码跟踪可以让我们很轻松应对所谓的一些八股面试题. 一. 属性解释 先来解释HashMap中重要的常量属性值 DEFAULT_INITIAL_CAPACITY : 默认初始化容量,也就是如果不指定初始化的Map存储容量大小,默认生成一个存储16个空间的Map集合…

MySQL中的事务与锁

目录 事务 InnoDB 和 ACID 模型 原⼦性的实现 持久性的实现 ​隔离性的实现 锁 隔离级别 ​多版本控制(MVCC) 事务 1.什么是事务? 事务是把⼀组SQL语句打包成为⼀个整体&#xff0c;在这组SQL的执⾏过程中&#xff0c;要么全部成功&#xff0c;要么全部失败&#…

C#开发基础:WPF和WinForms关于句柄使用的区别

1、前言 在 Windows 应用程序开发中&#xff0c;WPF&#xff08;Windows Presentation Foundation&#xff09;和 WinForms&#xff08;Windows Forms&#xff09;是两种常见的用户界面&#xff08;UI&#xff09;框架。它们各自有不同的架构和处理方式&#xff0c;其中一个显…

基于.NET开源、功能强大且灵活的工作流引擎框架

前言 工作流引擎框架在需要自动化处理复杂业务流程、提高工作效率和确保流程顺畅执行的场景中得到了广泛应用。今天大姚给大家推荐一款基于.NET开源、功能强大且灵活的工作流引擎框架&#xff1a;elsa-core。 框架介绍 elsa-core是一个.NET开源、免费&#xff08;MIT License…

.NET6中WPF项目添加System.Windows.Forms引用

.NET6中WPF项目添加System.Windows.Forms引用 .NET6的WPF自定义控件默认是不支持System.Windows.Forms引用的&#xff0c;需要添加这个引用方法如下&#xff1a; 1. 在项目浏览器中找到项目右击&#xff0c;选择编辑项目文件&#xff08;Edit Project File&#xff09;。 …

16.UE5拉怪机制,怪物攻击玩家,伤害源,修复原视频中的BUG

2-18 拉怪机制&#xff0c;怪物攻击玩家、伤害源、黑板_哔哩哔哩_bilibili 目录 1.实行行为树实现拉怪机制 1.1行为树黑板 1.2获取施加伤害对象&#xff08;伤害源&#xff09; 2.修复原视频中&#xff0c;第二次攻击怪物后&#xff0c;怪物卡在原地不动的BUG 3.怪物攻击玩…

<项目代码>YOLOv8 草莓成熟识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

Vue全栈开发旅游网项目(9)-用户登录/注册及主页页面开发

1.用户登录页面开发 1.查询vant组件 2.实现组件模板部分 3.模型层准备 4.数据上传 1.1 创建版权声明组件Copyright 新建文件&#xff1a;src\components\common\Copyright.vue <template><!-- 版权声明 --><div class"copyright">copyright xx…

后台管理系统窗体程序:文章管理 > 文章列表

目录 文章列表的的功能介绍&#xff1a; 1、进入页面 2、页面内的各种功能设计 &#xff08;1&#xff09;文章表格 &#xff08;2&#xff09;删除按钮 &#xff08;3&#xff09;编辑按钮 &#xff08;4&#xff09;发表文章按钮 &#xff08;5&#xff09;所有分类下拉框 &a…

【万字详解】如何在微信小程序的 Taro 框架中设置静态图片 assets/image 的 Base64 转换上限值

设置方法 mini 中提供了 imageUrlLoaderOption 和 postcss.url 。 其中&#xff1a; config.limit 和 imageUrlLoaderOption.limit 服务于 Taro 的 MiniWebpackModule.js &#xff0c; 值的写法要 &#xff08;&#xff09;KB * 1024。 config.maxSize 服务于 postcss-url 的…