Android ANR 机制

源码基于 Android 11.0/R/sdk-30。

Broadcast ANR

// BroadcastQueue.java
final void processNextBroadcast(boolean fromMsg) {
    //通过 AMS 更新 CPU 使用信息
    mService.updateCpuStats();

    // First, deliver any non-serialized broadcasts right away.
    while (mParallelBroadcasts.size() > 0) {
        //...
    }

    //发送 BROADCAST_TIMEOUT_MSG 消息
    broadcastTimeoutLocked(false); // forcibly finish this broadcast
    //执行广播
    performReceiveLocked(r.callerApp, r.resultTo,...);
    //移除 BROADCAST_TIMEOUT_MSG 消息
    cancelBroadcastTimeoutLocked();
}
  1. 先处理并行广播,因为是单向通知,不需要等待反馈,所以并行广播没有 ANR。
  2. 再处理串行广播。
    1. 首先判断是否已经有一个广播超时消息;
    2. 然后,根据目标进程优先级,分别在前台队列和后台队列(超时时限不同)中排队处理;
    3. 接下来,根据不同的队列,发出不同延时的 ANR 消息;如果处理及时,取消延时消息;如果处理超时,触发 ANR;

广播的 ANR 处理相对简单,主要是再次判断是否超时、记录日志,记录 ANR 次数等。然后就继续调用 processNextBroadcast 函数,处理下一条广播了。

// 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;

Service ANR

public final class ActiveServices {
    static final int SERVICE_TIMEOUT = 20*1000;
    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;

    // 启动服务时判断是否是前台服务
    ComponentName startServiceLocked(IApplicationThread caller, ...){
        final boolean callerFg;
        if (caller != null) {
            final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
            callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
        } else {
            callerFg = true;
        }
    }

    // 发送服务超时消息。启动服务时调用
    void scheduleServiceTimeoutLocked(ProcessRecord proc) {
        Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_TIMEOUT_MSG);
        msg.obj = proc;
        mAm.mHandler.sendMessageDelayed(msg,
                proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
    }

    // 处理服务超时消息
    void serviceTimeout(ProcessRecord proc) {
        String anrMessage = null;
        if (timeout != null && mAm.mProcessList.mLruProcesses.contains(proc)) {
            Slog.w(TAG, "Timeout executing service: " + timeout);
            //...
            anrMessage = "executing service " + timeout.shortInstanceName;
        } else {
            //时间未到,发送超时消息
        }
        if (anrMessage != null) {
            mAm.mAnrHelper.appNotResponding(proc, anrMessage);
        }
    }

    // 服务执行完成取消超时消息
    private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
            boolean finishing) {
        if (r.app.executingServices.size() == 0) {
            mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
        }
    }
}
  1. 启动服务时判断是否是前台服务,决定超时时间;
  2. 发送 SERVICE_TIMEOUT_MSG 服务超时消息;
  3. 如果服务没有超时,移除消息。否则处理服务超时逻辑,记录日志等。

ContentProvider ANR

ContentProvider 超时为 CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s

public abstract class ContentResolver implements ContentInterface {
    /**
     * How long we wait for an attached process to publish its content providers
     * before we decide it must be hung.
     */
    public static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS = 10 * 1000;
}

public class ActivityManagerService{
    private boolean attachApplicationLocked(@NonNull IApplicationThread thread, int pid, ...) {
        //启动的 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, ContentResolver.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS);
        }
    }

    // App 进程发布 Provider 成功后移除消息
    public final void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) {
        //成功pubish则移除该消息
        if (wasInLaunchingProviders) {
            mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
        }
    }

    // 发布 Provider 失败,
    private final void processContentProviderPublishTimedOutLocked(ProcessRecord app) {
        cleanupAppInLaunchingProvidersLocked(app, true);
        mProcessList.removeProcessLocked(app, false, true,
                ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
                ApplicationExitInfo.SUBREASON_UNKNOWN,
                "timeout publishing content providers");
    }
}

Activity ANR

