Android 广播发送流程分析

在上一篇文章中Android 广播阻塞、延迟问题分析方法讲了广播阻塞的分析方法,但是分析完这个问题,自己还是有一些疑问:

  • 广播为啥会阻塞呢?发送给接收器就行了,为啥还要等着接收器处理完才处理下一个?
  • 由普通的后台广播改为前台广播后,为啥处理的会更快?

这篇参考文章Android 9.0系统源码_广播(三)广播的发送来学习下广播的分发过程,这样对广播阻塞这个问题会理解更深刻。

首先,复习下广播相关的概念。

一、广播相关概念

广播分类

1、标准广播:异步广播,广播发出后,所有注册了的广播接收器都会同时接收到该广播。打个比方:做地铁过程中的语音播报,当列车员(广播发出者)进行语音播报(发送广播)时,所有乘客(注册接收该广播的程序)都可以同时听到语音,不分先后顺序。

sendBroadcast(); 

2、有序广播:同步发送,广播发出后,按照注册了的广播接收器的优先级顺序广播,优先级的范围是-1000~1000,数字越大,优先级越高,最先接收到该广播,接收器的优先级通过android:priority设置,并且先收到广播的可以修改或者抛弃该广播,后面优先级小的接收器就无法收到了。同级别的广播,动态优先于静态;同级别同类型的广播,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。

sendOrderedBroadcast();

3、粘性广播:粘性广播的机制就是发送广播的时候如果没有找到接收器,就会先保存下来,等有广播接收器注册的时候,再把之前保存的广播发出去。因为从Android 5.0(API 21)开始,因为安全性的问题,官方已经正式废弃了粘性广播。

sendStickyBroadcast();

广播注册方式

1、动态注册:在Context(即Service或Activity)组件中注册,通过registerReceiver()方法注册,在不使用时取消注册unregisterReceiver()。如果广播发送的时候注册Broadcast的组件没有启动的话,是收不到广播的。

2、 静态注册:在AndroidManifest.xml中注册,并在intent-filter中添加响应的action,并新建一个Broadcast组件类来处理注册广播。如果广播发送的时候Broadcast的组件类进程没有启动的话,会收到广播并启动进程。

具体实现可参考Android的有序广播和无序广播(解决安卓8.0版本之后有序广播的接收问题) 区分了Android8.0前后的实现方式。

前台广播及后台广播

前台广播:在广播发送的时候添加Intent.FLAG_RECEIVER_FOREGROUND flag。
后台广播:在广播发送的时候没有Intent.FLAG_RECEIVER_FOREGROUND flag。

默认情况下,Intent是不带Intent.FLAG_RECEIVER_FOREGROUND 的flag的,所以我们默认使用的都是后台广播。

二、广播发送流程

参考文章写得非常详细,建议大家好好看看,我是看完又按照自己的思路整理了一遍写个笔记,只是大体的逻辑思路,重点是帮助我搞清楚上面对广播的一些疑问。

1、调用ContextImpl的sendBroadcast发送广播

frameworks/base/core/java/android/app/ContextImpl.java

@Override
public void sendBroadcast(Intent intent) {
    warnIfCallingFromSystemProcess();
    String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
    try {
        intent.prepareToLeaveProcess(this);
        //直接调用AMS的broadcastIntent方法
        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();
    }
}

2、调用AMS的broadcastIntent方法

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

// 发送广播
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是否合法
        intent = verifyBroadcastLocked(intent);
        // 根据caller从缓存mLruProcesses中获取进程对象ProcessRecord
        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;
    }
}

3、调用AMS的broadcastIntentLocked方法

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

这个方法非常长,拆开分别来看:

3.1 系统广播处理broadcastIntentLocked

final 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) {
        intent = new Intent(intent);
        final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
        // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
        if (callerInstantApp) {
            intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
        }
        
        // By default broadcasts do not go to stopped apps.
        // 增加下面flag,默认不发送广播到已经停止的app
        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);// 只发给注册的receiver
        }
        
        // 获取当前发送广播应用所在用户的userId
        userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
                ALLOW_NON_FULL, "broadcast", callerPackage);

        // Make sure that the user who is receiving this broadcast or its parent is running.
        // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
        if (userId != UserHandle.USER_ALL && !mUserController.isUserOrItsParentRunning(userId)) {
            if ((callingUid != SYSTEM_UID
                    || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
                    && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
                return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
            }
        }
        
        final String action = intent.getAction();
        BroadcastOptions brOptions = null;
        ...
        // 验证是不是受保护的广播(是不是系统广播)
        final boolean isProtectedBroadcast;
        try {           
            isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
        } catch (RemoteException e) {
            return ActivityManager.BROADCAST_SUCCESS;
        }
        
		// 检查是不是系统调用
        final boolean isCallerSystem;
        switch (UserHandle.getAppId(callingUid)) {
            case Process.ROOT_UID:
            case Process.SYSTEM_UID:
            case Process.PHONE_UID:
            case Process.BLUETOOTH_UID:
            case Process.NFC_UID:
                isCallerSystem = true;
                break;
            default:
                isCallerSystem = (callerApp != null) && callerApp.persistent;
                break;
        }
        
		// First line security check before anything else: stop non-system apps from
        // sending protected broadcasts.
        if (!isCallerSystem) {// 不是系统发送的广播
            if (isProtectedBroadcast) {// 非系统进程发送受保护广播抛出异常
                throw new SecurityException(msg);
            } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
                    || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
                // 如果是APPWIDGET的配置和升级的广播
                // Special case for compatibility: we don't want apps to send this,
                // but historically it has not been protected and apps may be using it
                // to poke their own app widget.  So, instead of making it protected,
                // just limit it to the caller.
                if (callerPackage == null) {
                    throw new SecurityException(msg);
                } else if (intent.getComponent() != null) {
                    // They are good enough to send to an explicit component...  verify
                    // it is being sent to the calling app.
                    if (!intent.getComponent().getPackageName().equals(
                            callerPackage)) {
                        throw new SecurityException(msg);
                    }
                } else {
                    // Limit broadcast to their own package.
                    // 限制发送广播给自己包里
                    intent.setPackage(callerPackage);
                }
            }
        }
        
		// 下面主要是针对系统广播的处理
        if (action != null) {
            switch (action) {
                case Intent.ACTION_UID_REMOVED:// 移除uid
                case Intent.ACTION_PACKAGE_REMOVED:// 卸载应用
                case Intent.ACTION_PACKAGE_CHANGED:// 应用更改,比如:停用,启动等
                case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:// 外部应用不可用,比如安装到sd卡的应用,卸载了sd卡
                case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:// 外部应用可用
                case Intent.ACTION_PACKAGES_SUSPENDED:// 暂停应用
                case Intent.ACTION_PACKAGES_UNSUSPENDED:// 应用可用
                    switch (action) {
                        case Intent.ACTION_UID_REMOVED:// 移除系统userId(删除一个用户)
                            ...
                            break;
                        case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:// 外部应用不可用,一般是卸载SD...
                            break;
                        case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:// 外部应用可用,一般是插入SD...
                            break;
                        case Intent.ACTION_PACKAGE_REMOVED:// 卸载
                        case Intent.ACTION_PACKAGE_CHANGED:// 更新
                            ...
                            break;
                        case Intent.ACTION_PACKAGES_SUSPENDED:
                        case Intent.ACTION_PACKAGES_UNSUSPENDED:
                            ...
                            break;
                    }
                    break;
                case Intent.ACTION_PACKAGE_REPLACED: {// 升级应用
                    ...
                    break;
                }
                case Intent.ACTION_PACKAGE_ADDED: {// 安装应用
                    ...
                    break;
                }
                case Intent.ACTION_PACKAGE_DATA_CLEARED: {// 清理应用数据
                    ...
                    break;
                }
                case Intent.ACTION_TIMEZONE_CHANGED:// 时区改变
                    ...
                    break;
                case Intent.ACTION_TIME_CHANGED:// 时间改变
                    ...
                    break;
                case Intent.ACTION_CLEAR_DNS_CACHE:// 清理DNS缓存
                    ...
                    break;
                case Proxy.PROXY_CHANGE_ACTION:// 代理改变
                    ...
                    break;
                case android.hardware.Camera.ACTION_NEW_PICTURE:// 新照片
                case android.hardware.Camera.ACTION_NEW_VIDEO:// 新视频
                    ...
                    break:
                case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
                    ...
                    break;
                case "com.android.launcher.action.INSTALL_SHORTCUT":
                    // As of O, we no longer support this broadcasts, even for pre-O apps.
                    // Apps should now be using ShortcutManager.pinRequestShortcut().
                    return ActivityManager.BROADCAST_SUCCESS;
            }
        }
        ...
}

