ANR原理及案例分析

目录

ANR概念

发生ANR原因

ANR类型

ANR触发机制

ServiceTimeout ANR (服务启动超时)

ANR发生原理

前台服务与后台服务

ANR 触发流程

BroadcastQueueTimeout ANR (广播接收超时)

ANR发生原理​编辑

前台广播与后台广播

广播的类型

ANR 触发流程

广播启动入口

ANR 触发任务

ContentProviderTimeout ANR (内容提供者发布超时)

InputDispatchingTimeout ANR(输入事件分发超时)

窗口处理超时

InputDispatcher 分发事件

InputDispatcher 移除 waitQueue 队列事件

InputDispatcher 对 ANR 的判定

ANR 触发原理

ANR问题分析思路

小结


ANR (Application Not responding),是指应用程序未响应,Android 系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间未能得到有效响应或者响应时间过长,都会造成 ANR

ANR问题即使应用响应超时问题,它根据发生的原因不同,主要分为KeyDispatchTimeout、BroadcastTimeout、ContentProviderTimeout、ServiceTimeout四类。

  1. ANR概念

ANR是Application not response的简写,即应用程序未响应。ANR问题是我们在进行应用开发时经常遇到的问题。当应用程序在UI线程阻塞太长时间,就会弹出系统弹框,询问我们继续等待还是关闭应用程序,此时发生了ANR。

  1. 发生ANR原因

通常来说发生ANR原因主要有以下几点:

  • 主线程频繁进行耗时IO操作,如读取数据、进行网络访问等;

  • 多线程操作导致死锁,主线程被block;

  • 进行binder通讯,binder连接数达到上限无法和对端通讯,或者对端处理超时,导致binder通讯超时;

  • 系统服务WatchDog出现ANR

  • 系统资源耗尽,如CPU、IO。

  1. ANR类型

ANR根据产生的原因不同,主要被分为四种:

  • InputDispatchTimeout

用户输入事件在5秒内没有处理完,后面再一次发生输入事件时产生。

关键日志:“Input event dispatching timed out”。

  • BroadcastTimeout

广播事件处理超时产生的ANR问题。前台广播在10秒内没有处理完成或者后台广播在60秒内没有处理完成,则产生ANR。

关键日志:“Timeout of broadcast BroadcastRecord”。

  • ServiceTimeout

服务处理超时产生的ANR问题。前台服务在20秒内没有处理完成或者后台服务在200秒内没有处理完成则发生ANR。

关键日志:“Timeout executing sevice”。

  • ContentProviderTimeout

ContentProvider在10秒内没有处理完成发生ANR。

关键日志:“timeout publishing content providers”。

  1. ANR触发机制

ANR是安卓系统监控应用程序响应是否及时的监控机制。从实现的角度,流程大概有三部:

a、应用进程进行某项操作时,AMS启动监控倒计时;

b、应用进程操作完成,停止到计时,此时安全;

c、倒计时完,说明操作未完成,超时,触发ANR弹框。

  1. ServiceTimeout ANR (服务启动超时)

  1. ANR发生原理
  1. 应用进程通过Binder向系统进程发起请求服务

  2. 系统进程收到请求后,向AMS发送消息开启超时响应倒计时;

  3. 系统进程通过Binder通讯向目标服务进程发起请求;

  4. 服务进程收到消息后,转交给自己的业务程序;

  5. 服务进程进行事务处理,执行启动服务的声明周期;

  6. 服务启动完成,向系统进程发消息,告知处理完成;

  7. 系统进程向AMS发消息,取消超时响应倒计时;

从第2步开始,到第7步结束,这期间如果超过倒计时时间,则在倒计时结束时,AMS就会触发ANR弹框上来,就发生了ANR。这个倒计时时间,就是上面说的前台服务20s,后台服务200s。

  1. 前台服务与后台服务

前台服务和后台服务发生ANR时间是不同的。分别是20秒和200秒。

  • 在startService时,根据调用进程的所属的进程调度组决定被启动的服务是前台服务还是后台服务。当放弃放进程不属于后台进程组ProcessList.SCHED_GROUP_BACKGROUND,则认为是前台服务,否则为后台服务。前后台服务被标记在ServiceRecord的createdFromFg成员变量中。

  • 进程是否属于SCHED_GROUP_BACKGROUND主要是通过AMS的oom_adj进行判断的。AMS将进程调度组分为前台进程、可见进程、服务进程、后台进程和空进程。

  • oom_adj值小于0的都为系统进程,优先级是最高的;值为0的是前台进程;值在100~200属于可见进程;值大于200对用户来说基本是无感知的,属于后台进程

  1. ANR 触发流程

服务启动超时这一 ANR 触发流程如图所示。

Service 的相关逻辑都封装在 AMS 的成员对象 ActiveServices 里,我们只需要了解 Service 启动的入口函数 realStartServiceLocked 方法,便能了解 “Service Timeout ” 触发的流程,该方法的简化代码如下:

private void realStartServiceLocked(ServiceRecord r, ProcessRecord app,
        IApplicationThread thread, int pid, UidRecord uidRecord, boolean execInFg,
        boolean enqueueOomAdj) throws RemoteException {
    ……
    //1. 往handler发送SERVICE_TIMEOUT_MSG延时任务,即ANR触发任务
    bumpServiceExecutingLocked(r, execInFg, "create",
            OOM_ADJ_REASON_NONE);

    boolean created = false;
    try {
        ……
        //2. 同步执行目标service的onCreate方法
        thread.scheduleCreateService(r, r.serviceInfo,
                null
                app.mState.getReportedProcState());
        r.postNotification(false);
        created = true;
    } catch (DeadObjectException e) {
        ……
        throw e;
    } finally {
        if (!created) {
            ……
            //3. 移除SERVICE_TIMEOUT_MSG延时任务
            serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
                    OOM_ADJ_REASON_STOP_SERVICE);
            ……
        }
    }

    ……
}

该方法中主要流程的解释如下:

1)首先调用 bumpServiceExecutinLoced 方法,该方法会通过 Handler 发送一个 SERVICE_TIMEOUT_MSG 消息,也就是 ANR 触发的消息,该消息的延时时间取决于服务是前台还是后台,前台服务的超时时间是 20 秒,后台服务的超时时间是 200 秒。

2)接着通过 Binder 机制通知目标服务所在的进程,让其执行服务的创建或绑定操作,目标服务所在的进程会通过 ActivityThread 类的 scheduleCreateService 来调度服务的 onCreate 生命周期