Activity 的 ANR 是相对最复杂的,也只有 Activity 中出现的 ANR 会弹出 ANR 提示框。
最终的表现形式是:弹出一个对话框,告诉用户当前某个程序无响应,输入一大堆与 ANR 相关的日志,便于开发者解决问题。

InputDispatching:

Activity 最主要的功能之一是交互,为了方便交互,Android 中的 InputDispatcher 会发出操作事件,最终在 InputManagerService 中发出事件,通过 InputChannel,向 Activity 分发事件。交互事件必须得到响应,如果不能及时处理,IMS 就会报出 ANR,交给 AMS 去弹出 ANR 提示框。

KeyDispatching:

如果输入是个 Key 事件,会从 IMS 进入 ActivityRecord.Token.keyDispatchingTimeOut,然后进入 AMS 处理,不同的是,在 ActivityRecord 中,会先截留一次 Key 的不响应,只有当 Key 连续第二次处理超时,才会弹出 ANR 提示框。

窗口焦点:

Activity 总是需要有一个当前窗口来响应事件的,但如果迟迟没有当前窗口(获得焦点),比如在 Activity 切换时,旧 Activity 已经 onPause,新的 Activity 一直没有 onResume,持续超过 5 秒,就会 ANR。
App 的生命周期太慢,或 CPU 资源不足,或 WMS 异常,都可能导致窗口焦点。

1. 判断是否有 focused 组件以及 focused Application:

这种一般是在应用启动时触发,比如启动时间过长在这过程中触发了 keyevent 或者 trackball moteionevent 就会出现。

// ~/frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::dumpDispatchStateLocked(String8& dump) {
    dump.appendFormat(INDENT "DispatchEnabled: %d\n", mDispatchEnabled);
    dump.appendFormat(INDENT "DispatchFrozen: %d\n", mDispatchFrozen);
    if (mFocusedApplicationHandle != NULL) {
        dump.appendFormat(INDENT "FocusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
                mFocusedApplicationHandle->getName().string(),
                mFocusedApplicationHandle->getDispatchingTimeout(
                        DEFAULT_INPUT_DISPATCHING_TIMEOUT) / 1000000.0);
    } else {
        dump.append(INDENT "FocusedApplication: <null>\n");
    }
    ...
}

对应于

Reason: Input dispatching timed out (Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)

// ActivityManagerService.java
/**
* Handle input dispatching timeouts.
* @return whether input dispatching should be aborted or not.
*/
boolean inputDispatchingTimedOut(ProcessRecord proc, ...) {
    final String annotation;
    if (reason == null) {
        annotation = "Input dispatching timed out";
    } else {
        annotation = "Input dispatching timed out (" + reason + ")";
    }
    if (proc != null) {
        synchronized (this) {
            if (proc.isDebugging()) {
                return false;
            }

            if (proc.getActiveInstrumentation() != null) {
                Bundle info = new Bundle();
                info.putString("shortMsg", "keyDispatchingTimedOut");
                info.putString("longMsg", annotation);
                finishInstrumentationLocked(proc, Activity.RESULT_CANCELED, info);
                return true;
            }
        }
        mAnrHelper.appNotResponding(proc, activityShortComponentName, aInfo,
                parentShortComponentName, parentProcess, aboveSystem, annotation);
    }
    return true;
}

2. 判断前面的事件是否及时完成:

对应于

Reason: Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago. Wait queue length: 10. Wait queue head age: 5591.3ms.)

出现这种问题意味着主线程正在执行其他的事件但是比较耗时导致输入事件无法及时处理。

ANR 流程

ANR 原理

InputDispatcher 超时是最常见的 ANR 类型,而且其类型也比较多。
当用户触摸屏幕或者按键操作,首次触发的是硬件驱动,驱动收到事件后,将该相应事件写入到输入设备节点, 这便产生了最原生态的内核事件。接着,输入系统取出原生态的事件,经过层层封装后成为 KeyEvent 或者 MotionEvent ;最后,交付给相应的目标窗口(Window)来消费该输入事件。可见,输入系统在整个过程起到承上启下的衔接作用。

