Android开机广播是有序还是无序?广播耗时原因是什么?

前言

前端时间,在项目中有个问题:有应用反馈收到开机广播的时间过长,距离开机过去可能半分钟了,才收到开机广播,严重影响交互。

带着这个问题,我只得查看发送广播的源码实现,并排查以下可能原因:

  • 开机广播是否是有序广播?
  • 安卓系统是怎样处理并发送有序广播、无序广播的?
  • 是什么原因,导致开机广播的传递花费这么长时间?

这里先说出部分结果:

  • 开机广播是有序广播

    在广播接收器的中,可以直接通过isOrderedBroadcast验证

  • 广播先并行处理无序广播,再串行处理有序广播

最后一个问题,从源码分析去找答案把!

源码

基于Android 10

广播由ActivityManager管理,所以主要代码都在frameworks/base/services/core/java/com/android/server/am/目录下

开机广播发送前准备

UserController

当用户系统加载完成,会通知ActiviyManagerService执行unlockUser,并在一大串方法调用链下,走到UserController.javafinishUserUnlockedCompleted方法,进行开机广播发送前的准备工作。

java 复制代码
//UserController.java

void finishUserUnlocked(final UserState uss) {

    Slog.d(TAG, "UserController event: finishUserUnlocked(" + userId + ")");
	
    ...
        
    if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT)) {
       ...
    } else {
        finishUserUnlockedCompleted(uss);
    }
}

private void finishUserUnlockedCompleted(UserState uss) {

    ... 

    Slog.i(TAG, "Posting BOOT_COMPLETED user #" + userId);
    
	...
    //开机广播intent定义
    final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
    bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
    bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                        | Intent.FLAG_RECEIVER_OFFLOAD);
    final int callingUid = Binder.getCallingUid();
    final int callingPid = Binder.getCallingPid();
    FgThread.getHandler().post(() -> {
        mInjector.broadcastIntent(bootIntent, null,new IIntentReceiver.Stub() {
            @Override
            public void performReceive(Intent intent, int resultCode, String data,Bundle extras, boolean ordered, boolean sticky, int sendingUser) throws RemoteException {
                //1.当所有接收器处理完开机广播,会回调该方法
                Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u"
                       + userId);
                mBootCompleted = true;
            }
        }, 0, null, null,new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},AppOpsManager.OP_NONE, null, 
                                  true,//2.这个参数就可以表明,开机广播是有序广播
                                  false, MY_PID, SYSTEM_UID,callingUid, callingPid, userId);
    });
}


static class Injector {
    private final ActivityManagerService mService;
    
    protected int broadcastIntent(Intent intent, String resolvedType,
                IIntentReceiver resultTo, int resultCode, String resultData,
                Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
                boolean ordered/*3.代表广播是否有序*/, 
                boolean sticky, int callingPid, int callingUid, int realCallingUid,
                int realCallingPid, int userId) {          
            synchronized (mService) {
                //4.交给ActivityManagerService去发广播
                return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo,
                        resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions,
                        ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid,
                        userId);
            }
        }
}

从图中的2、3注释可以看出,开机广播是有序广播,所以第一个疑问解决

AMS

从上面代码可以看到,UserController最终调用AMS的broadcastIntentLocked,该方法是重载方法,为广播发送前做最后准备

java 复制代码
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 realCallingUid,
            int realCallingPid, int userId) {
        return broadcastIntentLocked(callerApp, callerPackage, intent, resolvedType, resultTo,
            resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions, ordered,
            sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId,
            false /* allowBackgroundActivityStarts */);
    }

    @GuardedBy("this")
    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 realCallingUid,
            int realCallingPid, int userId, boolean allowBackgroundActivityStarts) {
        
        // 1.一系列的检查工作,会抛出异常
        
        // 2.获取注册了该广播的广播接收器
        // Figure out who all will receive this broadcast.
        List receivers = null;
        List<BroadcastFilter> registeredReceivers = null;
        
        // 3.静态注册的广播
        if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
                 == 0) {
            receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
        }
        
        // 4. 动态注册的广播
        if (intent.getComponent() == null) {
                       ....
                    List<BroadcastFilter> registeredReceiversForUser =
                            mReceiverResolver.queryIntent(intent,
                                    resolvedType, false /*defaultOnly*/, users[i]);
                ....            
        }
        // 5.先发送无序广播给动态注册的接收器
        if (!ordered && NR > 0) {
            final BroadcastQueue queue = broadcastQueueForIntent(intent);
            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                    callerPackage, callingPid, callingUid, callerInstantApp, resolvedType,
                    requiredPermissions, appOp, brOptions, registeredReceivers, resultTo,
                    resultCode, resultData, resultExtras, ordered, sticky, false, userId,
                    allowBackgroundActivityStarts, timeoutExempt);
            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
            final boolean replaced = replacePending
                    && (queue.replaceParallelBroadcastLocked(r) != null);
            // Note: We assume resultTo is null for non-ordered broadcasts.
            if (!replaced) {
                queue.enqueueParallelBroadcastLocked(r);
                queue.scheduleBroadcastsLocked();
            }
            registeredReceivers = null;
            NR = 0;
        }
        
        
        // 6.Merge into one list.
        int ir = 0;
        if (receivers != null) {
         int NT = receivers != null ? receivers.size() : 0;
            int it = 0;
            ResolveInfo curt = null;
            BroadcastFilter curr = null;
            while (it < NT && ir < NR) {
                if (curt == null) {
                    curt = (ResolveInfo)receivers.get(it);
                }
                if (curr == null) {
                    curr = registeredReceivers.get(ir);
                }
                if (curr.getPriority() >= curt.priority) {
                    // Insert this broadcast record into the final list.
                    receivers.add(it, curr);
                    ir++;
                    curr = null;
                    it++;
                    NT++;
                } else {
                    // Skip to the next ResolveInfo in the final list.
                    it++;
                    curt = null;
                }
            }   
        }
        
        while (ir < NR) {
            if (receivers == null) {
                receivers = new ArrayList();
            }
            receivers.add(registeredReceivers.get(ir));
            ir++;
        }

        ....
		// 7.
        if ((receivers != null && receivers.size() > 0)
                || resultTo != null) {
            BroadcastQueue queue = broadcastQueueForIntent(intent);
            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                    callerPackage, callingPid, callingUid, callerInstantApp, resolvedType,
                    requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,
                    resultData, resultExtras, ordered, sticky, false, userId,
                    allowBackgroundActivityStarts, timeoutExempt);
            ....          
            } else {
                queue.enqueueOrderedBroadcastLocked(r);//存放无序广播的静态接收器、有序广播的动态\静态接收器
                queue.scheduleBroadcastsLocked();
            }
        
    }