3)最后,当服务完成创建或绑定操作后,执行 serviceDoneExecutingLocked 方法,该方法会移除之前发送的 SERVICE_TIMEOUT_MSG 消息,表示服务已经正常启动或绑定,不会触发 ANR 。

如果 SERVICE_TIMEOUT_MSG 这个消息没有在规定的时间内被移除则会执行 serviceTimeout 方法来触发 ANR,ANR 的触发也是调用 AMS 的 appNotResponding 方法来进行的。

  1. BroadcastQueueTimeout ANR (广播接收超时)

  1. ANR发生原理

广播事件处理超时响应机制主要有这样几个过程:

  1. 应用或系统广播产生并发送给AMS;

  2. AMS将广播投递到广播队列后,开始启动超时检测;

  3. 广播通过Binder通讯机制被投递到目标应用进程;

  4. 应用进程将广播加入到自己的消息队列等待处理;

  5. 应用进程从消息队列取到广播消息,并调用相应的接收器进行处理;

  6. 广播消息处理完成,通知AMS。

这个过程如果没有发生超时,AMS取消超时检测,否则在超时时间达到触发ANR弹框。

  1. 前台广播与后台广播

根据广播发送中的intent是否带有FLAG_RECEIVER_FOREGROUND标记将广播分为前台广播和后台广播。前台广播超时时间是10s,后台广播超时时间是60s。默认情况下是后台广播,是不带这个标志的。

另外,只有串行处理的广播才会发生ANR。并行广播系统是通过一个循环一次性发送给所有接受者,不存在超时检测。

  1. 广播的类型

主要有三类广播,分别如下:

  • 普通广播(Normal Broadcast):普通广播是异步的,发送者不关心接收者的处理结果,也不会等待接收者的响应。通过 sendBroadcast 方法发送的广播即为普通广播。

  • 有序广播(Ordered Broadcast):有序广播是同步的,发送者需要等待接收者的处理结果,通过 sendOrderedBroadcast 方法即可发送有序广播。

  • 粘性广播(Sticky Broadcast):这种广播是一种特殊的普通广播,它可以在发送后一直保留在系统中,这样后来注册的接收者也可以收到之前发送的广播,通过 sendStickyBroadcast 方法可发送粘性广播。

上面的三种类型的广播中,只有有序广播才会导致 ANR 的,因为该广播是同步的,如果接收者在广播接收函数 onReceive 函数中执行耗时超过 10 秒,系统便会触发 "BroadcastReceiver Timeout" 类型的 ANR。

  1. ANR 触发流程

该 ANR 触发流程主要有这几步:

  1. ActivityManagerService 通过 processNextBroadcast 方法启动广播

  2. 启动流程中,如果是有序广播,则会启动触发 “BroadcastReceiver Timeout” 这一 ANR 的延时任务,

  3. 阻塞执行 performReceiverLocked 方法,该方法会触发应用中的 Receiver 回调 onReceive 函数,当 onReciver 函数执行完成后,ActivityManagerService 才会继续执行后续流程,移除前面启动的 ANR 触发的延时任务。

通过流程可以知道,如果在规定的时间内,ANR 触发的延时任务没被移除,便会触发 ANR,上述流程如图所示,我们接着通过代码实现来更深入的了解该 ANR 的触发原理。

广播启动入口

ActivityManagerService(AMS) 中的 BroadcastQueueImpl 成员对象是专门用来处理和广播相关的对象,当 AMS 需要启动一个广播时,会通知 BroadcastQueueImpl 中的 Handler 触发 processNextBroadcast 方法来启动广播,代码如下:

// BroadcastQueueImpl中的 Handler
private final class BroadcastHandler extends Handler {
    public BroadcastHandler(Looper looper) {
        super(looper, null);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case BROADCAST_INTENT_MSG: {
                //1. 广播启动入口
                processNextBroadcast(true);
            } break;
            case BROADCAST_TIMEOUT_MSG: {
                //2. 广播超时,会触发广播类型的ANR
                synchronized (mService) {
                    broadcastTimeoutLocked(true);
                }
            } break;
        }
    }
}

该 Handler 中只处理两个任务,一个是 BROADCAST_INTENT_MSG 消息用于广播启动,一个是 BROADCAST_TIMEOUT_MSG 消息用于触发广播超时的 ANR 。我们先看看 processNextBroadcast 这个广播启动入口函数,该函数代码如下:

public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
    // 1. 处理并行广播
    while (mParallelBroadcasts.size() > 0) {
        ……
    }
    ……    
    boolean looped = false;
    //2. 处理有序广播
    do {
        final long now = SystemClock.uptimeMillis();
        //3. 获取mOrderedBroadcasts中的第一个广播
        r = mDispatcher.getNextBroadcastLocked(now);
        ……
        int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
        if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
            if ((numReceivers > 0) &&
                    (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
                //4. 启动广播超时延时任务
                broadcastTimeoutLocked(false); 
            }
        }
        ……
        if (r.receivers == null || r.nextReceiver >= numReceivers
                || r.resultAbort || forceReceive) {
            if (r.resultTo != null) {
                ……
                try {
                    //5. 同步调用接收者的 onReceive 方法
                    performReceiveLocked(r, r.resultToApp, r.resultTo,
                            new Intent(r.intent), r.resultCode,
                            r.resultData, r.resultExtras, false, false, r.shareIdentity,
                            r.userId, r.callingUid, r.callingUid, r.callerPackage,
                            r.dispatchTime - r.enqueueTime,
                            now - r.dispatchTime, 0,
                            r.resultToApp != null
                                    ? r.resultToApp.mState.getCurProcState()
                                    : ActivityManager.PROCESS_STATE_UNKNOWN);
                } catch (RemoteException e) {
                    r.resultTo = null;
                }
                ……
            }
            //6. 取消 ANR 判断任务
            cancelBroadcastTimeoutLocked();
            ……
            continue;
        }
        ……
    } while (r == null);

    // 预处理下一个广播对应的receiver
    ……
}

该方法中主要流程说明如下:

1)首先循环处理并行广播,即普通广播,普通广播在启动时,都是先放入到 mParallelBroadcasts 队列中。

2)接着开始循环处理有序广播

3)处理有序广播时,首先会通过在 mOrderdBroadcasts 队列中获取位于队头的有序广播

4)接着调用 broadcastTimeoutLoced 来启动广播启动超时的延时任务

5)然后调用 performReceiverLocked 方法,而该函数会同步调用接受者的 onReceive 方法

