• android U广播详解(一)


    概念介绍

    进程队列

    BroadcastQueueModernImpl 的设计围绕着为设备上的每个潜在进程维护一个单独的 BroadcastProcessQueue 实例。表明用于传送到特定进程的Pending {@link BroadcastRecord} 条目队列。整个类都标记为 {@code NotThreadSafe},因为调用者有责任始终与持有的相关锁进行交互。

    结构

    在内部,每个队列由一个等待调度的待处理广播和一个当前正在调度的活动广播组成。给等待调度的广播分别维护了紧急、普通、负载三个有序集合,优先级为紧急(3) > 普通(10) > 负载,但是当优先级高的集合被处理的广播数量超过一定限制时,优先级低的广播也有机会得到执行。

    可运行

    每个队列都有一个在未来特定时间“可运行”(runnable at 时间戳)的概念,它支持在每个进程的基础上任意暂停或延迟交付。当它下一次符合执行条件时,该值会受到各种策略的影响,例如:

    • 哪些广播正在等待发送给给定进程。 例如,“紧急”(前台、源于用户交互、闹钟)广播通常会导致较早的runnable at(-120s)时间,或者“延迟”广播通常会导致较晚的runnable at时间。
    • 进程或 UID 的当前状态。 例如,“cached”(procState > PROCESS_STATE_RECEIVER)进程通常会导致较晚的runnable at(+120s)时间,或者“instrumented”进程通常会导致较早的runnable at(-120s)时间。
    • 阻塞等待较早的接收器完成。 例如,“有序”或“优先”广播通常会导致not currently runnable值。

    并行调度

    调度

    给定具有有效 runnable at 时间戳的每个进程队列的集合,BroadcastQueueModernImpl 然后愿意将这些 runnable队列提升为 running 状态。 我们根据 runnable at 时间戳的排序顺序选择下一个要提升的每个进程队列,首先选择等待时间最长的进程,旨在减少整体广播调度延迟。
    限制

    • 为了保持系统健康(因为有序或静态注册的会在binder线程接着分发),在任何给定时间最多允许“BroadcastConstants.MAX_RUNNING_PROCESS_QUEUES”(4,低内存2个)进程处于running状态,额外会在此基础上再允许运行一个有紧急广播的队列。
    • 在任何给定时间最多允许一个进程被冷启动。 (对于后台,通过fork和专门化 zygote 的冷启动进程是一项相对繁重的操作,因此将我们自己限制在单个挂起的冷启动减少了系统范围的资源争用。)等前一个进程冷启结束后可立刻安排下一次调度。
    分批派发

    考虑将进程队列中任何其他Pending广播分派到该进程,旨在分批分派以更好地分摊 OOM 调整的成本。每个进程队列一次可最多分派MAX_RUNNING_ACTIVE_BROADCASTS(16,低内存8)个广播,以避免其他进程处于饥饿状态。

    有序

    收集一次可分发到的receivers时,如果遇到有序广播或静态注册的receiver,则直接中断收集 ,将已有receivers分发到app进程。等app进程完成分发后告知系统,仍然在binder线程继续完成当前进程队列的分发。

    饥饿注意事项

    仔细关注几种类型的潜在资源匮乏,以及缓解机制:

    • 进程队列的应用延迟runnable at 策略可能会导致Pending列表变得非常大。 当队列变得太大时,“BroadcastConstants.MAX_PENDING_BROADCASTS”会绕过任何延迟来缓解这种情况。
    • 具有大量Pending广播的进程队列可能会独占有限的 runnable 插槽之一。 这可以通过使用“BroadcastConstants.MAX_RUNNING_ACTIVE_BROADCASTS”来暂时“退出”正在运行的进程以让其他进程有机会运行来缓解。
    • 一个“紧急”广播被发送到一个有大量“非紧急”广播积压的进程可能会有很大的发送延迟。 这可以通过维护一个单独的紧急事件的 mPendingUrgent 队列来缓解,我们更愿意在正常的 mPending 队列之前调度它。
    • 具有有序广播的进程希望执行,但严重的 CPU 争用可能会导致进程在触发 ANR 超时之前无法接收到足够的资源。 这可以通过将“软”ANR 超时延长至原始超时长度的两倍来缓解。

    BroadcastQueueModernImpl

    替代 {@link BroadcastQueue} 实现,它以每个进程为基础调度广播。
    每个进程现在都有自己的广播队列,由 {@link BroadcastProcessQueue} 实例表示。 每个队列都有一个在未来特定时间“可运行”的概念,它支持在每个进程的基础上任意暂停或延迟交付。
    为了让事情更容易推理,有一种非常强烈的偏好,即让广播交互以这种特定顺序通过一组一致的方法流动:

    • {@link #updateRunnableList} 在每个进程队列有相关的未决广播时将其提升为可运行
    • {@link #updateRunningList} 促进可运行队列运行并安排第一次广播的传送
    • {@link #scheduleReceiverColdLocked} 请求任何需要的冷启动,结果{@link#onApplicationAttachedLocked} 报告
    • {@link #scheduleReceiverWarmLocked} 请求将当前活动的广播发送到正在运行的应用程序,并通过 {@link #finishReceiverLocked} 报告结果

    主要特点

    android U广播机制重构原因:

    • 遗留广播队列以串行方式一次一个地向接收方应用程序发送广播(有序)
    • 最初是一个合理的选择,因为这些是后台任务
    • 这些年来,病态的案例不断涌现,就像在许多接收器中翻滚一样。
    • 除了一些迭代改进外,整体架构自Android~1.0以来保持不变

    高效的广播传输

    允许多个进程并行处理广播,并通过一次将多个广播分派给一个进程来最小化 OOm 调整成本。

    • 引入每个进程队列来处理广播传输并在每个进程的基础上设置策略(暂停/延迟)
    • 允许多个进程(4+1)并行处理广播,同时保留来自每个应用程序 PoV 的广播传送顺序
    • 通过连续向一个进程传送多个广播(16,core 进程最多可16+64)来最小化 OOM 调整成本

    广播从入队到分发的大致流程,更新mRunning列表在ActivityManager线程。
    在这里插入图片描述

    允许多个进程(4+1)并行处理广播:

    • 有序广播分发后会等到receiver finish后重新在binder线程继续分发当前进程队列里等待分发的广播,直到没有广播分发或达到分发上限。
    • 同一时间内只会允许冷启动一个进程,进程启动成功后会在binder线程中分发当前进程队列里等待分发的广播。

    所以最多会有5个线程可同时针对5个各自的进程队列进行广播反而分发,大大提高了广播的分发速度(尤其是有序广播或功能清单注册的接收器)。

    在这里插入图片描述

    消除冗余广播

    丢弃和合并频繁发送的广播

    允许系统丢弃和合并某些高频广播的新 API

    广播发送者可以指定如何处理他们的广播

    • 使用 setDeliveryGroupMatching*() API 指示应如何对广播进行分组
    • 使用 setDeliveryGroupPolicy() API 来指示如何“合并”属于一个组的广播
      在这里插入图片描述
      下发组策略:
        // 下发组策略,表示下发组内的所有广播都需要按原样下发。
        @SystemApi
        public static final int DELIVERY_GROUP_POLICY_ALL = 0;
            // 下发组策略,表示只下发该下发组中最新的广播,其余的可以丢弃。
        @SystemApi
        public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1;
            // 交付组策略,指示交付组中广播的额外数据需要合并到单个广播中,其余数据可以丢弃。
        public static final int DELIVERY_GROUP_POLICY_MERGED = 2;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    例子

    将 MOST_RECENT 策略应用于 CONNECTIVITY_ACTION

    • 将networkType设置为匹配键,表示特定网络类型对应的所有广播都属于同一个投递组
    • 设置MOST_RECENT为policy,表示只需要传递一个传递组中最近的广播,其余的可以丢弃
      在这里插入图片描述
    应用策略的示例广播

    在这里插入图片描述

    减少后台工作(已被废弃)

    阻止处于缓存状态的应用程序使用广播(已废弃,改为阻止冻结状态)
    防止处于Cached状态的应用使用广播消耗系统资源

    • 添加了一个新的BroadcastOptions BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE以允许发送者指示他们对缓存应用程序的广播可以延迟到它们变为活跃状态

    限制cache进程接收广播的更改历史:

    • 谷歌从开始的限制(procState > 11)的进程接收广播;
    • 到后面限制(procState > 15)的进程接收广播;
    • 再到现在只限制冻结的进程接收广播。
    /** {@hide} */
    @IntDef(prefix = { "DEFERRAL_POLICY_" }, value = {
            DEFERRAL_POLICY_DEFAULT,
            DEFERRAL_POLICY_NONE,
            DEFERRAL_POLICY_UNTIL_ACTIVE,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface DeferralPolicy {}
    
    public @NonNull BroadcastOptions setDeferralPolicy(@DeferralPolicy int deferralPolicy) {
        mDeferralPolicy = deferralPolicy;
        return this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    如何分析

    通过执行如下命令可以查看历史的广播分发记录等信息。

    adb shell dumpsys activity broadcasts
    
    • 1

    整体结构

      // 完整的广播队列列表,如果没有Active状态的广播则不打印当前的进程队列
      📋 Per-process queues:
      
      // mRunnableHead 中保存的进程队列(可运行的)
      🧍 Runnable:
        (none)
      
      // mRunning 中保存的进程队列(正在运行的)
      🏃 Running:
          (none)
          (none)
          (none)
          (none)
          (none)
          
      // 可暂不管,系统配置的是否忽略传递组策略
      Broadcasts with ignored delivery group policies:
        {}
      
      // mUidForeground ,保存当前处于前台(procstate为PROCESS_STATE_TOP)的uid
      Foreground UIDs:
        {}
      
      // 一些常量
      Broadcast parameters (key=bcast_fg_constants, observing=true):
        bcast_timeout=+10s0ms 
        bcast_slow_time=+5s0ms 
        bcast_deferral=+5s0ms 
        bcast_deferral_decay_factor=0.75 
        bcast_deferral_floor=0 
        bcast_allow_bg_activity_start_timeout=+10s0ms 
      
      Broadcast parameters (namespace=activity_manager_native_boot):
        modern_queue_enabled=true  // 使用现代广播队列逻辑处理广播
        bcast_max_running_process_queues=4 // 一次最多可同时进行分发的广播队列数量
        bcast_max_running_active_broadcasts=16 // 单个广播队列一次最多可分发的广播数量
        bcast_max_core_running_blocking_broadcasts=16 
        bcast_max_core_running_non_blocking_broadcasts=64 
        bcast_max_pending_broadcasts=256  
        bcast_delay_normal_millis=+500ms 
        bcast_delay_cached_millis=+2m0s0ms 
        bcast_delay_urgent_millis=-2m0s0ms 
        bcast_delay_foreground_proc_millis=-2m0s0ms 
        bcast_delay_persistent_proc_millis=-2m0s0ms 
        bcast_max_history_complete_size=256  // 最多可保存的完整的历史已分发完成的广播数量
        bcast_max_history_summary_size=1024 // 最多可保存的简要的历史已分发完成的广播数量
        bcast_max_consecutive_urgent_dispatches=3 
        bcast_max_consecutive_normal_dispatches=10 
        bcast_core_defer_until_active=true 
        pending_cold_start_check_interval_millis=30000 
      
        // 广播的历史记录
        // 正在被分发或等待被分发的广播
        Pending broadcasts:
          <empty>
      
        // 已经分发完毕的广播(完整的信息)
        Historical broadcasts [modern]:
        Historical Broadcast modern #0:
        
        // 已经分发完毕的广播(简要的信息)
        Historical broadcasts summary [modern]:
        #0: act=miui.intent.action.CYCLE_CHECK flg=0x40000010
          0 dispatch +5ms finish
          enq=2023-10-12 19:04:51.070 disp=2023-10-12 19:04:51.070 fin=2023-10-12 19:04:51.075
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    广播的分发状态

        @IntDef(flag = false, prefix = { "DELIVERY_" }, value = {
                DELIVERY_PENDING, // 初始状态:等待未来运行
                DELIVERY_DELIVERED, // 终端状态:成功完成
                DELIVERY_SKIPPED, // 终端状态:由于内部政策而跳过
                DELIVERY_TIMEOUT, // 终端状态:尝试投递时超时
                DELIVERY_SCHEDULED, // 中间状态:当前正在执行
                DELIVERY_FAILURE, // 终端状态:派送失败
        })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    PENDING
        Broadcast #5:
          BroadcastRecord{67ebdbd android.intent.action.SCREEN_OFF/u-1} to user -1
          Intent { act=android.intent.action.SCREEN_OFF flg=0x50200010 }
          caller=null null pid=2195 uid=1000
           // deferralPolicy设置冻结状态进程不能接收此广播
          // deliveryGroupMatchingKey 指示当前广播可跟SCREEN_ON归并为一个广播,可能会被合并skip
          // deliveryGroupPolicy 表示只下发该下发组中最新的广播,其余的可以丢弃
          options=Bundle[{android:broadcast.deferralPolicy=2, android:broadcast.deliveryGroupMatchingKey=android.intent.action.SCREEN_ON, android:broadcast.deliveryGroupMatchingNamespace=dc326423-1f3f-4b61-b786-497343eed8a6, android:broadcast.deliveryGroupPolicy=1}]
          enqueueClockTime=2023-10-12 19:48:10.649 dispatchClockTime=2023-10-12 19:48:10.651
          dispatchTime=-339ms (+2ms since enq) receiverTime=--
          resultTo=com.android.server.power.Notifier$3@173f7cf resultCode=0 resultData=null
          terminalCount=14
          ......
          PENDING (0) #10: BroadcastFilter{4bba08a 1002/u0 ReceiverList{696aaf5 4930 com.android.bluetooth/1002/u0 remote:5e5002c}}
          SCHEDULED scheduled +34ms (0) #13: BroadcastFilter{f6f7d24 1000/u0 ReceiverList{25811b7 5098 com.android.systemui/1000/u0 remote:b8a0b6}}
            reason: scheduleReceiverWarmLocked
          PENDING (0) #14: BroadcastFilter{1827b0 1002/u0 ReceiverList{9ddef3 4930 com.android.bluetooth/1002/u0 remote:e04ad62}}
          PENDING (0) #15: BroadcastFilter{32d3fb5 1002/u0 ReceiverList{699efec 6219 com.xiaomi.bluetooth/1002/u0 remote:ffcf29f}}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    DELIVERED
        Historical Broadcast modern #0:
          BroadcastRecord{fd510eb android.intent.action.SIM_STATE_CHANGED/u-1} to user -1
          Intent { act=android.intent.action.SIM_STATE_CHANGED flg=0x15000010 (has extras) }
            extras: Bundle[{slot_id=0, subscription_id=-1, phone_id=0, android.telephony.extra.SLOT_INDEX=0, phoneName=Phone, reason=null, rebroadcastOnUnlock=true, ss=ABSENT, phone=0}]
          caller=com.android.phone 5080:com.android.phone/1001 pid=5080 uid=1001
          enqueueClockTime=2023-10-12 19:48:09.686 dispatchClockTime=1970-01-01 08:00:00.000
          dispatchTime=-- (-22s714ms since enq) finishTime=-44ms (+23s984ms since disp)
          resultAbort=false ordered=false sticky=true initialSticky=false originalStickyCallingUid=-1
          nextReceiver=57
          terminalCount=57
          DELIVERED scheduled +1ms terminal 0 (0) #0: BroadcastFilter{934d218 1001/u0 ReceiverList{e8291fb 5080 com.android.phone/1001/u0 remote:a5fa78a}}
            // 动态注册的receiver & 无序广播,无需等待app的finish回调,直接结束当前receiver的分发
            reason: assuming delivered
         .....
          DELIVERED scheduled +512ms terminal +109ms (0) #4: (manifest) //清单文件注册的receiver
            priority=1000 preferredOrder=0 match=0x108000 specificIndex=-1 isDefault=false
            ActivityInfo:
              name=com.miui.vsimcore.ProvisionReceiver
              packageName=com.miui.vsimcore
              labelRes=0x7f0b001f nonLocalizedLabel=null icon=0x0 banner=0x0
              enabled=true exported=true directBootAware=false
              launchMode=LAUNCH_MULTIPLE flags=0x10000 privateFlags=0x0 theme=0x0
              resizeMode=RESIZE_MODE_RESIZEABLE
              knownActivityEmbeddingCerts={}
             // app进程回调finishReceiverLocked,一般是清单注册的receiver或分发有序广播
            reason: remote app 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    SKIPPED
          SKIPPED terminal +22s715ms (6) #54: (manifest) // 清单注册的receiver
            priority=0 preferredOrder=0 match=0x108000 specificIndex=-1 isDefault=false
            ActivityInfo:
              name=com.android.updater.receiver.SimChangeReceiver
              packageName=com.android.updater
              enabled=true exported=false directBootAware=false
              launchMode=LAUNCH_MULTIPLE flags=0x10000 privateFlags=0x0 theme=0x0
              resizeMode=RESIZE_MODE_RESIZEABLE
              knownActivityEmbeddingCerts={}
              // 入队的时候就被skip了,广播发送方没有ActivityInfo的相关权限
            reason: skipped by policy at enqueue: Permission Denial: broadcasting Intent { act=android.intent.action.SIM_STATE_CHANGED flg=0x15000010 (has extras) } from com.android.phone (pid=5080, uid=1001) to com.android.updater/.receiver.SimChangeReceiver is not exported from uid 6102
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
        Historical Broadcast modern #2:
          BroadcastRecord{ee963f4 android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED/u0} to user 0
          Intent { act=android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED flg=0x40000010 }
          caller=android 2195:system/1000 pid=2195 uid=1000
          options=Bundle[{android:broadcast.deferralPolicy=2, android:broadcast.deliveryGroupPolicy=1}]
          enqueueClockTime=2023-10-12 19:48:10.810 dispatchClockTime=1970-01-01 08:00:00.000
          dispatchTime=-- (-23s839ms since enq) finishTime=-105ms (+23s925ms since disp)
          resultTo=null resultCode=0 resultData=null
          nextReceiver=1
          terminalCount=1
          // 广播发送断指定了相关的传送策略
          SKIPPED terminal +23s925ms (-1) #0: BroadcastFilter{47578bc 1000/u-1 ReceiverList{c80dcaf 2195 system/1000/u-1 local:25a5c8e}}
            reason: mBroadcastConsumerSkipAndCanceled
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    SCHEDULED
        Broadcast #5:
          BroadcastRecord{67ebdbd android.intent.action.SCREEN_OFF/u-1} to user -1
          Intent { act=android.intent.action.SCREEN_OFF flg=0x50200010 }
          caller=null null pid=2195 uid=1000
          options=Bundle[{android:broadcast.deferralPolicy=2, android:broadcast.deliveryGroupMatchingKey=android.intent.action.SCREEN_ON, android:broadcast.deliveryGroupMatchingNamespace=dc326423-1f3f-4b61-b786-497343eed8a6, android:broadcast.deliveryGroupPolicy=1}]
          enqueueClockTime=2023-10-12 19:48:10.649 dispatchClockTime=2023-10-12 19:48:10.651
          dispatchTime=-339ms (+2ms since enq) receiverTime=--
          resultTo=com.android.server.power.Notifier$3@173f7cf resultCode=0 resultData=null
          terminalCount=14
          ......
          // 已经准备派发到app进程了,动态注册的receiver
          SCHEDULED scheduled +34ms (0) #13: BroadcastFilter{f6f7d24 1000/u0 ReceiverList{25811b7 5098 com.android.systemui/1000/u0 remote:b8a0b6}}
            reason: scheduleReceiverWarmLocked
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
          // 已经准备派发到app进程了,静态注册的receiver
          SCHEDULED scheduled +1s267ms (6) #56: (manifest)
            priority=0 preferredOrder=0 match=0x108000 specificIndex=-1 isDefault=false
            ActivityInfo:
              name=com.xiaomi.activate.SimStateReceiver
              packageName=com.xiaomi.simactivate.service
              enabled=true exported=true directBootAware=false
              launchMode=LAUNCH_MULTIPLE flags=0x10000 privateFlags=0x0 theme=0x0
              resizeMode=RESIZE_MODE_RESIZEABLE
              knownActivityEmbeddingCerts={}
            reason: scheduleReceiverWarmLocked
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    App注意事项

    如何让广播更快被分发

    这里针对的是广播发送断

    1. 标记广播的intent为interactive
    BroadcastOptions options = BroadcastOptions.makeBasic();
    options.setInteractive(true);
    pendingIntent.send(options.toBundle());
    
    • 1
    • 2
    • 3

    一般用于标记用户启动的 PendingIntent,需要声明如下权限:

    <uses-permission android:name="android.permission.COMPONENT_OPTION_INTERACTIVE" />
    
    • 1
    1. 给广播的intent添加前台标记
    Intent intent = new Intent(XXX);
    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    
    • 1
    • 2

    App须知

    1. 耗时时间长&接受者多的广播建议加上负载标志FLAG_RECEIVER_OFFLOAD,目前而言就开机广播;
    2. 短时间内发送频率高的广播,建议发送时按需指定DELIVERY_GROUP_POLICY_MOST_RECENT或DELIVERY_GROUP_POLICY_MERGED的policy。
    final Bundle mostRecentDeliveryOptions = BroadcastOptions.makeBasic()
                            .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
                            .toBundle();
    
    • 1
    • 2
    • 3
  • 相关阅读:
    localStorage 和 sessionStorage 异同
    自定义类使用ArrayList中的remove
    SAP-QM-采购过程模式与特性检验不匹配QD244
    【Java实验五】继承与多态
    python基本语法
    1409. 查询带键的排列
    网络安全——SQL注入漏洞
    2、k-means聚类算法sklearn与手动实现
    【Linux学习】- POSIX多线程技术
    小学生加减乘除闯关运算练习流量主微信小程序开发
  • 原文地址:https://blog.csdn.net/xiaoyantan/article/details/133795980