anr原因二:BroadcastQueue

本文基于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 &lt;receiver&gt; 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。

 

 

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值