6)最后调用 cancelBroadcastTimeoutLoced 方法来取消 ANR 判定的延时任务

通过这段代码,我们也就能清晰的明白广播启动超时这一 ANR 的触发机制,如果广播接收者在 onReceive 方法中耗时太久,那么就来不及调用 cancelBroadcastTimeoutLoced 方法来移除延时的 ANR 任务,所以该 ANR 任务就会被触发。

ANR 触发任务

我们接着看 broadcastTimeoutLocked 这个会触发 ANR 的延时任务,该函数的简化代码实现如下:

final void broadcastTimeoutLocked(boolean fromMsg) {
    ……
    try {
        long now = SystemClock.uptimeMillis();
        BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
        if (fromMsg) {
            ……
            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
            if (timeoutTime > now) {
                //启动触发 ANR 的延时任务
                setBroadcastTimeoutLocked(timeoutTime);
                return;
            }
        }

        ……

    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    }

}

final void setBroadcastTimeoutLocked(long timeoutTime) {
    if (! mPendingBroadcastTimeoutMessage) {
        Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
        mHandler.sendMessageAtTime(msg, timeoutTime);
        mPendingBroadcastTimeoutMessage = true;
    }
}

通过代码可以看到,setBroadcastTimeoutLocked 方法会往 mHander 发送延时为 timeoutTime ,消息类型为 BROADCAST_TIMEOUT_MSG 的 ANR 触发任务,如果在 timeoutTime 的时间内,这个任务没移除,便会执行 BROADCAST_TIMEOUT_MSG 消息对应的 broadcastTimeoutLocked 函数来触发 ANR,该方法的精简代码实现如下:

final void broadcastTimeoutLocked(boolean fromMsg) {
    ……
    try {
        ……

        //  通过AMS触发ANR
        if (!debugging && app != null) {
            mService.appNotResponding(app, timeoutRecord);
        }

    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    }

}

在上面的代码中 mService.appNotResponding 这一方法便会调用 AMS 的 appNotResponding 方法来触发目标进程的 ANR。

  1. ContentProviderTimeout ANR (内容提供者发布超时)

ContentProvider类型的ANR特别之处是在ContentProvider进程首次启动时才会检测如果ContentProvider已经是启动后的状态,进行ContentProvider相关请求是不会触发ANR的。

  1. 应用进程向系统进程发起获取ContentProvider的请求;

  2. 此时ContentProvider没有启动,通过zygote创建Provider应用进程;

  3. Provider通过AMS进行注册;

  4. AMS启动超时检测机制;

  5. AMS通过binder通知到Provider开始启动工作;

  6. Provider进程进行启动相关工作;

  7. Provider启动完成,通知到AMS;

  8. AMS收到Provider启动完成,取消超时检测;

以上从4到8步骤中,发生超时,则触发弹出ANR问题。

程序首次启动过程中,AMS 会执行 attachApplicationLocked 方法,该方法会通过 binder 调用程序的 bindApplication 方法,该方法会触发程序启动的一系列流程,比如将 mainfest 中配置的 ContentProvider 进行发布,触发 Application 的 onAttach 等生命周期。该方法简化的代码如下:

private void attachApplicationLocked(@NonNull IApplicationThread thread,
        int pid, int callingUid, long startSeq) {

    ……
    //获取程序中配置的provider列表
    List<ProviderInfo> providers = normalMode
                                        ? mCpHelper.generateApplicationProvidersLocked(app)
                                        : null;

    //1. 发送CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG任务,该任务触发ContentProvider Timeout
    if (providers != null && mCpHelper.checkAppInLaunchingProvidersLocked(app)) {
        Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
        msg.obj = app;
        mHandler.sendMessageDelayed(msg,
                ContentResolver.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS);

    ……

    //2. 通过binder调用目标程序的bindApplication方法
    thread.bindApplication(processName, appInfo,
            app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
            providerList,
            instr2.mClass,
            profilerInfo, instr2.mArguments,
            instr2.mWatcher,
            instr2.mUiAutomationConnection, testMode,
            mBinderTransactionTrackingEnabled, enableTrackAllocation,
            isRestrictedBackupMode || !normalMode, app.isPersistent(),
            new Configuration(app.getWindowProcessController().getConfiguration()),
            app.getCompat(), getCommonServicesLocked(app.isolated),
            mCoreSettingsObserver.getCoreSettingsLocked(),
            buildSerial, autofillOptions, contentCaptureOptions,
            app.getDisabledCompatChanges(), serializedSystemFontMap,
            app.getStartElapsedTime(), app.getStartUptime());
    ……
}

通过上面代码可以知道, AMS 在调用目标程序的 bindApplication 方法之前,会先向 Handler 发送 CONTENT_PROVIDER_PUBLISH_TIMEOUT 的延时任务。该任务便会触发内容提供者发布超时这一 ANR。当目标程序在 bindApplication 方法中发布完 ContentProvider 后,便会通知 AMS 移除该任务消息。从这里我们也能发现,内容提供者的发布是在程序启动的过程中进行的,所以当内容提供者发布耗时太久,触发了 ANR,会直接影响到程序的正常启动

  1. InputDispatchingTimeout ANR(输入事件分发超时)

这种ANR跟上面介绍的三种有明显的不同。input超时机制不是到了定时时间就会触发,而是在处理后续输入事件时检查上一次是否超时,从而爆出ANR。

先来了解一下关于input的知识。intput有两个非常重要的线程。

InputReader线程:负责通过EventHub(监听/dev/input)读取输入事件,当监听到输入事件则放入到InputDispatcher的mInBoundQueue队列中。

InputDispatcher线程:负责输入事件的分发,当它收到来自inputReader的事件时,则将该事件分发给目标窗口。该线程维护了三个存放事件的队列。分别是mInBoundQueue、outBoundQueue、waitQueue。

mInBoundQueue存放来自InputDispatcher的输入事件;

outBoundQueue 存放即将发给目标窗口的输入事件;

waitQueue 记录已发送给目标应用,但尚未处理完成的输入事件。

InputDispatchingTimeout ANR发生过程

  1. InputReader线程监听到输入事件,将其放入mInboundQueue;

  2. InputDispatcher收到输入事件后被唤醒。先检查是否有在处理的输入事件。如果没有则取出mInboundQueue队头事件开始处理,重置ANR超时事件,检查窗口是否就绪。如果有正在处理的事件,如果outBoundQueue或waitQueue不为空,则开始检查是否存在输入事件处理超时。如果超时则弹出ANR。

  3. 应用窗口准备就绪,将事件转移到outBoundQueue队列;

  4. 应用端连接正常,则将事件从outBoundQueue放入waitQueue;

  5. InputDispatcher通过socket通讯,将事件发传递给目标进程;

  6. 目标进程收到事件后,开始事件分发和消费;

  7. 目标进程事件处理完成,通知InputDispatcher,将事件从waitQueue移除。

输入事件分发,它是指 Android 系统将用户触摸、按键等操作事件传递给对应程序的 Activity,Fragment 等组件的过程,这个过程主要在 InputFlinger 进程进行,涉及事件的捕获、传递、处理和响应等多个环节。该流程中的主要成员对象有 EventHub, InputReader,InputDispatcher,这几个对象详细的作用如下:

  • EventHub:EventHub 从底层的输入设备驱动程序接收原始的输入事件数据,并将原始事件数据转换为触摸事件(MotionEvent)、按键事件(KeyEvent)等输入事件对象,并传递给 InputReader 线程。

  • InputReader:InputReader 是一个线程,它会不断读取的 EventHub 中的输入事件,并根据设备类型和输入源,对输入事件进一步进行转换,加工,和分类。比如将多点触控事件转换为手势事件,将按键事件转换为字符事件等。

  • InputDispatcher:InputDispatcher 也是一个线程,从该对象的名称也能明白它的作用主要是进行事件的分发。它从 InputReader 那里接收加工后的输入事件后,会根据分发策略,将输入事件分发给合适的窗口或程序,在分发事件时,会根据输入事件的类型,设定一个超时时间,如果在超时时间内没有收到对应的窗口或程序的消费结果,InputDispatcher 便会认为窗口或应用无响应,触发 “InputDispatching TimedOut” 。

事件分发的流程非常的长,我们主要聚焦在 ANR 的触发逻辑上。在分发的过程中会有多个原因触发 “InputDispatching TimedOut” 这个 ANR,这些原因都可以归于窗口未就绪和窗口处理超时这两类,分别如下:

  • 窗口未就绪:InputDispatcher 要分发的窗口没有准备好接收新的输入事件,例如窗口已暂停、窗口连接已死亡、窗口连接已满等情况,那么 InputDispatcher 会等待窗口就绪,如果等待时间超过 5 秒,就会触发 ANR

  • 窗口处理超时:InputDispatcher 将输入事件分发给窗口后,如果窗口在 5 秒内没有返回事件处理结果给 InputDispatcher, 则 InputDispatcher 会认为窗口处理超时,触发 ANR

窗口处理超时

在实际场景中,大部分的输入事件分发超时都是因为窗口处理超时导致,而窗口处理超时中最常见的,又是主线程处理任务超时导致的。所以我们接着了解窗口处理超时导致 ANR 的机制流程。这一流程主要有这四步:

  1. 对 InputDispatcher 来说,每一个窗口都以一个 Connection 对象来维护, 在事件分发时,InputDispatcher 会先寻找到正确的窗口 Connection 对象。

  2. 当 InputDispatcher 找到对应的 Connection 后,就会通过 Socket 通信将事件分发给程序窗口,程序窗口会通过 InputChannel 对象来接收事件,InputDispatcher 分发完该事件后,会接着将事件放入到 waitQueue 队列中,

  3. 如果程序的窗口的主线程消费了这个事件,InputChannel 会通过 Socket 通信来通知 InputDispatcher 从 waitQueue 队列移除这个事件

  4. 当 InputDispatcher 在下一次事件分发时,会判断 waitQueue 队列中是否有事件在五秒内没被移除,如果有就会认为发生了 ANR。

这一流程如图所示。

InputDispatcher 分发事件

InputDispatcher 是一个在不断运行的线程,该线程会循环的执行 dispatchOnce 方法来进行事件的分发。该方法的代码如下

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { 
        std::scoped_lock _l(mLock);
        mDispatcherIsAlive.notify_all();
        // 1. 分发输入事件
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }
        // 2. 处理输入命令
        if (runCommandsLockedInterruptable()) {
            nextWakeupTime = LLONG_MIN;
        }
        // 3. 处理 ANR ,并返回下一次线程唤醒的时间。
        const nsecs_t nextAnrCheck = processAnrsLocked();
        nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);

        if (nextWakeupTime == LONG_LONG_MAX) {
            mDispatcherEnteredIdle.notify_all();
        }
    } 
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    // 4. 线程休眠 timeoutMillis 毫秒
    mLooper->pollOnce(timeoutMillis);
}

该方法主要做的事情有这四件:

  1. 调用 dispatchOnceInnerLocked 方法来分发事件

  2. 调用 runCommandsLockedInterruptable 方法处理输入命令,比如横竖屏切换,音量的调节等都是输入命令,该方法中会对输入命令封装,然后分发给目标对象处理

  3. 调用 processAnrsLocked 来判断是否发生了 ANR,该方法会去 waitQueue 寻找是否有超过 5S 未被处理输入事件,如果有则抛出 ANR

  4. 基于性能考虑将当前线程休眠一定时间,直到休眠结束或者有新的输入事件要分发而被唤醒

dispatchOnceInnerLocked 即为事件分发的方法,但由于这个方法内部逻辑和路径非常多,我们在这里先通过时序图,如下图所示,来了解该方法的主路径。

在时序图中,序列 3 findToucheWindowTargetsLocked 方法会根据焦点寻找目标窗口,序列 7 startDispatchCyclelocked 便会通过对应 Connection 将事件分发给程序的 InputChannel 中,我们主要看看这个方法的实现。该方法的简化代码如下所示。

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection) {
    // 遍历 Connection 的 outboundQueue 队列,直到队列为空或者 Connection 状态异常
    while (connection->status == Connection::STATUS_NORMAL && 
            !connection->outboundQueue.isEmpty()) {
        // 取出队列头部的 DispatchEntry 对象
        DispatchEntry* dispatchEntry = connection->outboundQueue.head;
        // 设置分发时间为当前时间
        dispatchEntry->deliveryTime = currentTime;
        status_t status;
        // 获取对应的 EventEntry 对象
        EventEntry* eventEntry = dispatchEntry->eventEntry;
        // 1. 根据事件类型,调用不同的 publish 函数,将输入事件发送给目标窗口或应用
        switch (eventEntry->type) {
            case EventEntry::TYPE_KEY: {
                status = connection->inputPublisher.publishKeyEvent(……);
                break;
            }
            case EventEntry::TYPE_MOTION: {
                status = connection->inputPublisher.publishMotionEvent(……);
                break;
            }
            default:               
                return;
        }
        // 2. 判断发送状态是否正常
        if (status) {   
            if (status == WOULD_BLOCK) {
                 /*如果发送状态为 WOULD_BLOCK,
                   表示目标窗口或应用的 InputChannel 缓存区已满,无法接收新的输入事件
                    此时会直接中断分发循环,触发 ANR*/
               ……
            } else {
                /* 如果发送状态为其他错误,表示发送过程出现了异常,
                也直接中断分发循环,触发 ANR*/
                abortBrokenDispatchCycleLocked(currentTime, connection, true);
            }
            return;
        }
        // 3. 如果发送状态正常,就将 DispatchEntry 对象从 outboundQueue 队列中移除
        connection->outboundQueue.dequeue(dispatchEntry);
        // 将 DispatchEntry 对象添加到 waitQueue 队列的尾部,等待目标窗口或应用的反馈
        connection->waitQueue.enqueueAtTail(dispatchEntry);
        // 将 DispatchEntry 对象添加到 mAnrTracker 中,用于追踪输入事件的超时情况
        mAnrTracker.insert(dispatchEntry);
    }
}

