本文基于android9.0源码分析。
android出现经常与四大组件有紧密联系,主要有四种情况1、activity界面输入事件超时 2、broadcastReceiver发送以及接收事件超时 3、service处理事务超时 4、contentProvider处理事务超时。
在上一篇文章《anr原因一:inputDispatcher》中对输入事件的anr源码进行了分析,本文则对broadcastReceiver产生的anr进行分析。
一、发送广播
先从发送广播说起吧,执行sendBroadcast(@RequiresPermission Intent intent)的时候会走到抽象类Context.java中,在《startService过程》一文有提到四大组件的Context最终实现是ContextImpl.java。
1.1 sendBroadcast
ContextImpl.java中的sendBroadcast方法调用了ActivityManagerService.java(后面简称AWS)的broadcastIntent方法。
//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();
}
}
1.2 broadcastIntent
AWS的broadcastIntent方法在加锁情况下调用了broadcastIntentLocked方法。
//ActivityManagerService.java
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;
}
}
}
1.3 broadcastIntentLocked
broadcastIntentLocked方法里面代码较多,实际上就是log打印、判断广播接收进程是否停止、权限检查等行为,然后根据广播类型的不同将广播记录BroadcastRecord分别添加添加到无序广播集合mParallelBroadcasts和有序广播集合mOrderedBroadcasts中,并通过hanlder发送消息执行后面的操作。
//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){
……
// Note: We assume resultTo is null for non-ordered broadcasts.
if (!replaced) {
queue.enqueueParallelBroadcastLocked(r);//添加无序广播
queue.scheduleBroadcastsLocked(); //执行广播
}
……
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);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r
+ ": prev had " + queue.mOrderedBroadcasts.size());
if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
"Enqueueing broadcast " + r.intent.getAction());
final BroadcastRecord oldRecord =
replacePending ? queue.replaceOrderedBroadcastLocked(r) : null;
if (oldRecord != null) {
// Replaced, fire the result-to receiver.
if (oldRecord.resultTo != null) {
final BroadcastQueue oldQueue = broadcastQueueForIntent(oldRecord.intent);
try {
oldQueue.performReceiveLocked(oldRecord.callerApp, oldRecord.resultTo,
oldRecord.intent,
Activity.RESULT_CANCELED, null, null,
false, false, oldRecord.userId);
} catch (RemoteException e) {
Slog.w(TAG, "Failure ["
+ queue.mQueueName + "] sending broadcast result of "
+ intent, e);
}
}
} else {
queue.enqueueOrderedBroadcastLocked(r); //添加有序广播
queue.scheduleBroadcastsLocked();//执行广播分发
}
}
……
return ActivityManager.BROADCAST_SUCCESS;
}
1.4 processNextBroadcast
BroadcastQueue的scheduleBroadcastsLocked方法中发送了BROADCAST_INTENT_MSG消息,被handler接收后调用了processNextBroadcast方法。processNextBroadcast方法再同步锁的情况下调用了processNextBroadcastLocked方法
//BroadcastQueue.java
final void processNextBroadcast(boolean fromMsg) {
synchronized (mService) {
processNextBroadcastLocked(fromMsg, false);
}
}
1.5 processNextBroadcastLocked
processNextBroadcastLocked方法中先对并行广播mParallelBroadcasts(也称普通广播,区别于有序广播)通过deliverToRegisteredReceiverLocked方法进行分发处理,这里需要注意无序广播直接发送不关注结果。然后处理有序广播,如果之前有个有序广播经历了很长时间(now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))还没分发完成,那么就调用broadcastTimeoutLocked方法并将强制接收标志置为true,在broadcastTimeoutLocked该方法中会弹出anr框。如果本次广播已完成,也就是说没有其他接收者了(r.receivers == null || r.nextReceiver >= numReceivers|| r.resultAbort || forceReceive),那么就将当前广播从集合中去除(mOrderedBroadcasts.remove(0)),开始执行下一轮的while循环;如果本次广播还没完成,则跳出while循环,取出当前接收者(int recIdx = r.nextReceiver++),并且通过setBroadcastTimeoutLocked(timeoutTime)设置超时事件,然后开始执行广播分发。那么如果在设置的超时时间后还没处理完广播的分发,超时事件就会被激活。
mTimeoutPeriod的值是在BroadcastQueue对象构造时传进来的,在AMS中可以看到有两种广播序列:前台广播序列(mFgBroadcastQueue)和后台广播序列(mBgBroadcastQueue),前台广播序列具有前台优先级,低延迟,可以通过发送广播时设置Intent.FLAG_RECEIVER_FOREGROUND参数来配置。在AWS中可以看到前台广播的超时事件时10s,后台广播(也就是普通广播)的超时事件时1min。
//ActivityManagerService.java
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000;
static final int BROADCAST_BG_TIMEOUT = 60*1000;
//BroadcastQueue.java
……
BroadcastQueue(ActivityManagerService service, Handler handler,
String name, long timeoutPeriod, boolean allowDelayBehindServices) {
mService = service;
mHandler = new BroadcastHandler(handler.getLooper());
mQueueName = name;
mTimeoutPeriod = timeoutPeriod;//超时事件设置
mDelayBehindServices = allowDelayBehindServices;
}
/**
* If true, we can delay broadcasts while waiting services to finish in the previous
* receiver's process.
*/
final boolean mDelayBehindServices;
……
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
BroadcastRecord r;
……
// First, deliver any non-serialized broadcasts right away.首先立即分发并行广播
while (mParallelBroadcasts.size() > 0) { //并行广播数量大于0
r = mParallelBroadcasts.remove(0); //逐步取出并行广播并从结合中去除
r.dispatchTime = SystemClock.uptimeMillis();
r.dispatchClockTime = System.currentTimeMillis();
……
final int N = r.receivers.size();
if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
+ mQueueName + "] " + r);
for (int i=0; i<N; i++) {
Object target = r.receivers.get(i);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Delivering non-ordered on [" + mQueueName + "] to registered "
+ target + ": " + r);
deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i); //分发消息
}
……
}
// Now take care of the next serialized one...然后处理有序广播
……
do {
if (mOrderedBroadcasts.size() == 0) { 没有有序广播的话processNextBroadcastLocked方法就可以结束了
// No more broadcasts pending, so all done!
mService.scheduleAppGcsLocked();
if (looped) {
// If we had finished the last ordered broadcast, then
// make sure all processes have correct oom and sched
// adjustments.
mService.updateOomAdjLocked();
}
return;
}
r = mOrderedBroadcasts.get(0); //逐步取出有序广播
boolean forceReceive = false;
// Ensure that even if something goes awry with the timeout
// detection, we catch "hung" broadcasts here, discard them,
// and continue to make progress.
//
// This is only done if the system is ready so that PRE_BOOT_COMPLETED
// receivers don't get executed with timeouts. They're intended for
// one time heavy lifting after system upgrades and can take
// significant amounts of time.
int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
if (mService.mProcessesReady && r.dispatchTime > 0) {
long now = SystemClock.uptimeMillis();
if ((numReceivers > 0) &&
(now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
Slog.w(TAG, "Hung broadcast ["
+ mQueueName + "] discarded after timeout failure:"
+ " now=" + now
+ " dispatchTime=" + r.dispatchTime
+ " startTime=" + r.receiverTime
+ " intent=" + r.intent
+ " numReceivers=" + numReceivers
+ " nextReceiver=" + r.nextReceiver
+ " state=" + r.state);
broadcastTimeoutLocked(false); // forcibly finish this broadcast 设置超时事件
forceReceive = true;
r.state = BroadcastRecord.IDLE;
}
}
if (r.state != BroadcastRecord.IDLE) {
if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
"processNextBroadcast("
+ mQueueName + ") called when not idle (state="
+ r.state + ")");
return;
}
if (r.receivers == null || r.nextReceiver >= numReceivers
|| r.resultAbort || forceReceive) { //如果广播已被分发成功
// No more receivers for this broadcast! Send the final
// result if requested...
if (r.resultTo != null) {
try {
if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
"Finishing broadcast [" + mQueueName + "] "
+ r.intent.getAction() + " app=" + r.callerApp);
performReceiveLocked(r.callerApp, r.resultTo,
new Intent(r.intent), r.resultCode,
r.resultData, r.resultExtras, false, false, r.userId);
r.resultTo = null;
} catch (RemoteException e) {
r.resultTo = null;
Slog.w(TAG, "Failure ["
+ mQueueName + "] sending broadcast result of "
+ r.intent, e);
}
}
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
cancelBroadcastTimeoutLocked(); //广播已被分发成功,取消超时事件
……
mOrderedBroadcasts.remove(0); //去掉已处理的有序广播
r = null;
looped = true;
continue;
}
} while (r == null);//当前广播已处理完全,继续循环执行下一个广播的分发
// Get the next receiver...处理下一个广播
int recIdx = r.nextReceiver++;
……
if (! mPendingBroadcastTimeoutMessage) { //如果之前的广播都分发完了,也就是说mPendingBroadcastTimeoutMessage为false了
long timeoutTime = r.receiverTime + mTimeoutPeriod;
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Submitting BROADCAST_TIMEOUT_MSG ["
+ mQueueName + "] for " + r + " at " + timeoutTime);
setBroadcastTimeoutLocked(timeoutTime); //重新设置一个超时事件
}
……//开始分发事件
}
1.6 broadcastTimeoutLocked
1.5中说到在执行有序广播的分发时,如果发现有个有序广播经历了很长时间(now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))还没分发完成,那么他就是不可饶恕了,直接anr把。跟进方法中可以看到最后执行了mHandler.post(new AppNotResponding(app, anrMessage))。anr后面逻辑我们在在上一篇文章《anr原因一:inputDispatcher》中已经讲过。
//BroadcastQueue.java
final void broadcastTimeoutLocked(boolean fromMsg) {
……
long now = SystemClock.uptimeMillis();
BroadcastRecord r = mOrderedBroadcasts.get(0);
if (fromMsg) {
if (!mService.mProcessesReady) { //系统还未准备好则返回
// Only process broadcast timeouts if the system is ready. That way
// PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended
// to do heavy lifting for system up.
return;
}
long timeoutTime = r.receiverTime + mTimeoutPeriod;
if (timeoutTime > now) { //如果当前时间还未超过超时时间则设置超时时间
// We can observe premature timeouts because we do not cancel and reset the
// broadcast timeout message after each receiver finishes. Instead, we set up
// an initial timeout then kick it down the road a little further as needed
// when it expires.
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Premature timeout ["
+ mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
+ timeoutTime);
setBroadcastTimeoutLocked(timeoutTime);//设置超时时间,并返回,等待下次事件
return;
}
}
……
if (!debugging && anrMessage != null) {
// Post the ANR to the handler since we do not want to process ANRs while
// potentially holding our lock.
mHandler.post(new AppNotResponding(app, anrMessage));//已经超时则弹出anr框
}
}
1.7 setBroadcastTimeoutLocked
1.5中开始分发有序广播时会通过setBroadcastTimeoutLocked方法设定一个超时事件,该方法内部其实就是超时一个延迟执行的handler,到达超时时间后handler开始执行,还是调用超时方法broadcastTimeoutLocked,和之前processNextBroadcastLocked方法里面调用不同的是方法的fromMsg参数为true,代表从message中调用的。fromMsg参数在setBroadcastTimeoutLocked方法中的作用只是确定一下当前时间是否已经过了超时时间,如果没有则返回不弹出anr。
//BroadcastQueue.java
final void setBroadcastTimeoutLocked(long timeoutTime) {
if (! mPendingBroadcastTimeoutMessage) {
Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);//设置超时handler
mHandler.sendMessageAtTime(msg, timeoutTime);//延迟指定事件执行
mPendingBroadcastTimeoutMessage = true;
}
}
……
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_TIMEOUT_MSG: {
synchronized (mService) {
broadcastTimeoutLocked(true);//执行超时方法
}
} break;
}
}
}
1.8 onReceive
根据上面的源码流程看到貌似只有有序广播才会发生anr,普通的无序广播不会发生anr,确实是这样吗?上面说的主要是广播的发送环节,那么广播的接收环节呢?我们知道广播接收者最终都需要在onReceive(Context context, Intent intent)方法中收到广播到来的吧通知,onReceive方法执行在主线程,那么如果在onReceive方法中不开子线程就去执行耗时操作,可能会导致inputDispatcher类型的anr,源码中也给出了详细说明。
/**
* This method is called when the BroadcastReceiver is receiving an Intent
* broadcast. During this time you can use the other methods on
* BroadcastReceiver to view/modify the current result values. This method
* is always called within the main thread of its process, unless you
* explicitly asked for it to be scheduled on a different thread using
* {@link android.content.Context#registerReceiver(BroadcastReceiver,
* IntentFilter, String, android.os.Handler)}. When it runs on the main
* thread you should
* never perform long-running operations in it (there is a timeout of
* 10 seconds that the system allows before considering the receiver to
* be blocked and a candidate to be killed). You cannot launch a popup dialog
* in your implementation of onReceive().
*
* <p><b>If this BroadcastReceiver was launched through a <receiver> tag,
* then the object is no longer alive after returning from this
* function.</b> This means you should not perform any operations that
* return a result to you asynchronously. If you need to perform any follow up
* background work, schedule a {@link android.app.job.JobService} with
* {@link android.app.job.JobScheduler}.
*
* If you wish to interact with a service that is already running and previously
* bound using {@link android.content.Context#bindService(Intent, ServiceConnection, int) bindService()},
* you can use {@link #peekService}.
*
* <p>The Intent filters used in {@link android.content.Context#registerReceiver}
* and in application manifests are <em>not</em> guaranteed to be exclusive. They
* are hints to the operating system about how to find suitable recipients. It is
* possible for senders to force delivery to specific recipients, bypassing filter
* resolution. For this reason, {@link #onReceive(Context, Intent) onReceive()}
* implementations should respond only to known actions, ignoring any unexpected
* Intents that they may receive.
*
* @param context The Context in which the receiver is running.
* @param intent The Intent being received.
*/
public abstract void onReceive(Context context, Intent intent);
二、总结
前台广播的超时时间是10s,后台广播的超时时间是1min,有序广播可能会在广播分发时产生anr,无序广播不会在广播分发时产生anr。有序广播和无序广播接收时,如果不开子线程执行耗时事件都可能产生anr。