上面这个部分主要干了三件事:

  • 一些变量值得获取,例如:userIdaction等;
  • 一些特殊情况、异常判断的处理;
  • 对系统广播的判断和处理。

3.2 粘性广播处理

       // Add to the sticky list if requested.
       // 判断是否是粘性广播,如果是,AMS就需要保存这个广播,以便后面注册要接收此类型广播的接收者可以获得这个广播
       if (sticky) {
           // 检查粘性广播是否申请了权限
           if (checkPermission(android.Manifest.permission.BROADCAST_STICKY, callingPid, callingUid)
                   != PackageManager.PERMISSION_GRANTED) {
               throw new SecurityException(msg);
           }
           if (requiredPermissions != null && requiredPermissions.length > 0) {
               return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
           }
           // sticky广播不能指定目标组件
           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(
                                       "Sticky broadcast " + intent + " for user "
                                               + userId + " conflicts with existing global broadcast");
                           }
                       }
                   }
               }
           }

           // 首先检查mStickyBroadcasts是否有该用户的粘性广播列表
           ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
           // 该广播列表中没有该用户的stick广播列表
           if (stickies == null) {
               stickies = new ArrayMap<>();
               mStickyBroadcasts.put(userId, stickies);
           }
           // 获取注册广播的Action对应的粘性广播的Intent列表
           ArrayList<Intent> list = stickies.get(intent.getAction());

           if (list == null) {//如果为空
               list = new ArrayList<>();//创建一个列表
               stickies.put(intent.getAction(), list);//以action为键保存该列表
           }
           // 获取该action对应粘性广播Intent列表的个数
           final int stickiesCount = list.size();
           int i;
           // 检查在粘性广播列表中是否保存了一个与参数Intent一致的广播。如果存在直接替换,否则将参数
           // Intent描述的广播添加到粘性广播列表list中
           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) {// 如果该列表中不存在该粘性广播的Intent加入进去
               list.add(new Intent(intent));   
           }
       }

上面这部分是对粘性广播的处理过程。把粘性广播放在了list列表中,而listaction为键放置在了stickies中,而stickies又以userId为键放在了mStickyBroadcasts中,因此mStickyBroadcasts保存了设备中所有用户粘性广播的Intent相关信息。

3.3 获取静态和动态注册的接收器

		int[] users;
        if (userId == UserHandle.USER_ALL) {
            // Caller wants broadcast to go to all started users.
            // 获取所有已启动用户的列表
            users = mUserController.getStartedUserArrayLocked();
        } else {// 发送广播给指定用户
            // Caller wants broadcast to go to one specific user.
            users = new int[]{userId};
        }

 		// Figure out who all will receive this broadcast.
        List receivers = null;//静态注册接收者
        List<BroadcastFilter> registeredReceivers = null;//动态注册接收者
        
        // Need to resolve the intent to interested receivers...
        // 如果当前的广播Intent没有指定FLAG_RECEIVER_REGISTERED_ONLY标记,也就是允许静态注册   
        if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
            // 允许静态注册的Intent,需要从PMS中去查询对应的广播接收者
            receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
        }
        
        // 如果参数intent没有指定广播接收者的组件名,说明是发送给所有已注册并且要接收该广播的接收者的
        if (intent.getComponent() == null) {
            if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
                // Query one target user at a time, excluding shell-restricted users
                // 查找所有用户的所有动态注册的广播接收者
                for (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 {
                // 查找当前用户的所有动态注册的广播接收者
                registeredReceivers = mReceiverResolver.queryIntent(intent,
                        resolvedType, false /*defaultOnly*/, userId);
            }
        }
  • 上面这段代码主要是获取静态和动态注册的接收器。其中receivers 表示静态注册接收器列表, registeredReceivers 表示动态注册接收器列表;
  • Intent.FLAG_RECEIVER_REGISTERED_ONLYflag表示仅支持动态注册,不支持静态注册,如果在manifest中注册是收不到该广播的;
  • 如果发送广播不加Component信息会遍历获取所有的当前intent的接收者,因此如果定向发给某个应用的话,要把Component信息加上。

3.4 将无序动态注册接收器添加到并发列表中

		//是否支持替换之前待发出的广播
        final boolean replacePending = (intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
        
        // 动态注册的广播接收者个数
        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 don't wait for the
            // components to be launched.
            //如果发送方是系统
            if (isCallerSystem) {
                checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
                        isProtectedBroadcast, registeredReceivers);
            }
            // 根据intent查找对应的广播队列,前台或者后台队列
            final BroadcastQueue queue = broadcastQueueForIntent(intent);
            // 创建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);
            // 在BroadcastQueue中等待发送广播中搜索是否有相同的BroadcastRecord并且是否替换
            final boolean replaced = replacePending && (queue.replaceParallelBroadcastLocked(r) != null);
            // Note: We assume resultTo is null for non-ordered broadcasts.
            if (!replaced) {
                // 如果不需要替换则插入到BroadcastQueue中
                // 也就是说动态广播接收者都放在了BroadcastQueue的mParallelBroadcasts中
                queue.enqueueParallelBroadcastLocked(r);
                //并推动一次广播发送
                queue.scheduleBroadcastsLocked();
            }
            registeredReceivers = null;
            NR = 0;
        }
  • 上面这段代码是,如果发送的是无序广播,且存在动态注册的广播接收者,就将该广播加入并行处理队列,并进行一次广播发送。简单来说就是【动态注册且接受无序广播的广播接收者】是并行操作,广播发送速度会比较快。
  • Intent.FLAG_RECEIVER_REPLACE_PENDINGflag表示是否替换待发出的广播,如果flag为1会进行替换, 位置与待发广播一样。
  • 代码中出现关于队列相关的逻辑后面讲,下面来看下【动态注册接受有序广播的广播接收者】和【静态注册的广播接收者】的广播发送。

3.5 合并有序动态注册和静态注册接收器

		 // Merge into one list.
        int ir = 0;
        if (receivers != null) {
            // 对于ACTION_PACKAGE_ADDED广播而言,如果是自己被add了,那么这个广播只能别人收到,
            // 自己即使注册了这个静态广播也接收不到,注释上说是担心有些应用一安装就接收自己的PACKAGE_ADDED
            // 广播,然后就启动了。简言之,应用永远接收不到自己的PACKAGE_ADDED广播。
            // A special case for PACKAGE_ADDED: do not allow the package
            // being added to see this broadcast.  This prevents them from
            // using this as a back door to get run as soon as they are
            // installed.  Maybe in the future we want to have a special install
            // broadcast or such for apps, but we'd like to deliberately make
            // this decision.
            String skipPackages[] = null;// 需要跳过的广播
            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--;
                            }
                        }
                    }
                }
            }

            // 开始把动态注册的广播接收者列表registeredReceivers和静态注册的广播接收者列表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);//获取静态广播接收者列表第it个条目
                }
                if (curr == null) {
                    curr = registeredReceivers.get(ir);//获取动态广播接收者列表第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;
                }
            }
        }
        // 将两个列表合并到一个列表中,这样静态注册广播接收者receivers列表就包含了所有的广播接收者(无序广播和有序广播)。
        while (ir < NR) {
            if (receivers == null) {
                receivers = new ArrayList();
            }
            receivers.add(registeredReceivers.get(ir));
            ir++;
        }

        if (isCallerSystem) {
            checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
                    isProtectedBroadcast, receivers);
        }
  • 上面这段代码主要是对【动态注册接受有序广播的广播接收者】和【静态注册的广播接收者】进行合并,如果是有序广播,动态接收者和静态的接收者合并到一个队列里面进行处理,也就是说order广播下,所有的接收者(静态和动态)处理方式都是一样的,都是串行处理;
  • 对于静态注册的接收者而言,始终是和order广播的处理方式是一样的,也就是说静态的接收者只有order模式(串行化接收);
  • 在合并过程中,如果一个动态注册的广播接收者和一个静态注册的目标广播接收者的优先级相同,那么动态注册的目标接收者会排在静态注册的目标广播接收者前面,即动态注册的目标广播接收者会优先于静态注册的广播接收者接收到有序广播。