该方法中主要有三个流程,分别如下:

  1. 根据输入事件的类型(eventEntry->type)选择目标窗口(connection)对应的事件分发函数(publishMotionEvent 或 publishKeyEvent)来进行事件分发

  2. 如果事件分发失败,此时会中断分发并触发 ANR,此时的 ANR 属于窗口未就绪导致的 ANR

  3. 最后将该输入事件放到 waitQueue 中,用于后续进行 ANR 超时判断

到这里我们就了解了 InputDispatcher 是如何进行事件分发的,在最后一步中,InputDispatcher 将事件放入到了 waitQueue 中,如果该队列中的事件超过 5 秒没被移除就会触发 ANR,所以我们接着看一下 waitQueue 中的事件是如何被移除的。

InputDispatcher 移除 waitQueue 队列事件

当目标窗口的主线程处理完输入事件后,便会通过 Socket 来通知 InputDispatcher 事件已经消费,InputDispatcher 会在 handleReceiveCallback 方法中处理这一流程,该方法的简化代码如下所示。

int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionToken) {
    std::scoped_lock _l(mLock);
    std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
    ……
    if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {
        ……
        for (;;) {
            //1. 接收窗口返回的事件消费反馈
            Result<InputPublisher::ConsumerResponse> result =
                    connection->inputPublisher.receiveConsumerResponse();
            if (!result.ok()) {
                status = result.error().code();
                break;
            }

            if (std::holds_alternative<InputPublisher::Finished>(*result)) {
                const InputPublisher::Finished& finish =
                        std::get<InputPublisher::Finished>(*result);
                //2. 处理窗口返回的事件消费反馈
                finishDispatchCycleLocked(currentTime, connection, finish.seq, 
                                            finish.handled,
                                            finish.consumeTime);
            } else if (std::holds_alternative<InputPublisher::Timeline>(*result)) {
               ……
            }
            gotOne = true;
        }
        if (gotOne) {
            //3. 执行command命令
            runCommandsLockedInterruptable();
            if (status == WOULD_BLOCK) {
                return 1;
            }
        }

    } else {
        ……
    }

    removeInputChannelLocked(connection->inputChannel->getConnectionToken(), notify);
    return 0;
}

该方法中的主要流程解释如下:

  1. 首先通过 receiveConsumerResponse 方法来接收窗口返回的事件消费通知

  2. 接着在 finishDispatchCycleLocked 方法中将处理接收的事件的任务封装成 Command 命令

  3. 最后调用 runCommandsLockedInterruptable 方法执行上面封装的处理事件消费 Command 任务

我们接着主要看一下 finishDispatchCycleLocked 中做的事情,该函数代码实现如下:

void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
                                                const std::shared_ptr<Connection>& connection,
                                                uint32_t seq, bool handled, nsecs_t consumeTime) {

    if (connection->status == Connection::Status::BROKEN ||
        connection->status == Connection::Status::ZOMBIE) {
        return;
    }

    //将对窗口回调处理任务封装成Command
    auto command = [this, currentTime, connection, seq, handled, consumeTime]() REQUIRES(mLock) {
        doDispatchCycleFinishedCommand(currentTime, connection, seq, handled, consumeTime);
    };
    //将 Command 添加到队列中
    postCommandLocked(std::move(command));
}

//输入事件移除逻辑
void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime,
                                                     const std::shared_ptr<Connection>& connection,
                                                     uint32_t seq, bool handled,
                                                     nsecs_t consumeTime) {
    ……

    //循环遍历waitQueue队列
    dispatchEntryIt = connection->findWaitQueueEntry(seq);
    if (dispatchEntryIt != connection->waitQueue.end()) {
        dispatchEntry = *dispatchEntryIt;
        //移除输入事件
        connection->waitQueue.erase(dispatchEntryIt);
        const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken();
        mAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken);
        ……
    }
    startDispatchCycleLocked(now(), connection);
}

可以看到,真正的事件处理函数是 doDispatchCycleFinishedCommand 方法,该方法中会遍历 waitQueue 寻找对应的事件,找到后会将该事件从 waitQueue 中移除,该处理函数被添加到 Command 队列中,直到 runCommandsLockedInterruptable 函数调用时才会从 Command 队列取出该命令并执行。

InputDispatcher 对 ANR 的判定

我们接着看 dispatchOnce 这个循环执行的方法中的最后一个步骤,调用 processAnrsLocked 函数来判断 ANR,该函数的代码实现如下。

void InputDispatcher::processAnrsLocked(nsecs_t currentTime) {
    // 遍历所有的 Connection 对象
    for (size_t i = 0; i < mConnectionsByFd.size(); i++) {
        const sp<Connection>& connection = mConnectionsByFd.valueAt(i);
        // 获取 waitQueue 队列
        Queue<DispatchEntry>* waitQueue = &connection->waitQueue;
        if (waitQueue->isEmpty()) {
            continue;
        }
        // 获取目标应用和窗口对象
        sp<InputApplicationHandle> applicationHandle = connection->inputApplicationHandle;
        sp<InputWindowHandle> windowHandle = connection->inputWindowHandle;
        // 遍历 waitQueue 队列中的 DispatchEntry 对象
        for (DispatchEntry* dispatchEntry = waitQueue->head; dispatchEntry; dispatchEntry = dispatchEntry->next) {
            // 获取 EventEntry 对象
            EventEntry* eventEntry = dispatchEntry->eventEntry;
            // 获取超时时间
            nsecs_t timeout = getDispatchingTimeoutLocked(applicationHandle, windowHandle);
            nsecs_t startTime = dispatchEntry->deliveryTime;
            nsecs_t waitTime = currentTime - startTime;
            // 判断是否超时
            if (waitTime >= timeout) {
                // 调用 onANRLocked 函数,触发 ANR
                onANRLocked(currentTime, applicationHandle, windowHandle, eventEntry->eventTime, startTime, "input dispatching timed out");
                // 跳出循环,继续下一个 Connection 对象
                break;
            }
        }
    }
}