Input 模块的主要组成:

  • Native 层的 InputReader 负责从 EventHub 取出事件并处理,再交给 InputDispatcher;
  • Native 层的 InputDispatcher 接收来自 InputReader 的输入事件,并记录 WMS 的窗口信息,用于派发事件到合适的窗口;
  • Java 层的 InputManagerService 跟 WMS 交互,WMS 记录所有窗口信息,并同步更新到 IMS,为 InputDispatcher 正确派发事件到 ViewRootImpl 提供保障;

生成 ANR 信息

// ProcessRecord.java
final void appNotResponding(ProcessRecord app, ActivityRecord activity,...) {
    long anrTime = SystemClock.uptimeMillis();
    // 默认 true
    if (isMonitorCpuUsage()) {
        // 更新CPU使用信息。ANR的第一次CPU信息采样,采样数据会保存在mProcessStats这个变量中
        mService.updateCpuStatsNow();
    }

    final boolean isSilentAnr;
    // 进程正在处于正在关闭的状态,正在crash的状态,被kill的状态,或者相同进程已经处在ANR的流程中的进程直接返回。

    // 记录ANR到 event log
    EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,...;

    // 选择需要 dump 的进程。系统进程,LRU 进程和 Native 进程。

    // 收集 log 信息,输出 main log.
    StringBuilder info = new StringBuilder();
    info.setLength(0);
    info.append("ANR in ").append(app.processName);
    if (activity != null && activity.shortComponentName != null) {
        info.append(" (").append(activity.shortComponentName).append(")");
    }
    info.append("\n");
    info.append("PID: ").append(app.pid).append("\n");
    if (annotation != null) {
        info.append("Reason: ").append(annotation).append("\n");
    }
    if (parent != null && parent != activity) {
        info.append("Parent: ").append(parent.shortComponentName).append("\n");
    }

    StringBuilder report = new StringBuilder();
    report.append(MemoryPressureUtil.currentPsiState());
    ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);

    // 保存日志到 /data/anr 目录
    File tracesFile = ActivityManagerService.dumpStackTraces(true, firstPids,...);

    // 默认 true
    if (isMonitorCpuUsage()) {
        // 更新CPU使用信息。ANR的第二次CPU使用信息采样。两次采样的数据分别对应ANR发生前后的CPU使用情况
        mService.updateCpuStatsNow();
        synchronized (mService.mProcessCpuTracker) {
            // 输出ANR发生前一段时间内各个进程的CPU使用情况
            report.append(mService.mProcessCpuTracker.printCurrentState(anrTime));
        }
        // 输出CPU负载
        info.append(processCpuTracker.printCurrentLoad());
        info.append(report);
    }

    // 输出ANR发生后一段时间内各个进程的CPU使用率
    info.append(processCpuTracker.printCurrentState(anrTime));

    // 打印 ANR 日志
    Slog.e(TAG, info.toString());

    if (tracesFile == null) {
        // There is no trace file, so dump (only) the alleged culprit's threads to the log
        // 发送signal 3(SIGNAL_QUIT)来dump栈信息
        Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
    } else if (offsets[1] > 0) {
        // We've dumped into the trace file successfully
        mService.mProcessList.mAppExitInfoTracker.scheduleLogAnrTrace(
                pid, uid, getPackageList(), tracesFile, offsets[0], offsets[1]);
    }

    // 将anr信息同时输出到DropBox
    mService.addErrorToDropBox("anr", app, app.processName, ...);

    // 后台 anr 会结束进程。可以在开发者选项中打开后台 ANR
    if (isSilentAnr() && !isDebugging()) {
        kill("bg anr", ApplicationExitInfo.REASON_ANR, true);
        return;
    }

    // Bring up the infamous App Not Responding dialog
    // 显示ANR对话框。发送 SHOW_NOT_RESPONDING_MSG 消息,显示 anr 对话框
    Message msg = Message.obtain();
    msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
    msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem);

    mService.mUiHandler.sendMessage(msg);
}
public class ActivityManagerService{
    public static final String ANR_TRACE_DIR = "/data/anr";