3.6 合并接收器添加到有序处理列表中

 		// 可以看出,是在合并入receiver后统一发送BroadcastQueue.scheduleBroadcastsLocked
        if ((receivers != null && receivers.size() > 0) || resultTo != null) {
            // 根据intent查找对应的广播队列
            final 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);

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


            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 {
                //无序广播+有序广播合并之后放在了BroadcastQueue的mOrderedBroadcasts中
                queue.enqueueOrderedBroadcastLocked(r);
                //并推动一次广播发送
                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);
            }
        }

上面这段代码是将合并后的接收添加到有序队列中进行处理。

系统广播处理broadcastIntentLocked函数这部分代码逻辑总结:

  • 首先是判断是不是系统广播,也就是switch语句中的部分,这部分的广播是系统发出的,根据不同广播做出不同的处理,系统广播我们可以接收但是不能发送,只能由系统发出;
  • 接着是粘性广播的处理;
  • 然后是【动态注册且接受无序广播的广播接收者】的处理,把他们放入到BroadcastQueue的mParallelBroadcasts中,并调用scheduleBroadcastsLocked,BroadcastQueue对mParallelBroadcasts列表中条目的最终处理是通过并行操作来完成的;
  • 最后是【动态注册接受有序广播的广播接收者】和【静态注册的广播接收者】的处理,把他们放入到BroadcastQueue的mOrderedBroadcasts中,并调用scheduleBroadcastsLocked,BroadcastQueue对mOrderedBroadcasts列表中条目的最终处理是通过串行操作来完成的;

4、BroadcastQueue广播处理

4.1 BroadcastQueue队列定义及入队列方法判断

frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

/**
 * Lists of all active broadcasts that are to be executed immediately
 * (without waiting for another broadcast to finish).  Currently this only
 * contains broadcasts to registered receivers, to avoid spinning up
 * a bunch of processes to execute IntentReceiver components.  Background-
 * and foreground-priority broadcasts are queued separately.
 */
final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();

/**
 * List of all active broadcasts that are to be executed one at a time.
 * The object at the top of the list is the currently activity broadcasts;
 * those after it are waiting for the top to finish.  As with parallel
 * broadcasts, separate background- and foreground-priority queues are
 * maintained.
 */
final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();

在广播队列中定义了两个处理列表,一个是并发处理列表mParallelBroadcasts,一个是有序处理列表mOrderedBroadcasts

BroadcastQueue broadcastQueueForIntent(Intent intent) {
     final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
     if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST,
             "Broadcast intent " + intent + " on "
             + (isFg ? "foreground" : "background") + " queue");
   	return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
}

public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
    mParallelBroadcasts.add(r);
    r.enqueueClockTime = System.currentTimeMillis();
}

public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
    mOrderedBroadcasts.add(r);
    r.enqueueClockTime = System.currentTimeMillis();
} 

上面的三个方法是上面源码中出现的,broadcastQueueForIntent根据intent是否含有Intent.FLAG_RECEIVER_FOREGROUND来判断是前台广播还是普通后台广播,如果是前台广播返回前台广播处队列。如果是普通后台广播返回后台广播处理队列

获取队列后,将发给【动态注册且接受无序广播的广播接收者】的广播通过enqueueParallelBroadcastLocked函数添加到该队列的并发处理列表中。将发给【动态注册接受有序广播的广播接收者】和【静态注册的广播接收者】的广播通过enqueueOrderedBroadcastLocked函数添加到该队列的有序处理列表中。

4.2 BroadcastQueue队列中广播发送流程

从上面的源码分析中看到最后我们都执行了scheduleBroadcastsLocked对广播进行了进一步发送处理,接下来看下这个函数的逻辑。

// 执行广播发送,所有广播都会从这里走,然后会到processNextBroadcast
public void scheduleBroadcastsLocked() {
    // 来调度保存在无序广播调度队列mParallelBroadcasts和有序广播调度队列mOrderedBroadcasts中的广播转发任务的
    if (mBroadcastsScheduled) {
        return;
    }
    // 虽然这里只发送了发送广播的消息,但是这一步执行完之后就已经标记广播发送了,因此可以看出广播发送和处理
    // 是异步的,即广播发送者将一个广播发送给AMS后,不会等待AMS将这个广播转发给广播接收者处理
    mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
    mBroadcastsScheduled = true;
}

//BroadcastQueue中的消息调度BroadcastHandler 
private final class BroadcastHandler extends Handler {
    public BroadcastHandler(Looper looper) {
        super(looper, null, true);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case BROADCAST_INTENT_MSG: {
                processNextBroadcast(true);//开始处理下一个广播
            }
            break;
            case BROADCAST_TIMEOUT_MSG: {
                synchronized (mService) {
                    broadcastTimeoutLocked(true);
                }
            }
            break;
        }
    }
}
  • 先判断mBroadcastsScheduled的值,如果true说明消息队列已经存在一个类型为BROADCAST_INTENT_MSG的消息了,直接返回,因为该消息在执行完毕会自动调用下一条消息;
  • 接下来广播的处理逻辑会走到 processNextBroadcast函数中,下面来看下该函数的逻辑。

4.2.1 BroadcastQueue并发列表广播发送流程

//参数fromMsg是用来描述processNextBroadcast是否是被handleMessage触发的
final void processNextBroadcast(boolean fromMsg) {
    synchronized (mService) {
        processNextBroadcastLocked(fromMsg, false);
    }
}
// 广播发送的核心内容
final void processNextBroadcastLocked(boolean fromMsg) {
    BroadcastRecord r;
    mService.updateCpuStats();
	// 否是来自handleMessage的BROADCAST_INTENT_MSG类型消息
    if (fromMsg) {
        // 前面说到,如果消息队列里面有BROADCAST_INTENT_MSG消息,该标记为true,阻止新的消息加入队列,
        // 这里开始处理这个消息的时候,将mBroadcastsScheduled变量设置为false,开始允许新的消息加入。
        mBroadcastsScheduled = false;
    }
    // First, deliver any non-serialized broadcasts right away.
    // 无序广播之间不存在相互等待,这里处理的是保存在无序广播调度队列mParallelBroadcasts中的广播发送任务,
    // 即把保存在无序广播调度队列mParallelBroadcasts中的广播发送给它的目标广播接收者处理
    while (mParallelBroadcasts.size() > 0) {
        // 首先获取保存无序广播调度队列mParallelBroadcasts中的每一个BroadcastRecord对象
        r = mParallelBroadcasts.remove(0);
        r.dispatchTime = SystemClock.uptimeMillis();
        r.dispatchClockTime = System.currentTimeMillis();
        //获取BroadcastRecord中的所有广播接收者的数量
        final int N = r.receivers.size();
        for (int i = 0; i < N; i++) {
            Object target = r.receivers.get(i);     
            // 通过deliverToRegisteredReceiverLocked调用ActivityThread.scheduleRegisteredReceiver处理广播
            deliverToRegisteredReceiverLocked(r, (BroadcastFilter) target, false, i);
        }
        addBroadcastToHistoryLocked(r);
    } 
    ...
}