这个方法的代码非常长,但主要就是干了这几件事:

  • 事先的一系列检查:包括权限、当前系统所处的状态,如果不符合发送广播条件,就抛出异常
  • 获取注册了该广播的静态接收器和动态接收器
  • 如果广播是无序广播,则先发送给动态注册的接收器,因为动态注册的接收器,其应用已经起来
  • 按照广播接收器的优先级,合并动态接收器和静态接收器(注意这里可能包含三种类型的广播接收器:无序广播的静态注册接收器、有序广播的静态\动态接收器)

发送广播

可以看到,无论是有序广播还是无序广播,AMS最终把广播接收器作为参数传给了BroadcastRecord(后面简化为BR)对象,然后再调用BroadcastQueue的两个方法:

java 复制代码
queue.enqueueParallelBroadcastLocked(r);//无序广播调用
queue.enqueueOrderedBroadcastLocked(r);//有序广播调用

queue.scheduleBroadcastsLocked();

第一个方法enqueueOrderedBroadcastLocked

java 复制代码
//BroadcastQueue.java
public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
    mDispatcher.enqueueOrderedBroadcastLocked(r);
    enqueueBroadcastHelper(r);
}
java 复制代码
//BroadcastDispatcher.java
private final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();

...
    
void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
    mOrderedBroadcasts.add(r);
}

可以看到BR被放到了BroadcastDispatcher的列表存放了起来。

第二个方法scheduleBroadcastsLocked

java 复制代码
//BroadcastQueue.java
public void scheduleBroadcastsLocked() {
    if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
                                + mQueueName + "]: current="
                                + mBroadcastsScheduled);

    if (mBroadcastsScheduled) {
        return;
    }
    mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
    mBroadcastsScheduled = true;
}

...

final BroadcastHandler mHandler;

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: {
                if (DEBUG_BROADCAST) Slog.v(
                    TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["
                    + mQueueName + "]");
                processNextBroadcast(true);
            } break;
            case BROADCAST_TIMEOUT_MSG: {
                synchronized (mService) {
                    broadcastTimeoutLocked(true);
                }
            } break;
        }
    }
}

//BroadcastQueue.java
final void processNextBroadcast(boolean fromMsg) {
    synchronized (mService) {
        processNextBroadcastLocked(fromMsg, false);
    }
}

scheduleBroadcastsLocked里面,利用handler发送消息,handler处理事件,调用processNextBroadcast

分段来分析:

第一段

java 复制代码
//发送广播的主要代码
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
    BroadcastRecord r;

    mService.updateCpuStats();

    if (fromMsg) {
        mBroadcastsScheduled = false;
    }

    // 1.First, deliver any non-serialized broadcasts right away.
    while (mParallelBroadcasts.size() > 0) {
        r = mParallelBroadcasts.remove(0);

        final int N = r.receivers.size();
        
        for (int i=0; i<N; i++) {
            Object target = r.receivers.get(i);
           
            deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i);
        }
        addBroadcastToHistoryLocked(r);
    }

如果mParallelBroadcasts不为空,发送所有无序广播

第二段