从源码实现可以看到,processAnrsLocked 函数会遍历所有的窗口 Connection 对象的 waitQueue 队列,比较输入事件的超时时间和当前时间,如果超过了超时时间,就调用 onANRLocked 函数来触发 ANR。onAnrLocked 函数的代码如下。

void InputDispatcher::onAnrLocked(const std::shared_ptr<Connection>& connection) {
    if (connection == nullptr) {
        LOG_ALWAYS_FATAL("Caller must check for nullness");
    }
    if (connection->waitQueue.empty()) {
        ALOGI("Not raising ANR because the connection %s has recovered",
              connection->inputChannel->getName().c_str());
        return;
    }

    DispatchEntry* oldestEntry = *connection->waitQueue.begin();
    const nsecs_t currentWait = now() - oldestEntry->deliveryTime;
    //打印 ANR 信息
    std::string reason =
            android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",
                                        connection->inputChannel->getName().c_str(),
                                        ns2ms(currentWait),
                                        oldestEntry->eventEntry->getDescription().c_str());
    sp<IBinder> connectionToken = connection->inputChannel->getConnectionToken();
    //将 ANR 信息发送给窗口
    updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason);
    //发送给 WindowManagerService 处理
    processConnectionUnresponsiveLocked(*connection, std::move(reason));

    cancelEventsForAnrLocked(connection);
}

onAnrLocked 函数中会将 ANR 的超时信息打印出来,并通过 Binder 发送 ANR 信号给目标窗口,并通过 processConnectionUnresponsiveLocked 方法将 ANR 发送给 WindowManagerService 做进一步处理 。

  1. ANR 触发原理

ANR (Application Not responding),是指应用程序未响应,Android 系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成 ANR。

触发 ANR 的过程可分为三个步骤:埋炸弹,拆炸弹,引爆炸弹

  1. Service Timeout 是位于”ActivityManager” 线程中的 AMS.MainHandler 收到 SERVICE_TIMEOUT_MSG 消息时触发。

对于 Service 有两类:

  • 对于前台服务,则超时为 SERVICE_TIMEOUT = 20s;

  • 对于后台服务,则超时为 SERVICE_BACKGROUND_TIMEOUT = 200s

由变量 ProcessRecord.execServicesFg 来决定是否前台启动.

埋炸弹阶段:在 Service 进程 attach 到 system_server 进程的过程中会调用 realStartServiceLocked() 方法 (准确说是 scheduleServiceTimeoutLocked 方法) 中来埋下炸弹.

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
    ...
    //发送delay消息(SERVICE_TIMEOUT_MSG),
    bumpServiceExecutingLocked(r, execInFg, "create");
    try {
        ...
        //最终执行服务的onCreate()方法
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);
    } catch (DeadObjectException e) {
        mAm.appDiedLocked(app);
        throw e;
    } finally {
        ...
    }
}
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
    ... 
    scheduleServiceTimeoutLocked(r.app);
}

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    if (proc.executingServices.size() == 0 || proc.thread == null) {
        return;
    }
    long now = SystemClock.uptimeMillis();
    Message msg = mAm.mHandler.obtainMessage(
            ActivityManagerService.SERVICE_TIMEOUT_MSG);
    msg.obj = proc;
    
    //当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程
    mAm.mHandler.sendMessageAtTime(msg,
        proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}

拆炸弹阶段:经过 Binder 等层层调用进入目标进程的主线程 ActivityThread.handleCreateService () 的过程。在这个过程会创建目标服务对象,以及回调 onCreate () 方法,紧接再次经过多次调用回到 system_server 来执行 serviceDoneExecuting.

最终在 serviceDoneExecutingLocked 中移除服务超时消息 SERVICE_TIMEOUT_MSG