上面这段代码逻辑:对并行处理列表中的广播调用deliverToRegisteredReceiverLocked将每一个无序广播发送给每一个广播接收者,异步处理广播。注意入参ordered值为false

//执行动态注册的广播接收者的发送接受过程
private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
                                               BroadcastFilter filter, boolean ordered, int index) {
    ...
    // order广播,所有的接收者需要依次以一种同步的方式发送广播,
    // 可以看到order广播在BroadcastRecord保存了几个状态
    if (ordered) {
        // IBinder类型,代表当前的接收者
        r.receiver = filter.receiverList.receiver.asBinder();
        // 当前正在处理的BroadcastFilter,和上面的receiver是对应好的
        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);
        }
    }
    try {
        if (filter.receiverList.app != null && filter.receiverList.app.inFullBackup) {
            // Skip delivery if full backup in progress
            // If it's an ordered broadcast, we need to continue to the next receiver.
            // 如果正在备份或者恢复备份跳过,
            // 如果是一个有序广播,则执行下一个广播
            if (ordered) {
                skipReceiverLocked(r);
            }
        } else {
            // 如果不需要进行权限检查或者通过权限检查,调用performReceiveLocked发送广播
            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;
        }
    } catch (RemoteException e) {
         ...
    }
}

在后面【有序广播+动态注册】的处理也会走到这个函数deliverToRegisteredReceiverLocked,因此从这个函数的入参开始加ordered来进行区分,上面这段代码最终会走到performReceiveLocked方法中,继续往下看。

void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
                          Intent intent, int resultCode, String data, Bundle extras,
                          boolean ordered, boolean sticky, int sendingUser) throws RemoteException {
    // Send the intent to the receiver asynchronously using one-way binder calls.
    if (app != null) {
        if (app.thread != null) {// 因为是动态注册广播,所以不为空
            // If we have an app thread, do the call through that so it is
            // correctly ordered with other one-way calls.
            try {
                // 这里scheduleRegisteredReceiver函数是一个Binder调用,注释上面说的很清楚,
                // 调用ApplicationThread对象的Binder代理对象的函数来向它发送广播
                app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
                        data, extras, ordered, sticky, sendingUser, app.repProcState);
            } catch (RemoteException ex) {
                // Failed to call into the process. It's either dying or wedged. Kill it gently.
                synchronized (mService) {
                    app.scheduleCrash("can't deliver broadcast");
                }
                throw ex;
            }
        } else {
            // Application has died. Receiver doesn't exist.
            throw new RemoteException("app.thread must not be null");
        }
    } else {
        // 直接调用与它关联的一个InnerReceiver对象的Binder代理对象的成员函数performReceive来向它发送广播
        receiver.performReceive(intent, resultCode, data, extras, ordered,
                sticky, sendingUser);
    }
}
  • app不为空,表示进程已经启动,调用ActivityThread.scheduleRegisteredReceiver发送当前广播;
  • 否则直接调用receiver.performReceive方法发送广播。因为当前分析的是动态注册的广播接收器,所以继续看scheduleRegisteredReceiver这个方法。

调用ActivityThread的scheduleRegisteredReceiver方法

/frameworks/base/core/java/android/app/ActivityThread.java

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

从上面的代码里看,最后走到了InnerReceiver中的performReceive方法中,继续往下看。

调用LoadedApk#ReceiverDispatcher#InnerReceiver的performReceive方法

/frameworks/base/core/java/android/app/LoadedApk.java#ReceiverDispatcher#InnerReceiver

final static class InnerReceiver extends IIntentReceiver.Stub {
    final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
    final LoadedApk.ReceiverDispatcher mStrongRef;

    InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
        mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
        mStrongRef = strong ? rd : null;
    }

    @Override
    public void performReceive(Intent intent, int resultCode, String data,
                               Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
        final LoadedApk.ReceiverDispatcher rd;
        if (intent == null) {
            rd = null;
        } else {
        	//获取rd的实例对象
            rd = mDispatcher.get();
        }
		//判断是否为空
        if (rd != null) {
        	//存在广播,rd 不为空,调用LoadedApk.ReceiverDispatcher对象的performReceive方法
            rd.performReceive(intent, resultCode, data, extras,
                    ordered, sticky, sendingUser);
        } else {
            IActivityManager mgr = ActivityManager.getService();
            try {
                if (extras != null) {
                    extras.setAllowFds(false);
                }
            	//不存在广播,rd为空,调用activityManagerService对象的finishReceiver结束广播的发送接受过程
                mgr.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }
}

LoadedApk.ReceiverDispatcher对象封装了广播接收者,如果该广播接收者注册了,那么该对象就会存在,这里就会调用LodedApk.ReceiverDispatcher.performReceive,否则就会调用AMS.finishReceiver方法,这里我们先看存在广播的情况。

调用LodedApk#ReceiverDispatcher的performReceive方法

/frameworks/base/core/java/android/app/LoadedApk.java#ReceiverDispatcher

public void performReceive(Intent intent, int resultCode, String data,
                                   Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
    final Args args = new Args(intent, resultCode, data, extras, ordered, sticky, sendingUser);
    // 将args对象post到主线程的消息队列里面,作为一个Runnable调度而不是在handleMessage中处理,
    // 因此这里不久后会调用Args.run函数
    if (intent == null || !mActivityThread.post(args.getRunnable())) {
        if (mRegistered && ordered) {
            IActivityManager mgr = ActivityManager.getService();
            args.sendFinished(mgr);
        }
    }
}

  • 首先将参数Intent所描述的一个广播封装成一个Args对象,然后将这个Args对象封装成一个消息对象,然后将这个消息对象发送到应用程序主线程的消息队列中;

  • 该对象实现了Runnable接口,在if句中调用post方法,最终会调用Args中的run方法。

  • 如果是【有序广播+动态注册】会走到sendFinished函数,通知AMS,它前面转发过来的有序广播已经处理完了,这时AMS就可以继续将这个有序广播转发给下一个目标广播接收者了。这也是实现串行的原因,当前的发出了,才会通知进行下一个进行。而并发的是直接循环post然后完事了。

调用LodedApk#ReceiverDispatcher#Args的getRunnable方法

final class Args extends BroadcastReceiver.PendingResult {
     ...
     public final Runnable getRunnable() {
         return () -> {
             // mReceiver指向一个广播接收者
             final BroadcastReceiver receiver = mReceiver;
             final boolean ordered = mOrdered;
             final IActivityManager mgr = ActivityManager.getService();
             final Intent intent = mCurIntent;
             mCurIntent = null;
             mDispatched = true;
             mPreviousRunStacktrace = new Throwable("Previous stacktrace");
             if (receiver == null || intent == null || mForgotten) {
                 if (mRegistered && ordered) {
                     sendFinished(mgr);
                 }
                 return;
             }
             try {
                 // 这里处理的是动态广播接收者,默认认为接收者BroadcastReceiver已经存在
                 ClassLoader cl = mReceiver.getClass().getClassLoader();
                 intent.setExtrasClassLoader(cl);
                 intent.prepareToEnterProcess();
                 setExtrasClassLoader(cl);
                 receiver.setPendingResult(this);
                 // 接收广播
                 receiver.onReceive(mContext, intent);                 
             } catch (Exception e) {
                 // 检查当前广播是否是有序广播,并且广播接收者是否已经注册到AMS中
                 if (mRegistered && ordered) {
                     // 通知AMS,它前面转发过来的有序广播已经处理完了,这时AMS就可以继续将这个有序广播
                     // 转发给下一个目标广播接收者了
                     sendFinished(mgr);
                 }
                 if (mInstrumentation == null || !mInstrumentation.onException(mReceiver, e)) {
                     throw new RuntimeException("Error receiving broadcast " + intent + " in " + mReceiver, e);
                 }
             }
             // 然后调用BroadcastReceiver.PendingResult.finish函数,也就是下面的finish函数
             if (receiver.getPendingResult() != null) {
                 finish();
             }
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         };
     }
 }

在run方法中调用BroadcastReceiver.onReceive方法,这样就会执行完一次无序广播的发送过程。从这里也能看出为什么并发队列中的处理是并行的,因为最后是新建了一个Runnable来处里,postRunnable后就可以执行下一个了,不会堵塞。

4.2.2 BroadcastQueue有序列表广播发送流程

4.2.1 讲了BroadcastQueue并发列表广播发送流程,我们把逻辑在移回去,看下有序广播发送流程。 processNextBroadcastLocked这个函数逻辑有点长,拆开来看下。

//参数fromMsg是用来描述processNextBroadcast是否是被handleMessage触发的
final void processNextBroadcast(boolean fromMsg) {
    synchronized (mService) {
        processNextBroadcastLocked(fromMsg, false);
    }
}
// 广播发送的核心内容
final void processNextBroadcastLocked(boolean fromMsg) {
    BroadcastRecord r;
    mService.updateCpuStats();
	// 否是来自handleMessage的BROADCAST_INTENT_MSG类型消息
    if (fromMsg) {
        mBroadcastsScheduled = false;
    }
	// 无序广播的处理流程
    ...
    //开始有序广播的处理流程
    // Now take care of the next serialized one...
    // If we are waiting for a process to come up to handle the next
    // broadcast, then do nothing at this point.  Just in case, we
    // check that the process we're waiting for still exists.   
    // mPendingBroadcast对象是用来描述一个正在等待静态注册的目标广播接收者启动起来的广播转发任务
    if (mPendingBroadcast != null) {
        // 记录目标广播接收者所在进程是否是死亡状态
        boolean isDead;
        if (mPendingBroadcast.curApp.pid > 0) {
            synchronized (mService.mPidsSelfLocked) {
                ProcessRecord proc = mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.pid);
                isDead = proc == null || proc.crashing;
            }
        } else {
            final ProcessRecord proc = mService.mProcessNames.get(
                    mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
            isDead = proc == null || !proc.pendingStart;
        }
        // 如果应用已经启动,会调用AMS的函数来处理静态广播,这里直接return
        if (!isDead) {
            // It's still alive, so keep waiting
            return;
        } else {
            mPendingBroadcast.state = BroadcastRecord.IDLE;
            mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
            mPendingBroadcast = null;
        }
    }
}

上面这段代码主要是处理静态注册广播,且注册进程没有启动的广播处理逻辑。mPendingBroadcast对象是用来描述一个正在等待静态注册的目标广播接收者启动起来的广播转发任务。

从前面可知,有序广播调度队列mOrderedBroadcast列表中的目标广播接收者有可能是静态注册的,而这些静态注册的目标广播接收者可能还没有启动起来,因此AMS将一个广播发送给它们处理时,首先将它们启动起来。

如果进程已经起来,会调用AMS的函数来处理静态广播,这里直接return。

	boolean looped = false;
    // 这里的do-while只会从mOrderedBroadcasts中取出第一个BroadcastRecord进行后续的处理!
    do {
        // 判断有序广播调度队列mOrderedBroadcasts是否还有需要处理的广播
        // 如果长度为0,则说明调度队列mOrderedBroadcasts中的广播已经全部处理完成
        if (mOrderedBroadcasts.size() == 0) {
            // No more broadcasts pending, so all done!
            mService.scheduleAppGcsLocked();
            if (looped) {
                // If we had finished the last ordered broadcast, then
                // make sure all processes have correct oom and sched
                // adjustments.
                mService.updateOomAdjLocked();
            }
            return;
        }
        // 如果没有处理完成,则继续取出mOrderedBroadcasts中的第一个BroadcastRecord
        r = mOrderedBroadcasts.get(0);
        boolean forceReceive = false;
        // Ensure that even if something goes awry with the timeout
        // detection, we catch "hung" broadcasts here, discard them,
        // and continue to make progress.
        //
        // This is only done if the system is ready so that PRE_BOOT_COMPLETED
        // receivers don't get executed with timeouts. They're intended for
        // one time heavy lifting after system upgrades and can take
        // significant amounts of time.
        // 获取广播转发任务的目标广播接收者的个数
        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); // forcibly finish this broadcast
                // 重置参数,继续处理有序广播调度队列mOrderedBroadcasts的下一个广播转发任务
                forceReceive = true;
                r.state = BroadcastRecord.IDLE;
            }
        }

        if (r.state != BroadcastRecord.IDLE) {
            return;
        }

        // 检测广播转发任务是否正在处理中,即AMS正在将一个有序广播转发给它的前一个目标广播接收处理者,
        // 如果是,AMS就会等待这个目标广播接收者处理完该有序广播,然后再转发给下一个广播接收者处理
        if (r.receivers == null || r.nextReceiver >= numReceivers
                || r.resultAbort || forceReceive) {
            // No more receivers for this broadcast!  Send the final
            // result if requested...
            if (r.resultTo != null) {
                try {
                    performReceiveLocked(r.callerApp, r.resultTo,
                            new Intent(r.intent), r.resultCode,
                            r.resultData, r.resultExtras, false, false, r.userId);
                    // Set this to null so that the reference
                    // (local and remote) isn't kept in the mBroadcastHistory.
                    r.resultTo = null;
                } catch (RemoteException e) {
                    r.resultTo = null;
                }
            }
           // 调用函数cancelBroadcastTimeoutLocked来删除前面发送到AMS所运行在的线程的
           // 消息队列中的一个BROADCAST_TIMEOUT_MSG消息
            cancelBroadcastTimeoutLocked();
            // ... and on to the next...
            addBroadcastToHistoryLocked(r);
            if (r.intent.getComponent() == null && r.intent.getPackage() == null
                    && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
                // This was an implicit broadcast... let's record it for posterity.
                mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
                        r.manifestCount, r.manifestSkipCount, r.finishTime - r.dispatchTime);
            }
            // BroadcastRecord处理完移除
            mOrderedBroadcasts.remove(0);
            r = null;
            looped = true;
            continue;
        }
    } while (r == null);// 如果第一次取出的r不为空,则退出循环

上面这段代码是在处理广播前对一些特殊情况进行处理,例如,处理列表为空,直接返回;当前广播正在处理中返回;下一个接收者为空或者当前接受者设置了resultAbort 等异常情况处理。

