前言
近来在对车机智能仓Android系统进行版本性能优化,在优化的过程中发现一个问题: 在系统刚刚启动的时候,很多通过监听系统开机广播【ACTION_BOOT_COMPLETED】进行自启动的应用,需要40秒左右才能收到开机广播并成功进行启动。
一、测试
1.1 开机立即发送测试广播
为了排查这个问题的原因,本人使用adb命令来模拟发送广播,当系统刚刚开机的时候,执行如下adb命令
adb shell am broadcast -a act=android.intent.action.Test Broadcast completed: result=0
最终发现需要等待40秒左右,才能收到回调。
1.2 开机等待一段时候以后再发送测试广播
进一步测试发现,当系统开机时间达到55秒以后,再次执行同样的adb命令:
发现只需要等待1秒即可收到回调。
1.3 分析测试结果
通过上面的两种测试结果可以很明显看出,这个问题是由于系统启动阶段做了什么事情,才导致广播接收慢的。 要处理这个问题,必须研究下 Android 发送广播的流程,看看到底哪个环节出了问题。另外想要实现软件开机自启动,基本都是通过静态注册的方式来配置广播接收者的。
本系列前篇几篇文章已经很详细的分析过了广播的注册、注销、发送流程;本篇文章我们忽略无序动态广播,简单来梳理一下静态注册的广播接收者广播发送接受的流程。
二、广播的发送流程
/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();
}
}
发送广播一般都是通过调用sendBroadcast 方法,该方法最终调用到 AMS 里面的 broadcastIntent 方法。
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 = verifyBroadcastLocked(intent);
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;
}
}
broadcastIntent又会调用broadcastIntentLocked方法,所以我们直接对broadcastIntentLocked方法的流程进行分析。
/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) {
......
List receivers = null;
List<BroadcastFilter> registeredReceivers = null;
if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)== 0) {
receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
}
if (intent.getComponent() == null) {
......
}
......
int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
if (!ordered && NR > 0) {
......
}
......
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 {
queue.enqueueOrderedBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
} else {
......
}
return ActivityManager.BROADCAST_SUCCESS;
}
由于这个函数代码非常多,这里我们忽略掉了动态注册的处理以及其他的一些流程,只针对静态注册的 receiver 进行分析; 可以看到 broadcastIntentLocked 中,针对静态注册的 receiver 主要做了 2 件事情:
- collectReceiverComponents 获取到静态注册的 receiver,放到 receivers 队列中;
- 构造 BroadcastRecord,调用 BroadcastQueue.enqueueOrderedBroadcastLocked加入到广播队列中,再调用BroadcastQueue 等待分发。
三、广播的分发流程
广播分发的主要代码在 BroadcastQueue中实现。
/frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
mOrderedBroadcasts.add(r);
enqueueBroadcastHelper(r);
}
可以看到 enqueueOrderedBroadcastLocked 只是简单地把 BroadcastRecord 加入到 mOrderedBroadcasts 队列中。
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;
}
}
}
scheduleBroadcastsLocked 也只是发送了一个 message 给 Handler,Handler最终调用 processNextBroadcast 进行真正的分发流程。
final void processNextBroadcast(boolean fromMsg) {
synchronized(mService) {
BroadcastRecord r;
......
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);
}
......
}
......
if (mPendingBroadcast != null) {
boolean isDead;
synchronized (mService.mPidsSelfLocked) {
ProcessRecord proc = mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.pid);
isDead = proc == null || proc.crashing;
}
if (!isDead) {
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 {
......
r = mOrderedBroadcasts.get(0);
boolean forceReceive = false;
......
} while (r == null);
int recIdx = r.nextReceiver++;
......
final Object nextReceiver = r.receivers.get(recIdx);
if (nextReceiver instanceof BroadcastFilter) {
......
}
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(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;
}
}
......
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;
}
mPendingBroadcast = r;
mPendingBroadcastRecvIndex = recIdx;
}
}
processNextBroadcast 是 BroadcastQueue 中分发广播的核心方法,对并行广播和串行广播进行处理。 由于代码太多,本篇博客主要关注静态注册广播的发送流程,所以删掉了大量的代码,只留下了和静态注册广播比较重要的部分。
概括起来,processNextBroadcast 在分发广播中主要做了这几件事情:
1、先处理并行广播队列(无序广播 + 动态注册的 receiver),遍历 mParallelBroadcasts 所有的 BroadcastRecord 以及 Receiver,异步发送广播。
2、再处理串行广播,从 mOrderedBroadcasts 中取出一个 BroadcastRecord,一次只处理其中的一个 Receiver
3、如果当前 receiver 为动态注册,调用 deliverToRegisteredReceiverLocked 进行处理
4、如果当前 receiver 为静态注册,分为两种情况:
4.1、应用已启动:调用 processCurBroadcastLocked 进行处理
4.2、应用未启动:调用 AMS.startProcessLocked 启动应用,将要处理的 BroadcastRecord 保存到 mPendingBroadcast,等待应用启动完成后在 AMS.attachApplicationLocked 中调用 BroadcastQueue.sendPendingBroadcastsLocked,sendPendingBroadcastsLocked方法最终又会调用processCurBroadcastLocked进行处理
四、开机广播接受速度缓慢的原因
其实分析到这里大概就能知道 应用在开机后启动慢的问题所在了。
- 静态注册的广播需要串行处理,如果应用未启动,需要等待应用启动完成后才能处理广播,然后才能继续分发给下一个接收器。
- 系统刚启动时,大量的应用通过静态注册被开机广播拉起来,再加上一些系统状态信息的广播,必然导致客户应用发出的广播需要在队列中等待很长的时间,才能分发并处理。
五、解决方案
1、尽量减少系统应用接收开机广播,尽量少地通过开机广播拉起应用。每启动一个应用都需要至少几百毫秒的耗时,加上系统的一些其他广播,使得第三方应用发出的广播都需要等很久。
2、接收开机广播的 Receiver,在 onReceive 中一定不要做耗时操作。由于静态注册是串行处理,如果在 onReceive 中做了耗时操作,会影响其他广播的处理,这块一定要注意!
3、应用内减少广播的使用,如果一个应用在系统启动的时候刚被唤醒,又发送了其他广播,这些广播也会按照优先级被插在广播队列中,这样同样会加剧开机广播耗时时间。
|