[ Android实战 ] 开机时通过广播启动应用,但是很长时间才能接收到,如何解决?

[ Android实战 ] 开机时通过广播启动应用,但是很长时间才能接收到,如何解决?

[ Android实战 ] 开机时通过广播启动应用,但是很长时间才能接收到,如何解决?

背景测试发送广播流程广播分发流程解决方案思考系统层面应用层面

总结

转载请注明出处!我的博客

背景

前段时间在做一个项目,在适配客户应用的过程中发现一个问题:客户在自己的 A 应用(Launcher 应用)中发送了一个广播,希望能启动另一个静态注册了该广播的应用 B。 但是实测过程中发现,开机完成后 A 应用起来了,广播也很快发出了。但是过了 40~50 秒,B 应用才启动并接收到广播! 这显然很不正常,按照之前的认知,通过广播启动应用在 Android 中是一种很常见的做法,最常见的就是通过接收 Android 开机广播来启动应用。这种做法,不应该有问题啊!

测试

为了排除客户应用的问题,自己写了 2 个简单的应用,通过 Demo 发送广播,启动静态注册的 Demo1。

开机启动完成后,马上点击应用 Demo,发出广播启动应用 Demo1,需要 19 秒。 同样的应用,开机启动完成后,等待 1 分钟左右,再点击 Demo,发出广播启动应用 Demo1,只需要 200 多毫秒,这个耗时就比较正常了。 测试用的机器并没装什么应用,所以耗时比客户应用实测的耗时会短一些,但是19秒的时间仍然很慢。

并且从上面测试的两种情况可以很明显看出,这个问题是由于系统启动阶段做了什么事情,才导致广播接收慢的。

要处理这个问题,必须研究下 Android 发送广播的流程,看看到底哪个环节出了问题。

发送广播流程

发送广播一般都是通过 sendBroadcast 方法,最终调用到 AMS 里面的 broadcastIntent 方法。

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

@Override

public void sendBroadcast(Intent intent) {

warnIfCallingFromSystemProcess();

String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());

try {

intent.prepareToLeaveProcess(this);

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

}

}

AMS 中的 broadcastIntent 又调用了 broadcastIntentLocked 方法,所以我们直接对这个方法的流程进行分析。由于这个函数代码太多,我们从问题出发,忽略掉动态注册的处理以及其他的一些流程,只针对静态注册的 receiver 进行分析。

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.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 userId) {

......

// Figure out who all will receive this broadcast.

List receivers = null;

List registeredReceivers = null;

// Need to resolve the intent to interested receivers...

if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)

== 0) {

// 这里先获取静态注册了该intent的receiver,放到receivers队列中

receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);

}

if (intent.getComponent() == null) {

// 这里调用 mReceiverResolver.queryIntent 获取动态注册了该intent的receiver,放到registeredReceivers队列中

......

}

......

int NR = registeredReceivers != null ? registeredReceivers.size() : 0;

// 对无序且动态注册的广播加入到广播队列等待分发,这部分广播是可以并行处理的

if (!ordered && NR > 0) {

// 将BroadcastRecord加入到mParallelBroadcasts,调用BroadcastQueue.scheduleBroadcastsLocked进行处理

......

}

......

// 对有序且动态注册的广播以及静态注册的广播进行处理,这部分广播是串行处理的

if ((receivers != null && receivers.size() > 0)

|| resultTo != null) {

BroadcastQueue queue = broadcastQueueForIntent(callerPackage, 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);

final BroadcastRecord oldRecord =

replacePending ? queue.replaceOrderedBroadcastLocked(r) : null;

if (oldRecord != null) {

......

} else {

// 这里是核心代码,将构造的BroadcastRecord加入到mOrderedBroadcasts

queue.enqueueOrderedBroadcastLocked(r);

// 调用BroadcastQueue.scheduleBroadcastsLocked,从广播队列中取出广播进行处理

queue.scheduleBroadcastsLocked();

}

} else {

// 没有receivers接收该广播,只作记录,不做其他处理

......

}

return ActivityManager.BROADCAST_SUCCESS;

}