    File dumpStackTraces(ArrayList<Integer> firstPids, ...){
        // 打印到 main log
        Slog.i(TAG, "dumpStackTraces pids=" + lastPids + " nativepids=" + nativePids);
        final File tracesDir = new File(ANR_TRACE_DIR);
        // 创建 ANR 文件,可能失败。以 yyyy-MM-dd-HH-mm-ss-SSS 格式命名。
        File tracesFile = createAnrDumpFile(tracesDir);
        Pair<Long, Long> offsets = dumpStackTraces(
                tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids);
        return tracesFile;
    }

    /**
    * @return The start/end offset of the trace of the very first PID
    */
    public static Pair<Long, Long> dumpStackTraces(String tracesFile, ArrayList<Integer> firstPids,
        ArrayList<Integer> nativePids, ArrayList<Integer> extraPids) {
        // 打印到 main log
        Slog.i(TAG, "Dumping to " + tracesFile);
        // We must complete all stack dumps within 20 seconds.
        long remainingTime = 20 * 1000;
        // First collect all of the stacks of the most important pids.
        if (firstPids != null) {
            // 首先收集重要进程的堆栈
        }

        // Next collect the stacks of the native pids
        if (nativePids != null) {
            // 再收集 Native 进程堆栈
        }

        // Lastly, dump stacks for all extra PIDs from the CPU tracker.
        if (extraPids != null) {
            // 最后收集其他进程堆栈信息
        }
    }
}
  1. 收集 ANR 信息最长 20 秒;
  2. 调用 Debug.dumpJavaBacktraceToFileTimeout() native 方法,按进程重要程度 dump 信息堆栈信息。
bool debuggerd_trigger_dump(pid_t tid, DebuggerdDumpType dump_type, unsigned int timeout_ms, unique_fd output_fd) {
    LOG(INFO) << TAG "started dumping process " << pid;

    // Send the signal.
    const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : BIONIC_SIGNAL_DEBUGGER;
    sigval val = {.sival_int = (dump_type == kDebuggerdNativeBacktrace) ? 1 : 0};
    if (sigqueue(pid, signal, val) != 0) {
        log_error(output_fd, errno, "failed to send signal to pid %d", pid);
        return false;
    }

    LOG(INFO) << TAG "done dumping process " << pid;
}

每一个应用进程都会有一个 SignalCatcher 线程,专门处理 SIGQUIT,来到 art/runtime/signal_catcher.cc

void* SignalCatcher::Run(void* arg) {
    SignalCatcher* signal_catcher = reinterpret_cast<SignalCatcher*>(arg);
    CHECK(signal_catcher != nullptr);

    Runtime* runtime = Runtime::Current();
    // ...
    // Set up mask with signals we want to handle.
    SignalSet signals;
    signals.Add(SIGQUIT);
    signals.Add(SIGUSR1);

    while (true) {
        int signal_number = signal_catcher->WaitForSignal(self, signals);
        if (signal_catcher->ShouldHalt()) {
        runtime->DetachCurrentThread();
        return nullptr;
        }

        switch (signal_number) {
        case SIGQUIT:
        signal_catcher->HandleSigQuit();
        break;
        case SIGUSR1:
        signal_catcher->HandleSigUsr1();
        break;
        default:
        LOG(ERROR) << "Unexpected signal %d" << signal_number;
        break;
        }
    }
}

当应用发生 ANR 之后,系统会收集许多进程,来 dump 堆栈,从而生成 ANR Trace 文件。收集的第一个,也是一定会被收集到的进程,就是发生 ANR 的进程。接着系统开始向这些应用进程发送 SIGQUIT 信号,应用进程收到 SIGQUIT 后开始 dump 堆栈。

参考

[1] developer ANRs
[2] Android ANR 分析详解
[3] 看完这篇 Android ANR 分析,就可以和面试官装逼了!
[4] 微信 Android 团队手把手教你高效监控 ANR
[5] Input 系统—ANR 原理分析 - Gityuan
[6] 彻底理解安卓应用无响应机制 - Gityuan
[7] 理解 Android ANR 的触发原理 - Gityuan

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容