java 复制代码
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.isCrashing();
        }
    } else {
        final ProcessRecord proc = mService.mProcessList.mProcessNames.get(
            mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
        isDead = proc == null || !proc.pendingStart;
    }
    if (!isDead) {
        // It's still alive, so keep waiting
        return;
    } else {
        mPendingBroadcast.state = BroadcastRecord.IDLE;
        mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
        mPendingBroadcast = null;
    }
}

mPendingBroadcast存放的是即将发送的广播,并且等待广播接收器的应用起来,所以这一段是判断这个广播的应用起来没。

第三段:

java 复制代码
boolean looped = false;

do {
    final long now = SystemClock.uptimeMillis();
    //1
    r = mDispatcher.getNextBroadcastLocked(now);

    if (r == null) {
        ...

            return;
    }

    boolean forceReceive = false;

    int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;

    if (!r.deferred) {
        final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
        if (mDispatcher.isDeferringLocked(receiverUid)) {

            BroadcastRecord defer;
            if (r.nextReceiver + 1 == numReceivers) {

            } else {
                // 2.
                defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);

            }
            mDispatcher.addDeferredBroadcast(receiverUid, defer);
            r = null;
            looped = true;
            continue;
        }
    }
} while (r == null);

这是一个循环,我主要是看如何满足条件跳出循环。

注释一,取出一个BR,并且保存到mCurrentBroadcast,直到处理完成前,都会一直是这个BR

java 复制代码
//BroadcastDispatcher.java
public BroadcastRecord getNextBroadcastLocked(final long now) {
    if (mCurrentBroadcast != null) {
        return mCurrentBroadcast;
    }

    final boolean someQueued = !mOrderedBroadcasts.isEmpty();

    BroadcastRecord next = null;

    if (next == null && someQueued) {
        next = mOrderedBroadcasts.remove(0);
        if (DEBUG_BROADCAST_DEFERRAL) {
            Slog.i(TAG, "Next broadcast from main queue: " + next);
        }
    }

    mCurrentBroadcast = next;
    return next;
}

注释二,找到该userID下所有注册该广播的接收器,并且deferred置为true

经过第一次循环这两个注释的处理,下一次取出的r!=null,并且defferred为true,就可以跳出循环。

第三段

java 复制代码
// 应用已经起来
if (app != null && app.thread != null && !app.killed) {
    try {
        app.addPackage(info.activityInfo.packageName,
                       info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
        maybeAddAllowBackgroundActivityStartsToken(app, r);
        processCurBroadcastLocked(r, app, skipOomAdj);//发送广播
        return;
    } 

    // If a dead object exception was thrown -- fall through to
    // restart the application.
}

// 应用还没起来

if ((r.curApp=mService.startProcessLocked(targetProcess,
                                          info.activityInfo.applicationInfo, true,
                                          r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
                                          new HostingRecord("broadcast", r.curComponent),
                                          (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
    == null) {
    // Ah, this recipient is unavailable.  Finish it if necessary,
    // and mark the broadcast record as ready for the next.
    Slog.w(TAG, "Unable to launch app "
           + info.activityInfo.applicationInfo.packageName + "/"
           + receiverUid + " for broadcast "
           + r.intent + ": process is bad");
    ....
    return;
}

maybeAddAllowBackgroundActivityStartsToken(r.curApp, r);
mPendingBroadcast = r;
mPendingBroadcastRecvIndex = recIdx;

第三段代码只贴了最后几句,前面的大部分都在判断该广播是否该skip。

最后几句代码:

  • 当前要发送的广播接收器的应用已经起来,直接发送
  • 当前要发送的广播接收器的应用还没起来,继续等待。
  • 将当前代码保存到mPendingBroadcast等待处理完毕。

这里其实就已经得到答案:

  • 对于有序广播,通过静态注册的广播接收器,要等待应用起来,然后才处理广播并调用onReceive方法

  • 如果某个应用的启动过程比较长,那会阻塞广播的传递,增加后面应用的等待时间。

参考: solarqiang.github.io/posts/37529...

相关推荐
CYRUS_STUDIO3 小时前
一步步带你移植 FART 到 Android 10,实现自动化脱壳
android·java·逆向
CYRUS_STUDIO3 小时前
FART 主动调用组件深度解析:破解 ART 下函数抽取壳的终极武器
android·java·逆向
蓝倾9766 小时前
淘宝/天猫店铺商品搜索API(taobao.item_search_shop)返回值详解
android·大数据·开发语言·python·开放api接口·淘宝开放平台
Propeller6 小时前
【Android】LayoutInflater 控件实例化的桥梁类
android
猿小蔡-Cool6 小时前
Robolectric如何启动一个Activity
android·单元测试·rebolectric
Industio_触觉智能6 小时前
瑞芯微RK3576开发板Android14三屏异显开发教程
android·开发板·瑞芯微·rk3576·多屏异显·rk3576j·三屏异显
AI视觉网奇8 小时前
android adb调试 鸿蒙
android
NRatel10 小时前
GooglePlay支付接入记录
android·游戏·unity·支付·googleplay
在下历飞雨10 小时前
为啥选了Kuikly?2025“液态玻璃时代“六大跨端框架横向对比
android·harmonyos