可以看到 broadcastIntentLocked 中,针对静态注册的 receiver 主要做了 2 件事情:

1、collectReceiverComponents 获取到静态注册的 receiver,放到 receivers 队列中

2、构造 BroadcastRecord,调用 BroadcastQueue.enqueueOrderedBroadcastLocked 加入到广播队列中,再调用 BroadcastQueue 等待分发。

PS:collectReceiverComponents 的实现其实就是调用了 PKMS 的 queryIntentReceivers 方法查询静态注册了该 intent 的 receiver,而 PKMS 则是在开机启动时扫描 package 的时候,从 manifest 中解析出 Receiver,保存到 mReceivers 这个全局变量中。

整个解析和查询的实现其实我也没细看,立个 flag,后续专门写一篇博客分析静态注册的流程~

广播分发流程

广播分发的主要代码在 BroadcastQueue 中实现。可以看到 enqueueOrderedBroadcastLocked 只是简单地把 BroadcastRecord 加入到 mOrderedBroadcasts 队列中。

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

public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {

mOrderedBroadcasts.add(r);

enqueueBroadcastHelper(r);

}

scheduleBroadcastsLocked 也只是发送了一个 message 给 Handler,调用 processNextBroadcast 进行真正的分发流程。

/frameworks/base/services/core/java/com/android/server/am/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;

}

private final class BroadcastHandler extends Handler {

@Override

public void handleMessage(Message msg) {

switch (msg.what) {

case BROADCAST_INTENT_MSG: {

if (DEBUG_BROADCAST) Slog.v(

TAG_BROADCAST, "Received BROADCAST_INTENT_MSG");

processNextBroadcast(true);

} break;

}

}

}

processNextBroadcast 是 BroadcastQueue 中分发广播的核心方法,对并行广播和串行广播进行处理。

由于代码太多,本篇博客主要关注静态注册广播的发送流程,所以删掉了大量的代码,只留下了我觉得比较重要的部分:

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

final void processNextBroadcast(boolean fromMsg) {

synchronized(mService) {

BroadcastRecord r;

......

// 首先,直接遍历mParallelBroadcasts,对并行广播(无序广播+动态注册的receiver)进行处理

while (mParallelBroadcasts.size() > 0) {

r = mParallelBroadcasts.remove(0);

......

final int N = r.receivers.size();

// 遍历广播的所有receivers,进行分发

for (int i=0; i

Object target = r.receivers.get(i);

// deliverToRegisteredReceiverLocked -> performReceiveLocked -> ActivityThread.scheduleRegisteredReceiver异步处理广播

deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i);

}

......

}

......

// mPendingBroadcast需要在应用启动完成后进行处理。在AMS.attachApplicationLocked中会调用sendPendingBroadcastsLocked进行分发

if (mPendingBroadcast != null) {

boolean isDead;

synchronized (mService.mPidsSelfLocked) {

ProcessRecord proc = mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.pid);

isDead = proc == null || proc.crashing;

}

// 如果应用启动,会在AMS调用函数处理静态广播,所以这里直接return

if (!isDead) {

// It's still alive, so keep waiting

return;

} else {

Slog.w(TAG, "pending app ["

+ mQueueName + "]" + mPendingBroadcast.curApp

+ " died before responding to broadcast");

mPendingBroadcast.state = BroadcastRecord.IDLE;

mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;

mPendingBroadcast = null;

}

}

boolean looped = false;

// 这里的do-while只会从mOrderedBroadcasts中取出第一个BroadcastRecord进行后续的处理!

do {

......

r = mOrderedBroadcasts.get(0);

boolean forceReceive = false;

// 后面主要是广播分发超时后的处理,以及广播分发给所有receiver后的处理,这里暂时跳过

......

} while (r == null);

// Get the next receiver...

int recIdx = r.nextReceiver++; // 取出一个Receiver

......

final Object nextReceiver = r.receivers.get(recIdx);

// 对动态注册receiver的处理,这里应该是分发有序广播

if (nextReceiver instanceof BroadcastFilter) {

// 通过deliverToRegisteredReceiverLocked调用ActivityThread.scheduleRegisteredReceiver处理广播,暂时跳过

......

}