4.2.2.1 动态接收器的处理

	// Get the next receiver...
    //获取下一个将要处理的广播接收者在其列表中的位置
    int recIdx = r.nextReceiver++;

    // Keep track of when this receiver started, and make sure there
    // is a timeout message pending to kill it if need be.
    // 保存当前时间
    r.receiverTime = SystemClock.uptimeMillis();
    if (recIdx == 0) {// 表示第一个开始处理的接收者,也就是BroadcastRecord对象r所描述的广播任务刚被处理
        // 接收者开始处理的时间戳,也就是这个接收者开始处理了,要记录开始时间来计算是否超过超时时间
        // 也就是说这是BroadcastRecord中第一个接收者开始被处理的时间戳,也就是上面BroadcastRecord
        // 超时的起点,可以看到上面超时比较的时候用的就是r.dispatchTime
        r.dispatchTime = r.receiverTime;
        r.dispatchClockTime = System.currentTimeMillis();
    }
    if (!mPendingBroadcastTimeoutMessage) {
        long timeoutTime = r.receiverTime + mTimeoutPeriod;
        // 设置超时,传入参数是r.receiverTime + mTimeoutPeriod,也就是开始时间加上超时时间
        // mTimeoutPeriod,mTimeoutPeriod初始化是在BroadcastQueue初始化的时候传入的,
        // 也就是在AMS(AMS构造函数中)中初始化mFgBroadcastQueue和mBgBroadcastQueue时传入的
        // BROADCAST_FG_TIMEOUT = 10 * 1000和BROADCAST_BG_TIMEOUT = 60 * 1000,
        // 这里开始埋了ANR的雷
        setBroadcastTimeoutLocked(timeoutTime);
    }

    final BroadcastOptions brOptions = r.options;
    // 得到下一个广播接收者
    final Object nextReceiver = r.receivers.get(recIdx);
    // 如果当前nextReceiver是一个BroadcastFilter类型,说明是一个动态注册接收者,不需要启动一个进程
    if (nextReceiver instanceof BroadcastFilter) {
        // Simple case: this is a registered receiver who gets
        // a direct call.
        BroadcastFilter filter = (BroadcastFilter) nextReceiver;
        // 调用deliverToRegisteredReceiverLocked向所有的receivers发送广播
        // 将它所描述的每一个无序广播发送给每一个广播接收者,异步处理广播
        // 通过deliverToRegisteredReceiverLocked调用ActivityThread.scheduleRegisteredReceiver处理广播
        deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
        // 检查BroadcastRecord对象r所描述的广播转发任务是否用来转发无序广播的。
        if (r.receiver == null || !r.ordered) {
            // 设置IDLE状态,表示AMS不需要等待它的前一个目标广播接收者处理完成一个广播就可以将该广播
            // 继续发送给它的下一个目标广播接收者处理
            r.state = BroadcastRecord.IDLE;
            // 调用下面函数就是为了将一个广播继续发送给BroadcastRecord对象r所描述的广播转发任务的
            // 下一个目标广播接收者处理的
            scheduleBroadcastsLocked();
        } else {
            if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) {
                scheduleTempWhitelistLocked(filter.owningUid,
                        brOptions.getTemporaryAppWhitelistDuration(), r);
            }
        }
        return;
    }

上面代码开始对广播进行处理,先进行了计时操作,然后根据接收者不同,先对动态注册接收器进行处理(BroadcastFilter类型)。逻辑最后通过deliverToRegisteredReceiverLocked调用ActivityThread.scheduleRegisteredReceiver处理广播。上面有分析这个函数,此时入参orderedtrue,用来执行动态注册的广播接收者的发送接收过程。

4.2.2.2 静态接收器的处理

接下来看下静态注册器的处理:

    // Hard case: need to instantiate the receiver, possibly
    // starting its application process to host it.
    // 如果上面if没有进行拦截,说明不是广播接收者动态注册的,而应该是静态注册的
    // 此时进程可能没有启动
    ResolveInfo info = (ResolveInfo) nextReceiver;
    ComponentName component = new ComponentName(
            info.activityInfo.applicationInfo.packageName,
            info.activityInfo.name);

    // 是否跳过该广播接收者不处理
    ...
    // This is safe to do even if we are skipping the broadcast, and we need
    // this information now to evaluate whether it is going to be allowed to run.
    final int receiverUid = info.activityInfo.applicationInfo.uid;
    // If it's a singleton, it needs to be the same app or a special app
    if (r.callingUid != Process.SYSTEM_UID && isSingleton
            && mService.isValidSingletonCall(r.callingUid, receiverUid)) {
        info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0);
    }
    // 得到ResolveInfo对象info所描述的广播接收者的android:process属性值,
    // 即它需要运行在的应用程序进程的名称,并且保存在变量targetProcess中
    String targetProcess = info.activityInfo.processName;
    // 获取当前广播接收者的进程记录,也就是该静态广播接收者是否已经运行
    ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
            info.activityInfo.applicationInfo.uid, false);
	...
    // Is this receiver's application already running?
    // 如果当前进程已经运行,则直接发给该进程,然后返回
    if (app != null && app.thread != null && !app.killed) {
        try {
            app.addPackage(info.activityInfo.packageName,
                    info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);
            // app进程存在,通过processCurBroadcastLocked -> ActivityThread.scheduleReceiver -> receiver.onReceive处理当前广播 
            processCurBroadcastLocked(r, app, skipOomAdj);
            // order广播是一种同步处理方式,因此处理完可以直接return
            return;
        } catch (RemoteException e) {
			...
        } catch (RuntimeException e) {
            // If some unexpected exception happened, just skip
            // this broadcast.  At this point we are not in the call
            // from a client, so throwing an exception out from here
            // will crash the entire system instead of just whoever
            // sent the broadcast.
            logBroadcastReceiverDiscardLocked(r);
            finishReceiverLocked(r, r.resultCode, r.resultData,
                    r.resultExtras, r.resultAbort, false);
            scheduleBroadcastsLocked();
            // We need to reset the state if we failed to start the receiver.
            r.state = BroadcastRecord.IDLE;
            return;
        }
        // If a dead object exception was thrown -- fall through to
        // restart the application.
    }

 	// 如果app进程不存在,会先创建该进程
    if ((r.curApp = mService.startProcessLocked(targetProcess,
            info.activityInfo.applicationInfo, true,
            r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
            "broadcast", r.curComponent,
            (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
            == null) {// 如果启动失败
        logBroadcastReceiverDiscardLocked(r);
        // 结束广播发送任务
        finishReceiverLocked(r, r.resultCode, r.resultData,
                r.resultExtras, r.resultAbort, false);
        scheduleBroadcastsLocked();
        r.state = BroadcastRecord.IDLE;
        return;
    }
    // 将BroadcastRecord赋值为mPendingBroadcast,等待应用启动完成后处理
    // 正在启动接收者进程,将正在启动的BroadcastRecord记录存储到mPendingBroadcast中,同时将当前正在
    // 启动的接收者进程在所有接收者中的索引存储到mPendingBroadcastRecvIndex,如果当前广播接收者处理
    // 完,需要继续从mPendingBroadcastRecvIndex计算到下一个接收者发送当前广播
    mPendingBroadcast = r;
    mPendingBroadcastRecvIndex = recIdx;

上面代码是对静态广播的处理,静态广播又分为两种,静态接收者所在进程是启动状态,静态接收者所在进程是未启动状态。根据所在进程的启动状态分别来进行处理。

app进程存在,通过processCurBroadcastLocked -> ActivityThread.scheduleReceiver -> receiver.onReceive处理当前广播。

app进程不存在,会先创建该进程。

APP存在,调用BroadcastQueue的processCurBroadcastLocked方法处理有序广播

private final void processCurBroadcastLocked(BroadcastRecord r,
                                             ProcessRecord app, boolean skipOomAdj) throws RemoteException {
    if (app.thread == null) {
        throw new RemoteException();
    }
    if (app.inFullBackup) {
        skipReceiverLocked(r);
        return;
    }
    // 将进程的相关信息写入当前BroadcastRecord中相关的接收者
    r.receiver = app.thread.asBinder();
    r.curApp = app;
    app.curReceivers.add(r);
    app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
    mService.updateLruProcessLocked(app, false, null);
    if (!skipOomAdj) {
        mService.updateOomAdjLocked();
    }

    // Tell the application to launch this receiver.
    r.intent.setComponent(r.curComponent);

    boolean started = false;
    try {
        mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
                PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
        // 处理广播,等待接收进程的返回
        app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
                mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo),
                r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
                app.repProcState);
        started = true;
    } finally {
        if (!started) {
            r.receiver = null;
            r.curApp = null;
            app.curReceivers.remove(r);
        }
    }
}

APP存在,调用ApplicationThread的scheduleReceiver方法

// 处理应用进程中接收到的静态广播消息,实际处理该广播的是ActivityThread.handleReceiver函数
public final void scheduleReceiver(Intent intent, ActivityInfo info,
        CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
        boolean sync, int sendingUser, int processState) {
    updateProcessState(processState, false);
    ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
            sync, false, mAppThread.asBinder(), sendingUser);
    r.info = info;
    r.compatInfo = compatInfo;
    sendMessage(H.RECEIVER, r);
}