private void handleCreateService(CreateServiceData data) {
        ...
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        Service service = (Service) cl.loadClass(data.info.name).newInstance();
        ...

        try {
            //创建ContextImpl对象
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);
            //创建Application对象
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            //调用服务onCreate()方法 
            service.onCreate();
            
            //拆除炸弹引线[见小节2.2.2]
            ActivityManagerNative.getDefault().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (Exception e) {
            ...
        }
    }
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) {
    ...
    if (r.executeNesting <= 0) {
        if (r.app != null) {
            r.app.execServicesFg = false;
            r.app.executingServices.remove(r);
            if (r.app.executingServices.size() == 0) {
                //当前服务所在进程中没有正在执行的service
                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
        ...
    }
    ...

引爆炸弹阶段:在 system_server 进程中有一个 Handler 线程,名叫”ActivityManager”. 当倒计时结束便会向该 Handler 线程发送 一条信息 SERVICE_TIMEOUT_MSG,

final class MainHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case SERVICE_TIMEOUT_MSG: {
                ...
                //【见小节2.3.2】
                mServices.serviceTimeout((ProcessRecord)msg.obj);
            } break;
            ...
        }
        ...
    }
}
void serviceTimeout(ProcessRecord proc) {
    String anrMessage = null;

    synchronized(mAm) {
        if (proc.executingServices.size() == 0 || proc.thread == null) {
            return;
        }
        final long now = SystemClock.uptimeMillis();
        final long maxTime =  now -
                (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
        ServiceRecord timeout = null;
        long nextTime = 0;
        for (int i=proc.executingServices.size()-1; i>=0; i--) {
            ServiceRecord sr = proc.executingServices.valueAt(i);
            if (sr.executingStart < maxTime) {
                timeout = sr;
                break;
            }
            if (sr.executingStart > nextTime) {
                nextTime = sr.executingStart;
            }
        }
        if (timeout != null && mAm.mLruProcesses.contains(proc)) {
            Slog.w(TAG, "Timeout executing service: " + timeout);
            StringWriter sw = new StringWriter();
            PrintWriter pw = new FastPrintWriter(sw, false, 1024);
            pw.println(timeout);
            timeout.dump(pw, " ");
            pw.close();
            mLastAnrDump = sw.toString();
            mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
            mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
            anrMessage = "executing service " + timeout.shortName;
        }
    }

    if (anrMessage != null) {
        //当存在timeout的service,则执行appNotResponding
        mAm.appNotResponding(proc, null, null, false, anrMessage);
    }
}

其中 anrMessage 的内容为”executing service [发送超时 serviceRecord 信息]”;

  1. BroadcastReceiver Timeout 是位于”ActivityManager” 线程中的 BroadcastQueue.BroadcastHandler 收到 BROADCAST_TIMEOUT_MSG 消息时触发。

对于广播队列有两个: foreground 队列和 background 队列:

  • 对于前台广播,则超时为 BROADCAST_FG_TIMEOUT = 10s;

  • 对于后台广播,则超时为 BROADCAST_BG_TIMEOUT = 60s ;

埋炸弹阶段:

通过调用 BroadcastQueue.processNextBroadcast () 来处理广播。其流程为先处理并行广播,再处理当前有序广播,最后获取并处理下条有序广播.

final void processNextBroadcast(boolean fromMsg) {
    synchronized(mService) {
        ...
        //part 2: 处理当前有序广播
        do {
            r = mOrderedBroadcasts.get(0);
            //获取所有该广播所有的接收者
            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))) {
                    //当广播处理时间超时,则强制结束这条广播
                    broadcastTimeoutLocked(false);
                    ...
                }
            }
            if (r.receivers == null || r.nextReceiver >= numReceivers
                    || r.resultAbort || forceReceive) {
                if (r.resultTo != null) {
                    //处理广播消息消息
                    performReceiveLocked(r.callerApp, r.resultTo,
                        new Intent(r.intent), r.resultCode,
                        r.resultData, r.resultExtras, false, false, r.userId);
                    r.resultTo = null;
                }
                //拆炸弹
                cancelBroadcastTimeoutLocked();
            }
        } while (r == null);
        ...

        //part 3: 获取下条有序广播
        r.receiverTime = SystemClock.uptimeMillis();
        if (!mPendingBroadcastTimeoutMessage) {
            long timeoutTime = r.receiverTime + mTimeoutPeriod;
            //埋炸弹
            setBroadcastTimeoutLocked(timeoutTime);
        }
        ...
    }
}

对于广播超时处理时机:

  1. 首先在 part3 的过程中 setBroadcastTimeoutLocked (timeoutTime) 设置超时广播消息;

  2. 然后在 part2 根据广播处理情况来处理:

    1. 当广播接收者等待时间过长,则调用 broadcastTimeoutLocked (false);

    2. 当执行完广播,则调用 cancelBroadcastTimeoutLocked;

final void setBroadcastTimeoutLocked(long timeoutTime) {
    if (! mPendingBroadcastTimeoutMessage) {
        Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
        mHandler.sendMessageAtTime(msg, timeoutTime);
        mPendingBroadcastTimeoutMessage = true;
    }
}

设置定时广播 BROADCAST_TIMEOUT_MSG,即当前往后推 mTimeoutPeriod 时间广播还没处理完毕,则进入广播超时流程。

拆炸弹阶段:

在 processNextBroadcast () 过程,执行完 performReceiveLocked, 便会拆除炸弹.

final void cancelBroadcastTimeoutLocked() {
    if (mPendingBroadcastTimeoutMessage) {
        mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
        mPendingBroadcastTimeoutMessage = false;
    }
}

引爆炸弹阶段:

BroadcastHandler.handleMessage

private final class BroadcastHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case BROADCAST_TIMEOUT_MSG: {
                synchronized (mService) {
                    //【见小节3.3.2】
                    broadcastTimeoutLocked(true);
                }
            } break;
            ...
        }
        ...
    }
}

不会引爆的四种情况

  • mOrderedBroadcasts 已处理完成,则不会 anr;

  • 正在执行 dexopt,则不会 anr;

  • 系统还没有进入 ready 状态 (mProcessesReady=false),则不会 anr;

  • 如果当前正在执行的 receiver 没有超时,则重新设置广播超时,不会 anr;

  1. ContentProvider Timeout 是位于”ActivityManager” 线程中的 AMS.MainHandler 收到 CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG 消息时触发。

ContentProvider 超时为 CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s. 这个跟前面的 Service 和 BroadcastQueue 完全不同,由 Provider 进程启动过程相关.

埋炸弹阶段:

埋炸弹的过程 其实是在进程创建的过程,进程创建后会调用 attachApplicationLocked () 进入 system_server 进程. 10s 之后引爆该炸弹

private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
    ProcessRecord app;
    if (pid != MY_PID && pid >= 0) {
        synchronized (mPidsSelfLocked) {
            app = mPidsSelfLocked.get(pid); // 根据pid获取ProcessRecord
        }
    } 
    ...
    
    //系统处于ready状态或者该app为FLAG_PERSISTENT进程则为true
    boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
    List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;

    //app进程存在正在启动中的provider,则超时10s后发送CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息
    if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
        Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
        msg.obj = app;
        mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
    }
    
    thread.bindApplication(...);
    ...
}

拆炸弹阶段:

当 provider 成功 publish 之后,便会拆除该炸弹

public final void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) {
   ...
   
   synchronized (this) {
       final ProcessRecord r = getRecordForAppLocked(caller);
       
       final int N = providers.size();
       for (int i = 0; i < N; i++) {
           ContentProviderHolder src = providers.get(i);
           ...
           ContentProviderRecord dst = r.pubProviders.get(src.info.name);
           if (dst != null) {
               ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
               
               mProviderMap.putProviderByClass(comp, dst); //将该provider添加到mProviderMap
               String names[] = dst.info.authority.split(";");
               for (int j = 0; j < names.length; j++) {
                   mProviderMap.putProviderByName(names[j], dst);
               }

               int launchingCount = mLaunchingProviders.size();
               int j;
               boolean wasInLaunchingProviders = false;
               for (j = 0; j < launchingCount; j++) {
                   if (mLaunchingProviders.get(j) == dst) {
                       //将该provider移除mLaunchingProviders队列
                       mLaunchingProviders.remove(j);
                       wasInLaunchingProviders = true;
                       j--;
                       launchingCount--;
                   }
               }
               //成功pubish则移除该消息
               if (wasInLaunchingProviders) {
                   mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
               }
               synchronized (dst) {
                   dst.provider = src.provider;
                   dst.proc = r;
                   //唤醒客户端的wait等待方法
                   dst.notifyAll();
               }
               ...
           }
       }
   }    
}

