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...

相关推荐
用户20187928316718 分钟前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子18 分钟前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜822727 分钟前
安卓接入Max广告源
android
齊家治國平天下27 分钟前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO28 分钟前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel31 分钟前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢36 分钟前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱
IT酷盖36 分钟前
Android解决隐藏依赖冲突
android·前端·vue.js
努力学习的小廉2 小时前
初识MYSQL —— 数据库基础
android·数据库·mysql
风起云涌~2 小时前
【Android】浅谈androidx.startup.InitializationProvider
android