// 开始对静态注册receiver的处理!!!

ResolveInfo info = (ResolveInfo)nextReceiver;

......

// 如果应用已经启动,则直接分发广播给该应用,并返回

if (app != null && app.thread != null && !app.killed) {

try {

app.addPackage(info.activityInfo.packageName,

info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);

// 通过processCurBroadcastLocked -> ActivityThread.scheduleReceiver -> receiver.onReceive处理当前广播

processCurBroadcastLocked(r, app);

return;

} catch (RemoteException e) {

Slog.w(TAG, "Exception when sending broadcast to "

+ r.curComponent, e);

} catch (RuntimeException e) {

Slog.wtf(TAG, "Failed sending broadcast to "

+ r.curComponent + " with " + r.intent, e);

......

return;

}

}

......

// 如果应用未启动,则在这里启动应用进程,广播将在AMS启动完成后被调用处理

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) {

Slog.w(TAG, "Unable to launch app "

+ info.activityInfo.applicationInfo.packageName + "/"

+ info.activityInfo.applicationInfo.uid + " for broadcast "

+ r.intent + ": process is bad");

......

return;

}

// 将BroadcastRecord赋值为mPendingBroadcast,等待应用启动完成后处理

mPendingBroadcast = r;

mPendingBroadcastRecvIndex = recIdx;

}

}

概括起来,processNextBroadcast 在分发广播中主要做了这几件事情:

1、先处理并行广播队列(无序广播 + 动态注册的 receiver),遍历 mParallelBroadcasts 所有的 BroadcastRecord 以及 Receiver,异步发送广播。

2、再处理串行广播,从 mOrderedBroadcasts 中取出一个 BroadcastRecord,一次只处理其中的一个 Receiver

4、如果当前 receiver 为动态注册,调用 deliverToRegisteredReceiverLocked 进行处理

5、如果当前 receiver 为静态注册,分为两种情况:

5.1、应用已启动:调用 processCurBroadcastLocked 进行处理

5.2、应用未启动:调用 AMS.startProcessLocked 启动应用,将要处理的 BroadcastRecord 保存到 mPendingBroadcast,等待应用启动完成后在 AMS.attachApplicationLocked 中调用 BroadcastQueue.sendPendingBroadcastsLocked 进行分发处理。

其实分析到这里大概就能知道 B 应用在开机后启动慢的问题所在了。

静态注册的广播需要串行处理,如果应用未启动,需要等待应用启动完成后才能处理广播,然后才能继续分发给下一个接收器。

系统刚启动时,大量的应用通过静态注册被开机广播拉起来,再加上一些系统状态信息的广播,必然导致客户应用发出的广播需要在队列中等待很长的时间,才能分发并处理。

解决方案

由于项目比较急,并且是临时固件,可以随便搞(bushi)。客户的应用我们没法改,为了尽快解决客户的问题,我在 BroadcastQueue 中做了改动,对指定的应用进行了特殊处理,当 A 应用发送启动 B 应用的广播时,直接插入到串行广播 mOrderedBroadcasts 的最前端。

这样虽然还是串行处理,但由于客户应用发送的广播被插入到穿行广播队列最前端,还是能比较快地进行分发处理。虽然不是最优解,但是也可以达到短时间内处理广播并启动 B 应用的效果。

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

public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {

// 判断客户要启动的应用和对应的receiver,如果匹配,则插入到串行广播的最前面

if (r.targetComp != null &&

"com.example.test".equals(r.targetComp.getPackageName()) &&

"com.xxx.BroadcastReceiver".equals(r.targetComp.getClassName())) {

mOrderedBroadcasts.add(0, r);

} else {

mOrderedBroadcasts.add(r);

}

enqueueBroadcastHelper(r);

}

思考

虽然上面的方案能够解决客户的问题,但是这样的方法并不常规,后续还是有可能会遇到类似的问题,还是需要思考更好、更合理的处理或解决方案。

系统层面

1、尽量减少系统应用接收开机广播,尽量少地通过开机广播拉起应用。 没启动一个应用都需要至少几百毫秒的耗时,加上系统的一些其他广播,使得第三方应用发出的广播都需要等很久。

