美文网首页
Android ANR 机制

Android ANR 机制

作者: VanceKing | 来源:发表于2022-02-28 19:52 被阅读0次

    源码基于 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

    相关文章

      网友评论

          本文标题:Android ANR 机制

          本文链接:https://www.haomeiwen.com/subject/laxrrrtx.html