class H extends Handler {
	...
     public void handleMessage(Message msg) {
             switch (msg.what) {
             		...
            case RECEIVER:
            	//该消息最终会触发handleReceiver方法
                handleReceiver((ReceiverData)msg.obj);
                break;    
              ...   		
      }
}

APP存在,调用ApplicationThread的handleReceiver方法

    private void handleReceiver(ReceiverData data) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        // 1) 创建BroadcastReceiver对象
        // 这里处理的是静态广播接收者,默认认为接收者BroadcastReceiver对象不存在
        // 每次接受都会创建一个新的BroadcastReceiver对象
        String component = data.intent.getComponent().getClassName();

        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);

        IActivityManager mgr = ActivityManager.getService();

        Application app;
        BroadcastReceiver receiver;
        ContextImpl context;
        try {
            // 首先从AMS传递的intent中获取当前处理该广播的组件名称,然后通过反射创建一个BroadcastReceiver
            // 对象,从这里可以看出来,静态广播处理的时候,每次都会创建一个新的BroadcastReceiver对象;

            // 创建Application对象,如果进程已经启动,Application对象已经创建
            app = packageInfo.makeApplication(false, mInstrumentation);
            context = (ContextImpl) app.getBaseContext();
            if (data.info.splitName != null) {
                context = (ContextImpl) context.createContextForSplit(data.info.splitName);
            }
            java.lang.ClassLoader cl = context.getClassLoader();
            data.intent.setExtrasClassLoader(cl);
            data.intent.prepareToEnterProcess();
            data.setExtrasClassLoader(cl);
            receiver = packageInfo.getAppFactory()
                    .instantiateReceiver(cl, data.info.name, data.intent);
        } catch (Exception e) {
            data.sendFinished(mgr);
            throw new RuntimeException(
                "Unable to instantiate receiver " + component
                + ": " + e.toString(), e);
        }

        // 2) 执行onReceive函数
        try {
            sCurrentBroadcastIntent.set(data.intent);
            // 调用接收者的onReceive方法,这里还调用了setPendingResult方法,详细内容请看BroadcastReceiver.goAsync方法。
            receiver.setPendingResult(data);
            receiver.onReceive(context.getReceiverRestrictedContext(),
                    data.intent);
        } catch (Exception e) {
            data.sendFinished(mgr);
            if (!mInstrumentation.onException(receiver, e)) {
                throw new RuntimeException(
                    "Unable to start receiver " + component
                    + ": " + e.toString(), e);
            }
        } finally {
            sCurrentBroadcastIntent.set(null);
        }
        // 3) 向AMS发送处理结束消息
        if (receiver.getPendingResult() != null) {
            data.finish();
        }
    }

上面代码主要干了三件事情:

  • 创建BroadcastReceiver对象;
  • 执行onReceive函数;
  • 向AMS发送处理结束消息,通知将当前广播发送给下一个接收者;

APP未启动,先创建该进程
如果静态广播接收者进程尚未启动,会直接调用AMS的startProcessLocked函数启动该接收者进程,并将当前正在等待进程
启动的BroadcastRecord存储到mPendingBroadcast里面,这个就是静态广播拉起应用的原理。

在app进程启动之后,会先调用application的attachonCreate方法,然后才会调用ActivityManagerService的sendPendingBroadcastsLocked方法。

boolean sendPendingBroadcastsLocked(ProcessRecord app) {
    boolean didSomething = false;
    for (BroadcastQueue queue : mBroadcastQueues) {
    	//调用BroadcastQueue的sendPendingBroadcastsLocked方法
        didSomething |= queue.sendPendingBroadcastsLocked(app);
    }
    return didSomething;
}

mBroadcastQueues是包含前台和后台广播队列,这里分别调用前台和后台优先级广播的BroadcastQueue.sendPendingBroadcastsLocked方法。

调用BroadcastQueue的sendPendingBroadcastsLocked方法

    // 未启动进程的广播接收者需要先启动进程,最后到达这个函数
    public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
        boolean didSomething = false;
        // 前面分析mPendingBroadcast用于存储当前正在等待进程启动的BroadcastRecord
        final BroadcastRecord br = mPendingBroadcast;
        if (br != null && br.curApp.pid > 0 && br.curApp.pid == app.pid) {
            if (br.curApp != app) {
                Slog.e(TAG, "App mismatch when sending pending broadcast to "
                        + app.processName + ", intended target is " + br.curApp.processName);
                return false;
            }
            try {
                // 启动完成设置为null
                mPendingBroadcast = null;
                // 调用processCurBroadcastLocked方法进行处理
                processCurBroadcastLocked(br, app, false);
                didSomething = true;
            } catch (Exception e) {
                Slog.w(TAG, "Exception in new application when starting receiver "
                        + br.curComponent.flattenToShortString(), e);
                logBroadcastReceiverDiscardLocked(br);
                finishReceiverLocked(br, br.resultCode, br.resultData,
                        br.resultExtras, br.resultAbort, false);
                scheduleBroadcastsLocked();
                // We need to reset the state if we failed to start the receiver.
                br.state = BroadcastRecord.IDLE;
                throw new RuntimeException(e.getMessage());
            }
        }
        return didSomething;
    }

这里是找到等待处理的广播并且判断是否为空,以及是否和当前进程的pid相同,也就是不是这个进程的等待广播,如果是就调用processCurBroadcastLocked方法进行处理,后面的处理和上面app已启动的流程一致了。

5、流程总结

上面流程大体流程图:
请添加图片描述
执行时序图:

在这里插入图片描述

三、总结

看完源码再来看下一开始的问题:
1、广播为啥会阻塞呢?发送给接收器就行了,为啥还要等着接收器处理完才处理下一个?

从上面的源码分析可知,广播的处理分为并行和有序两个队列,出问题的无序广播静态接收器放在了有序处理列表中,而有序处理列表的执行是串行的,只有前面的执行完,才会轮到下一个处理,所以前面的广播如果在onReceive中有耗时操作,后面的广播就会堵塞。

2、 由普通的后台广播改为前台广播后,为啥处理的会更快?

在上面源码中有个变量的注释:mTimeoutPeriod。这个变量初始化是在BroadcastQueue初始化的时候传入的,也就是在AMS(AMS构造函数中)中初始化mFgBroadcastQueue和mBgBroadcastQueue时传入的,前台广播的超时时间是10s,后台的超时时间是60s。

BROADCAST_FG_TIMEOUT = 10 * 1000 
BROADCAST_BG_TIMEOUT = 60 * 1000

后台广播的设计思想就是当前应用优先,尽可能多让收到广播的应用有充足的时间把事件做完。

而前台广播的目的是紧急通知,设计上就倾向于当前应用赶快处理完,尽快传给下一个。

也就是说在设计上前台广播主要用于响应性能较高的场景,因为ANR时间是10s,所以开发设计的时候应该尽可能少用。因为前台广播使用的比较少,所以队列相对空闲,响应速度快。

3、对照源码分析总结:

  • 前后台队列都有自己并行和有序广播队列,互相不影响;
  • 并行队列里是无序广播+动态注册接收者;
  • 有序队列里是有序广播+动态接收者和静态接收者,静态接收者默认就是有序的;
  • 有序广播+动态接收者执行优于静态接收者先执行,综合起来就是广播相同的情况下,动态接收器优于静态接收器
  • Android版本高的,很多系统广播只支持动态注册,静态注册的话收不到广播,例如:息屏亮屏广播。因为静态注册的话,发广播的时候会把所有注册未启动的app全部拉起来,静态处理器又默认串行处理,增加了广播的处理时间。

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

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