通过开机广播拉起应用的方案感觉在我们系统中有点被滥用了,后续还是得好好排查下这块的问题。

2、接收开机广播的 Receiver,在 onReceive 中一定不要做耗时操作。 由于静态注册是串行处理,如果在 onReceive 中做了耗时操作,会影响其他广播的处理,这块一定要注意!

应用层面

1、A 应用不要等待 B 应用启动 首先需要考虑的是,静态注册注定了无法保证及时接收到广播,所以不应该由于 B 应用启动慢而导致 A 应用原来的逻辑被阻塞,这部分逻辑一定要做好异步处理。

2、自定义广播和开机广播结合 首次启动可以通过 A 应用发出广播启动应用 B,后续则可以通过开机广播并设定优先级来启动应用 B。这样 B 应用的启动速度也会有明显提升。

3、发送广播时设置 FLAG_RECEIVER_FOREGROUND 的 flag 应用发送广播时可以通过设置 FLAG_RECEIVER_FOREGROUND 的 flag,换一条赛道,跑到广播队列的前面

Intent intent = new Intent("com.example.broadcast");

intent.setComponent(new ComponentName("com.example.demo1", "com.example.demo1.MyBroadcastReceiver"));

intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);

sendBroadcast(intent);

总结

最后简单做下总结:

AMS中维护着两个BroadcastQueue,分别为 mFgBroadcastQueue 和 mBgBroadcastQueue。如果广播没有携带 FLAG_RECEIVER_FOREGROUND 的 flag,则默认到 mBgBroadcastQueue 进行处理。

而 BroadcastQueue 中又维护着两个 BroadcastRecord 队列,分别为 mParallelBroadcasts 和 mOrderedBroadcasts。

mParallelBroadcasts 中存放的是无序广播,并且其对应的接收器都是动态注册的,当 processNextBroadcast 被调用时会遍历 mParallelBroadcasts 处理完所有的广播,且不会阻塞,所以称之为并行广播。

mOrderedBroadcasts 中存放的则是静态注册Receiver,以及动态注册的有序广播。processNextBroadcast 被调用时只会处理 BroadcastRecord 的其中一个 receiver,并且如果是静态注册且应用未启动,还需要等待应用启动完成后才能处理当前的广播,所以称之为串行广播。

以前对广播的理解只是停留在应用阶段,没有专门去看过这一块的源码。这次借着解决这个问题的机会,简单研究了一下发送广播的流程,还是有不少收获的。有了对这些知识点的理解,无论是处理相关的问题,还是对于应用开发,都有明显的帮助!

相关推荐

海洋科普︱外表萌态可掬,实则顶级杀手:虎鲸的双面“鲸”生
荷兰队|荷兰国家队
365沙巴体育入口

荷兰队|荷兰国家队

📅 07-05 👁️ 9496
自拍图片
365速发国际平台app下载

自拍图片

📅 10-18 👁️ 3526
女人出轨后如何挽回老公:7个有效策略修复婚姻关系
365速发国际平台app下载

女人出轨后如何挽回老公:7个有效策略修复婚姻关系

📅 08-26 👁️ 3967
剃须膏应该怎么使用 刮胡膏的正确使用方法?
365沙巴体育入口

剃须膏应该怎么使用 刮胡膏的正确使用方法?

📅 07-25 👁️ 6654
已结清的网贷记录多久消失?网贷记录怎么消除?
365沙巴体育入口

已结清的网贷记录多久消失?网贷记录怎么消除?

📅 07-20 👁️ 7359
崩字的成语有哪些
365速发国际平台app下载

崩字的成语有哪些

📅 09-16 👁️ 3280
正在阅读:迅雷自带的游戏盒子怎么干净卸载?迅雷自带的游戏盒子怎么干净卸载?
明日方舟空爆技能怎么样 明日方舟空爆值得练吗角色搭配
365彩票下载1.0.0老版本

明日方舟空爆技能怎么样 明日方舟空爆值得练吗角色搭配

📅 08-21 👁️ 7457