引爆炸弹阶段:

在 system_server 进程中有一个 Handler 线程,名叫”ActivityManager”. 当倒计时结束便会向该 Handler 线程发送 一条信息 CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG,

final class MainHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG: {
                ...
                ProcessRecord app = (ProcessRecord)msg.obj;
                synchronized (ActivityManagerService.this) {
                
                    processContentProviderPublishTimedOutLocked(app);
                }
            } break;
            ...
        }
        ...
    }
}

总结:

当出现 ANR 时,都是会调用到 AMS.appNotResponding () 方法

Timeout 时长

  • 对于前台服务,则超时为 SERVICE_TIMEOUT = 20s;

  • 对于后台服务,则超时为 SERVICE_BACKGROUND_TIMEOUT = 200s

  • 对于前台广播,则超时为 BROADCAST_FG_TIMEOUT = 10s;

  • 对于后台广播,则超时为 BROADCAST_BG_TIMEOUT = 60s;

  • ContentProvider 超时为 CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s;

超时检测

Service 超时检测机制:

  • 超过 timeout 时长没有执行完相应操作来触发移除延时消息,则会触发 anr;

BroadcastReceiver 超时检测机制:

  • 有序广播的总执行时间超过 2* receiver 个数 * timeout 时长,则会触发 anr;

  • 有序广播的某一个 receiver 执行过程超过 timeout 时长,则会触发 anr;

另外:

  • 对于 Service, Broadcast, Input 发生 ANR 之后,最终都会调用 AMS.appNotResponding;

  • 对于 provider, 在其进程启动时 publish 过程可能会出现 ANR, 则会直接杀进程以及清理相应信息,而不会弹出 ANR 的对话框. appNotRespondingViaProvider () 过程会走 appNotResponding (),

  1. ANR问题分析思路

遇到ANR问题,通常按如下的步骤进行排查:

  1. 找到发生ANR的进程以及ANR类型等摘要信息,判断是什么大概什么类型的问题

确定了ANR类型,查看系统日志,判断此次ANR经历的阶段。结合我们上面学到的ANR原理的知识。判断大概是哪个阶段出现问题。

以广播类型ANR为例。通过搜索关键日志”Enqueing broadcast”(广播入队),“Delivering to BroadcastFilter”(广播派发)等,以及判断在这个阶段经历的时间,粗略判断是否正常。比如,广播入队时间长可能是系统的问题,系统繁忙时则会影响广播派发的效率。处理广播耗时较长,可能应用在接收端进行耗时处理。

其他类型ANR也是这样的思路分析。

  1. 查看当前系统负载情况,CPU、内存、IO这些。

如果当前应用的CPU占比较高,说明应用在进行耗时操作,大概率从应用本身找原因。如果系统CPU占比较高,这个时候可能系统处于繁忙状态。其他应用CPU占比高,就看占比高的应用当前在进行什么操作。

其他情况,IO、内存占比高也是类似的分析思路。

  1. 查看调用堆栈以及分析代码。这是最重要的分析步骤,这里面可能遇到多种情况。常见的有主线程正在进行耗时操作,主线程在进行同步操作时被block在锁上等。

case1:主线程在进行耗时操作

这种情况是比较直观的,在抓取的trace.txt文件中,搜索”main”线程,观察它此时的线程状态,以及调用栈信息。

线程状态主要的有RUNABLE(可执行状态)、WAITING/ BLOCKED(阻塞)、NATIVE(native方法执行中)、SUSPENDED(挂起中,GC或者debug时)比较常见。

以下面的trace为例,看到是“Native”状态,正在执行本地方法,此时是正在执行的状态,大概率此时主线程正在进行超时操作。继续查看他的调用栈信息,找到响应的代码,看此时正在执行什么操作,进一步定位和解决问题。

case2:主线程被block

这种也是非常常见的情形,以下面trace为例。此时是“Waiting”状态,说明主线程此时block状态。通过关键字locked找到此时在等待的锁是“0x2bbf9d9e”这个对象。然后在trace文件搜索这个锁当前被谁持有。再分析持有锁的线程此时在进行什么操作。进而定位和解决问题。

race文件详细解读

阅读trace文件是分析ANR最主要的途径,下面对各字段的意义进行详细的解读

第1行:

线程名: “Binder_1”是一个binder线程。“main”标识的是主线程。其他线程,命名是“Thread-x”的格式,x是递增的id号;

prio: 线程优先级,默认是5;

tid:线程唯一标识ID;

线程状态:NATVIE表示正在执行本地方法;

第2行:

group: 线程组名称;

sCount: 线程被挂起的次数;

dsCount: debug时线程被调试器挂起的次数;

obj: 对象地址;

self: 线程native地址;

第3行:

sysTid: 线程号(主线程的线程号与进程号相同);

nice: 线程调度优先级;

sched: 线程调度策略及优先级;

cgrp: 调度归属组;

handle: 线程处理函数地址;

第4行:

state: 线程调度状态;

schedstat: CPU调度时间统计。括号里三个值分别是running、runable、switch的时间。(running是运行时间,单位ns;runable是排队等待时间,单位ns;switch是cpu调度切换次数);

utm: 线程在用户态执行时间片,单位jiffies,需要配合Hz字段计算时间;

stm: 线程在内核态执行时间片,单位jiffies,需要配合Hz字段计算时间;

core: 线程执行在哪个CPU核心。通常数字小为小核,数字大为大核;

Hz: 时钟频率,1秒内时间片的数量。100Hz,即每个时间片10ms;

第5行:

stack: 线程栈的地址区间;

stackSize: 栈的大小;

第6行:

mutex: 持有锁的类型。exclusive是独占锁,shared是共享锁;

关于线程状态,它的来源有java层和navite层两个,他们的对应关系和意义如下:

  1. 小结

结合ANR分析的案例场景,总结出ANR分析的大概流程:

在工作中我们应该保持良好编程习惯,避免ANR问题的发生:

  1. 避免在主线程进行复杂耗时的操作,如读写文件、操作数据库、进行网络请求等;

  2. 广播接收器中不要进行复杂的逻辑处理,复杂操作可以开线程执行;

  3. 在service、activity声明周期中,避免复杂操作;

  4. 编码中注意循环操作的边界控制,避免出现无限循环;

  5. 设计编码中注意同步逻辑,避免出现同步死锁问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值