相关文章

【不限于联想Y9000P电脑关盖再打开时黑屏的解决办法】

不限于联想Y9000P电脑关盖再打开时黑屏的解决办法 问题的前言问题的出现问题拟解决 问题的前言 事情发生在昨天&#xff0c;更新了Win11系统后&#xff1a; 最惹人注目的三处地方就是&#xff1a; 1.可以查看时间的秒数了&#xff1b; 2.右键展示的内容变窄了&#xff1b; 3.按…

205、仿真-51单片机直流数字电流表多档位切换Proteus仿真设计(程序+Proteus仿真+原理图+流程图+元器件清单+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、原理图 五、程序源码 资料包括&#xff1a; 方案选择 单片机的选择 方案一&#xff1a;STM32系列单片机控制&#xff0c;该型号单片机为LQFP44封装&#xff0c;内部资源…

等保案例 1

用户简介 吉林省人力资源和社会保障厅&#xff08;简称“吉林省人社厅”&#xff09;响应《网络安全法》的建设要求&#xff0c;为了向吉林省人民提供更好、更快、更稳定的信息化服务&#xff0c;根据《网络安全法》和等级保护2.0相关标准&#xff0c;落实网络安全与信息化建设…

【1572. 矩阵对角线元素的和】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个正方形矩阵 mat&#xff0c;请你返回矩阵对角线元素的和。 请你返回在矩阵主对角线上的元素和副对角线上且不在主对角线上元素的和。 示例 1&#xff1a; 输入&#xff1a;mat [[1,2,3]…

uniapp 官方扩展组件 uni-combox 实现:只能选择不能手写(输入中支持过滤显示下拉列表)

uniapp 官方扩展组件 uni-combox 实现&#xff1a;只能选择不能手写&#xff08;输入中支持过滤显示下拉列表&#xff09; uni-comboxuni-combox 原本支持&#xff1a;问题&#xff1a; 改造源码参考资料 uni-combox uni-combox 原本支持&#xff1a; 下拉选择。输入关键字&am…

24届近3年南京信息工程大学自动化考研院校分析

今天给大家带来的是南京信息工程大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、南京信息工程大学 学校简介 南京信息工程大学位于南京江北新区&#xff0c;是一所以大气科学为特色的全国重点大学&#xff0c;由江苏省人民政府、中华人民共和国教育部、中国气…

轻量级自动化测试框架WebZ

一、什么是WebZ WebZ是我用Python写的“关键字驱动”的自动化测试框架&#xff0c;基于WebDriver。 设计该框架的初衷是&#xff1a;用自动化测试让测试人员从一些简单却重复的测试中解放出来。之所以用“关键字驱动”模式是因为我觉得这样能让测试人员&#xff08;测试执行人员…

7-2 成绩转换

分数 15 全屏浏览题目 切换布局 作者 沈睿 单位 浙江大学 本题要求编写程序将一个百分制成绩转换为五分制成绩。转换规则&#xff1a; 大于等于90分为A&#xff1b;小于90且大于等于80为B&#xff1b;小于80且大于等于70为C&#xff1b;小于70且大于等于60为D&#xff1b;小…

gitlab修改远程仓库地址

目录 背景&#xff1a; 解决&#xff1a; 1.删除本地仓库关联的远程地址&#xff0c;添加新的远程仓库地址 2.直接修改本地仓库关联的远程仓库地址 3.打开.git隐藏文件修改远程仓库地址 4.拉取代码报错(git host key verification failed) 背景&#xff1a; 公司搬家&#…

数据可视化工具的三大类报表制作流程分享

电脑&#xff08;pc&#xff09;、移动、大屏三大类型的BI数据可视化报表制作步骤基本相同&#xff0c;差别就在于尺寸调整和具体的报表布局。这对于采用点击、拖拉拽方式来制作报表的奥威BI数据可视化工具来说就显得特别简单。接下来&#xff0c;我们就一起看看不这三大类型的…

【第三阶段】kotlin语言中的先决条件函数

用于函数内部判断异常&#xff0c;节省开发 1.checkNotNull&#xff08;&#xff09;如果传入为null则抛出异常 fun main() {var name:String?nullcheckNotNull(name) }执行结果 2.requireNotNull ()如果传入为null则抛出异常 fun main() {var name:String?nullrequireNot…

【图像分类】理论篇(4)图像增强opencv实现

随机旋转 随机旋转是一种图像增强技术&#xff0c;它通过将图像以随机角度进行旋转来增加数据的多样性&#xff0c;从而帮助改善模型的鲁棒性和泛化能力。这在训练深度学习模型时尤其有用&#xff0c;可以使模型更好地适应各种角度的输入。 原图像&#xff1a; 旋转后的图像&…

快手商品详情数据API 抓取快手商品价格、销量、库存、sku信息

快手商品详情数据API是用来获取快手商品详情页数据的接口&#xff0c;请求参数为商品ID&#xff0c;这是每个商品唯一性的标识。返回参数有商品标题、商品标题、商品简介、价格、掌柜昵称、库存、宝贝链接、宝贝图片、商品SKU等。 接口名称&#xff1a;item_get 公共参数 名…

[oneAPI] BERT

[oneAPI] BERT BERT训练过程Masked Language Model&#xff08;MLM&#xff09;Next Sentence Prediction&#xff08;NSP&#xff09;微调 总结基于oneAPI代码 比赛&#xff1a;https://marketing.csdn.net/p/f3e44fbfe46c465f4d9d6c23e38e0517 Intel DevCloud for oneAPI&…

【云原生】Kubernetes 概述

Kubernetes 概述 1.Kubernetes 简介 Kubernetes 是一个可移植的、可扩展的、用于管理容器化工作负载和服务的开源平台&#xff0c;它简化&#xff08;促进&#xff09;了声明式配置和自动化。它有一个庞大的、快速增长的生态系统。Kubernetes 的服务、支持和工具随处可见。 K…

电脑-C盘结构

一 缓存文件 winR 输入%temp% 就会进入到电脑缓存目录 这里面的东西都可以删除 主要目录在User/xxx/AppData\Local\Temp 二 临时文件 C盘右键&#xff0c;详细信息 三 桌面文件 文件类型 program data表示是游戏存档/系统/软件的配置文件 drivers文件表示驱动程序文件 s…

cs231n assignment3 q1Network Visualization

文章目录 嫌啰嗦直接看代码Q1 :Network Visualizationcompute_saliency_maps题面解析代码输出 make_fooling_image题面解析代码输出 class_visualization_update_step题面解析代码输出 结语 嫌啰嗦直接看代码 Q1 :Network Visualization compute_saliency_maps 题面 这部分的…

element-ui的el-dialog,简单的封装。

el-dialog是使用率很高的组件 使用el-dialog很多都是按照文档的例子&#xff0c;用一个变量控制是否显示&#xff0c;再来一个变量控制标题。 如果我这个对话框多个地方使用的话还要创建多个变量&#xff0c;甚至关闭之后还要清空一些变量&#xff0c;应该可以简化一点。我写…

JAVA语言:什么是懒加载机制?

JVM没有规定什么时候加载,一般是什么时候使用这个class才会什么时候加载,但是JVM规定了什么时候必须初始化(初始化是第三步、装载、连接、初始化),只要加载之后,那么肯定是要进行初始化的,所以我们就可以通过查看这个类有没有进行初始化,从而判断这个类有没有被加载。 …

第四章:前端框架Vue基础入门

文章目录 一、Vue框架概述1.1 声明响应式的数据 二、Vue内置指令2.1、条件渲染指令v-if/v-show2.2 v-for: 列表渲染2.3、v-text/v-html 模板指令2.4 v-on:事件监听器2.6 动态绑定v-bind2.7 v-model表单元素值绑定 三、计算属性与监视3.1 计算属性computed3.2 watch侦听器3.3 wa…