发生ANR之后的事情

本文深入解析Android系统的ANR(应用无响应)机制,探讨ANR触发条件与处理流程,包括四大组件启动流程、ANR记录及分析方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

触发ANR条件

  • Service Timeout:前台服务20s内未完成执行,后台服务超过200s
  • BroadcastQueue Timeout:前台广播在10s内未完成,后台广播超过60s未完成
  • ContentProvider Timeout:内容提供者,在publish过超时10s
  • InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件

对应最后执行ANR记录的方法是:

  • ActiveServices.serviceTimeout()
  • BroadcastQueue.AppNotResponding.run().appNotResponding()
  • AMS.appNotRespondingViaProvider()
  • AMS.inputDispatchingTimedOut()

理解为什么会出发ANR,必须明白四大组件的启动流程。这个我们以后分析。

分析ANR触发之后记录问题

可以将anr目录下的文件放到电脑上进行查看
adb pull data/anr .

属性系统可以通过adb shell getprop dalvik.vm.stack-trace-file这种方式查找对应的属性值

当触发ANR之后会调用AppErrors.appNotResponding()方法

final void appNotResponding(ProcessRecord app, ActivityRecord activity,
        ActivityRecord parent, boolean aboveSystem, final String annotation) {
        ArrayList<Integer> firstPids = new ArrayList<Integer>(5);
        SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20);
        ...
        //记录ANR时间
        long anrTime = SystemClock.uptimeMillis();
        //更新CPU状态
        if (ActivityManagerService.MONITOR_CPU_USAGE) {
            mService.updateCpuStatsNow();
        }
        //特定场景下忽略ANR
        synchronized (mService) {
            if (mService.mShuttingDown) {
                Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);
                return;
            } else if (app.notResponding) {
                Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);
                return;
            } else if (app.crashing) {
                Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
                return;
            }        
        }
        //为了防止多次对相同app的anr执行重复代码,在此处标注记录,属于上面的特定情况种的一种
        app.notResponding = true;
        //记录ANR信息到Event Log中
        EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
                app.processName, app.info.flags, annotation);        
        //添加当前app到firstpids列表中
        firstPids.add(app.pid);
        //如果可能添加父进程到firstpids列表种
        int parentPid = app.pid;
        ...
        // 将ANR信息存在info变量中,后续打印到LOGCAT,这部分的信息会以ActivityManager为Tag打印出来,包含了ANR的进程,出现原因以及当时的CPU状态,这些对分析ANR是非常重要的信息
        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");
        }
        //将ANR信息输出到traces文件,分为两种,一种带native层信息,一种不带
        ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true;
        String[] nativeProcs = NATIVE_STACKS_OF_INTEREST;
        // don't dump native PIDs for background ANRs
        File tracesFile = null;
        if (isSilentANR) {
            //这里返回了一个文件,这里的文件路径是:`/data/anr/traces.txt`
            //查找方法:adb shell getprop dalvik.vm.stack-trace-file
            tracesFile = mService.dumpStackTraces(true, firstPids, null, lastPids,
                null);
        } else {
            tracesFile = mService.dumpStackTraces(true, firstPids, processCpuTracker, lastPids,
                nativeProcs);
        }

        //再次更新CPU信息,并且输出到SystemLog中
        String cpuInfo = null;
        if (ActivityManagerService.MONITOR_CPU_USAGE) {
            mService.updateCpuStatsNow();
            synchronized (mService.mProcessCpuTracker) {
                cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
            }
            info.append(processCpuTracker.printCurrentLoad());
            info.append(cpuInfo);
        }

        info.append(processCpuTracker.printCurrentState(anrTime));
        Slog.e(TAG, info.toString());
        //上面的信息已经对应的ANR信息写入/data/anr/traces.txt中

        //给底层发送信号Process.SIGNAL_QUIT=3
        if (tracesFile == null) {
            Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
        }
        //将traces文件 和 CPU使用率信息保存到dropbox,即data/system/dropbox目录
        //命名:system_server/system_app/data_app + type+...比如下面
        //data_app_anr@1501989621992.txt.gz 
        //data_app_crash@1501989671926.txt
        mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
                cpuInfo, tracesFile, null);        

        synchronized (mService) {
            mService.mBatteryStatsService.noteProcessAnr(app.processName, app.uid);
            //如果是后台ANR则直接杀掉结束
            if (isSilentANR) {
                app.kill("bg anr", true);
                return;
            }
            //设置app的not响应状态,并查找errorReportReceiver
            makeAppNotRespondingLocked(app,
                    activity != null ? activity.shortComponentName : null,
                    annotation != null ? "ANR " + annotation : "ANR",
                    info.toString());

            //弹出ANR对话框
            Message msg = Message.obtain();
            HashMap<String, Object> map = new HashMap<String, Object>();
            msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
            msg.obj = map;
            msg.arg1 = aboveSystem ? 1 : 0;
            map.put("app", app);
            if (activity != null) {
                map.put("activity", activity);
            }
            //向ui线程发送,内容为SHOW_NOT_RESPONDING_MSG的消息
            mService.mUiHandler.sendMessage(msg);
        }
}

我们来小节一下上面发生了什么:

  • 立刻更新了CPU的信息
    java
    /** 2721 cpu (total|1|6),(user|1|6),(system|1|6),(iowait|1|6),(irq|1|6),(softirq|1|6) */
    public static final int CPU = 2721;
    给event_log中写入值
  • 忽略一些anr
  • 在event_log中打印am_anr的信息,这个是anr立刻发生的记录
  • 将ANR信息存在info变量中,后续打印到LOGCAT,这部分的信息会以ActivityManager为Tag打印出来,包含了ANR的进程,出现原因以及当时的CPU状态,这些对分析ANR是非常重要的信息
  • 将ANR信息输出到data/anr/traces文件
  • 没有输出到traces文件的时候,给底层发送一个rocess.SIGNAL_QUIT=3信号
  • 将traces文件 和 CPU使用率信息保存到dropbox,即data/system/dropbox目录
  • 如果是后台ANR则直接杀掉结束
  • 弹出ANR对话框

细节

怎么样就将信息保存到了/data/anr/traces.txt了

1.AMS.dumpStackTraces
public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids,
        ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
    //tracesPath = "data/anr/traces.txt"
    String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
    if (tracesPath == null || tracesPath.length() == 0) {
        return null;
    }

    File tracesFile = new File(tracesPath);
    try {
        if (clearTraces && tracesFile.exists()) tracesFile.delete();
        tracesFile.createNewFile();
        FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1); // -rw-rw-rw-
    } catch (IOException e) {
        Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesPath, e);
        return null;
    }
    //[2]
    dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
    return tracesFile;
}
2.
private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
        ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs){
    FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
        @Override
        public synchronized void onEvent(int event, String path) { notify(); }
    };

    try {
        observer.startWatching();

        // 获取发生ANR进程的pid,然后遍历这些进程给进程发送Process.SIGNAL_QUIT=3的信号
        if (firstPids != null) {
            try {
                int num = firstPids.size();
                for (int i = 0; i < num; i++) {
                    synchronized (observer) {
                        final long sime = SystemClock.elapsedRealtime();
                        Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
                        observer.wait(1000);  // Wait for write-close, give up after 1 sec
                    }
                }
            } catch (InterruptedException e) {
                Slog.wtf(TAG, e);
            }
        }

        // 接下来收集本地pids的堆栈
        if (nativeProcs != null) {
            int[] pids = Process.getPidsForCommands(nativeProcs);
            if (pids != null) {
                for (int pid : pids) {
                    final long sime = SystemClock.elapsedRealtime();
                    Debug.dumpNativeBacktraceToFileTimeout(pid, tracesPath, 10);//[3]输出native进程的trace并且限制超时时间
                }
            }
        }
        if (processCpuTracker != null) {
            processCpuTracker.init();
            System.gc();
            processCpuTracker.update();
            try {
                synchronized (processCpuTracker) {
                    processCpuTracker.wait(500); // measure over 1/2 second.
                }
            } catch (InterruptedException e) {
            }
            processCpuTracker.update();

            //从lastPids中选取CPU使用率 top 5的进程,输出这些进程的stacks
            final int N = processCpuTracker.countWorkingStats();
            int numProcs = 0;
            for (int i=0; i<N && numProcs<5; i++) {
                ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
                if (lastPids.indexOfKey(stats.pid) >= 0) {
                    numProcs++;
                    try {
                        synchronized (observer) {
                            final long stime = SystemClock.elapsedRealtime();
                            Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
                            observer.wait(1000);  // Wait for write-close, give up after 1 sec

                        }
                    } catch (InterruptedException e) {
                        Slog.wtf(TAG, e);
                    }
                } else if (DEBUG_ANR) {
                    Slog.d(TAG, "Skipping next CPU consuming process, not a java proc: "
                            + stats.pid);
                }
            }
        }
    } finally {
        observer.stopWatching();
    }
}

小结:

  • 收集发生anr进程的调用栈

    • 发生anr的进程
    • anr进程的父进程(anr进程是由于AMS生成,AMS在system_server进程中,system_server进程是anr的父进程)
    • mLruProcesses中所有的persistent进程
  • 收集Native进程的调用栈

    • "/system/bin/audioserver"
    • "/system/bin/cameraserver"
    • "/system/bin/drmserver"
    • "/system/bin/mediadrmserver"
    • "/system/bin/mediaserver"
    • "/system/bin/sdcard"
    • "/system/bin/surfaceflinger"
    • "media.codec" // system/bin/mediacodec
    • "media.extractor" // system/bin/mediaextractor
    • "com.android.bluetooth" // Bluetooth service
  • 收集lastPids进程的stacks
    • 收集前五名

注意收集信息等待的时间

3.Debug.dumpNativeBacktraceToFileTimeout()

static void android_os_Debug_dumpNativeBacktraceToFileTimeout(JNIEnv* env, jobject clazz,
    jint pid, jstring fileName, jint timeoutSecs)
{
    if (fileName == NULL) {
        jniThrowNullPointerException(env, "file == null");
        return;
    }
    const jchar* str = env->GetStringCritical(fileName, 0);
    String8 fileName8;
    if (str) {
        fileName8 = String8(reinterpret_cast<const char16_t*>(str),
                            env->GetStringLength(fileName));
        env->ReleaseStringCritical(fileName, str);
    }
    //打开文件(data/anr/traces.txt)
    int fd = open(fileName8.string(), O_CREAT | O_WRONLY | O_NOFOLLOW | O_CLOEXEC | O_APPEND, 0666);
    if (fd < 0) {
        fprintf(stderr, "Can't open %s: %s\n", fileName8.string(), strerror(errno));
        return;
    }

    dump_backtrace_to_file_timeout(pid, fd, timeoutSecs);//[4]

    close(fd);
}

4.dump_backtrace_to_file_timeout()

int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs) {
  //发送dump请求得到sock_fd
  int sock_fd = make_dump_request(DEBUGGER_ACTION_DUMP_BACKTRACE, tid, timeout_secs);
  if (sock_fd < 0) {
    return -1;
  }
  int result = 0;
  char buffer[1024];
  ssize_t n;
  int flag = 0;
  //从sock_fd中读取信息写入data/anr/traces.txt中
  while ((n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) {
    flag = 1;
    if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) {
      result = -1;
      break;
    }
  }
  close(sock_fd);
  ...
  return result;
}

主要是通过给底层发送DEBUGGER_ACTION_DUMP_BACKTRACE来请求dump的sock_fd句柄,底层调用dump_backtraces()来获取信息,从而写入data/anr/traces.txt文件中

总结

当发生anr的时候,距离ANR最近的时间是am_anr这个日志的时间,然后会打印各种信息有底层dump的,有进程的调用栈信息等等。最后将trances.txt写入data/system/dropbox目录下,并且重命名,规则见上文。

补充

其中Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);发出退出进程信号

<think>首先,用户的问题是关于ANR(Application Not Responding)日志分析的,具体是问如何通过ActivityManager日志评估系统负载情况。用户提供了几个引用,我需要参考这些来构建回答。 用户指令要求: - 所有行内数学表达式用$...$格式。 - 独立公式用$$...$$格式并单独成段。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 用户提供的引用: - [^1]: Android日志实战——ANR日志分析(四)。讨论ANR类型如KeyDispatchTimeout。 - [^2]: 【Android】ANR日志分析。提供ANR日志示例,包括输入分发超时。 - [^3]: Android ANR触发机制及日志分析。显示ANR日志片段。 - [^4]: 总结分析流程,包括搜索am_anr、查看CPU信息等。 用户的核心问题:在发生ANR时,如何通过ActivityManager日志分析系统负载。 我的回答需要: 1. **结构清晰**:逐步解释如何分析系统负载。 2. **参考引用**:自然地引用提供的材料。 3. **生成相关问题**:在回答结束后添加相关问题部分。 4. **使用中文**:整个回答用中文。 5. **数学格式**:如果有数学表达式,确保正确使用LaTeX。但在这个主题中,可能不需要太多数学,如果有的话,比如CPU使用率计算,我会用$...$。 关键点从引用中提取: - 引用[^4]提到分析流程:搜索am_anr,找到时间点、PID、ANR类型,然后搜索PID查看前5秒日志,过滤ANR IN查看CPU信息,接着看traces.txt。 - 系统负载可能涉及CPU、内存等资源。在ANR日志中,ActivityManager输出包括CPU使用情况。 - 例如,引用[^4]说:过滤ANR IN查看CPU信息。 分析系统负载的步骤: 1. **识别ANR事件**:在日志中搜索"am_anr"来找到ANR发生的时间点和进程。 2. **查看ANR详情**:日志会显示ANR类型(如KeyDispatchTimeout)、进程PID、和活动。 3. **分析前几秒日志**:围绕ANR时间点,查看前5秒左右的日志,特别是CPU使用情况。 4. **检查CPU信息**:在日志中,ActivityManager可能输出CPU使用率,如"CPU usage from ...",显示各个进程的CPU占用。 5. **评估系统负载**:高CPU使用率可能表示系统负载高,导致ANR。看是否有进程占用过多CPU。 6. **其他负载指标**:内存使用、IO等,但CPU是常见原因。 步骤化回答: - 步骤1: 定位ANR日志条目。 - 步骤2: 提取关键信息如PID和时间戳。 - 步骤3: 查看相关时间段的系统资源日志。 - 步骤4: 分析CPU使用情况。 - 步骤5: 评估负载并识别瓶颈。 引用自然融入: - 例如,在解释步骤时,说"如引用[^4]所述,..."。 生成相关问题: - 在回答结束后,添加"§§相关问题§§"部分,列出3-5个相关问题。 确保回答真实可靠:基于Android开发常识和提供的引用。 最终回答结构: 1. 引言:简要解释ANR和系统负载分析的重要性。 2. 逐步分析指南。 3. 总结。 4. 相关问题。</think>### ANR日志分析:如何通过ActivityManager日志评估系统负载 当Android应用发生ANR(Application Not Responding)时,ActivityManager日志是诊断系统负载的关键工具。系统负载过高(如CPU或内存资源紧张)常导致ANR事件,因为应用无法及时响应输入或广播。通过分析日志,您可以评估系统整体负载状况,识别资源瓶颈。以下是一个结构化的逐步指南,基于Android开发最佳实践和引用材料。整个过程需使用`adb logcat`或Android Studio的Logcat工具查看日志。 #### 步骤1: 定位ANR事件条目 首先,在日志中搜索ANR触发条目。ActivityManager会在ANR发生时输出"am_anr"标识,记录时间戳、进程PID、ANR类型和受影响的活动。例如: - 搜索关键词:`am_anr`。 - 示例日志输出(来自引用[^3]): ``` 05-23 10:21:38.029 2003 2016 E ActivityManager: ANR in com.example.demo (com.example.demo/.ui.login.LoginActivity) ``` 这里,`ANR in`后跟包名和活动名,`2003`是系统进程ID,`2016`是ANR进程的PID(本例中为32640)。ANR类型如`KeyDispatchTimeout`(输入事件超时)表示用户界面响应超时[^1]。记录下时间戳(如`05-23 10:21:38.029`)和PID,这些是后续分析的基础。 #### 步骤2: 提取关键信息并确定分析时间窗口 从ANR条目中提取PID和时间戳。ANR通常由前5秒的系统状态引起,因此需查看ANR发生前5秒左右的日志。例如: - 如果ANR发生在`10:21:38`,则分析`10:21:33`到`10:21:38`的日志。 - 使用日志工具过滤PID:例如,`adb logcat | grep <PID>`(将`<PID>`替换为实际值,如32640)。 - 日志可能显示ANR原因,如输入事件超时(引用[^2]): ``` Input dispatching timed out ... Waited 5001ms for MotionEvent ... policyFlags=0x6b000000 ``` 这表示UI线程阻塞超过5秒,可能与高系统负载相关。 #### 步骤3: 查看系统资源使用情况(CPU负载评估) 系统负载的核心指标是CPU使用率。ActivityManager在ANR前后会输出CPU使用详情,以"CPU usage from"开头。按以下步骤分析: 1. **过滤CPU日志**:搜索关键词`ANR IN`或`CPU usage from`,结合时间窗口。 - 示例命令:`adb logcat | grep -E "ANR IN|CPU usage from"`。 2. **解读CPU数据**:日志会列出各进程的CPU占用百分比。重点关注: - **总CPU使用率**:如果接近100%,表示系统整体负载高。 - **高占用进程**:查找占用CPU超过阈值(如>50%)的进程,尤其是系统进程或后台服务。 - **用户vs系统CPU**:日志区分`user`(应用层)和`kernel`(内核层)使用率,高`kernel`值可能表示IO或驱动问题。 示例日志片段(引用[^4]简化): ``` CPU usage from 12345ms to 0ms ago: 50% 1234/system_server: 30% user + 20% kernel 30% 5678/com.example.app: 25% user + 5% kernel ... ``` 这里: - `system_server`(系统进程)占50%,可能表示系统服务繁忙。 - 目标应用`com.example.app`占30%,如果这是ANR进程,高占用可能源于自身代码问题;否则,其他进程的高占用可能拖累系统。 - 计算公式:总负载 = $\sum \text{各进程CPU\%}$。如果总和使用率超过70-80%,系统可能过载[^4]。 3. **评估负载影响**: - **高负载指示**:如果多个进程CPU占用高,或`system_server`异常高,表明系统资源紧张,ANR可能由资源争抢引起。 - **低负载情况**:如果CPU使用率低(如<50%),ANR可能源于应用内部问题(如死锁),而非系统负载。 #### 步骤4: 结合其他日志验证负载原因 CPU是主要指标,但需检查相关日志以全面评估负载: - **内存使用**:搜索`Low memory`或`oom_adj`条目。高内存压力会导致频繁GC(垃圾回收),间接引发ANR。例如,日志中`ActivityManager`可能输出内存状态。 - **IO和线程阻塞**:查看`traces.txt`文件(通过`adb pull /data/anr/traces.txt`获取),分析ANR进程的堆栈。如果线程在IO操作(如文件读写)上阻塞,可能表示存储设备负载高。 - **系统事件**:过滤`ActivityManager`的其他输出,如`am_proc_die`(进程死亡)或`am_kill`(系统杀进程),这些可能在高负载时频繁出现。 #### 步骤5: 总结负载评估与优化建议 - **负载评估结论**:如果日志显示持续高CPU使用率(尤其在ANR前5秒),系统负载是ANR主因。否则,需排查应用代码。 - **优化建议**: - 减少后台进程:限制并发服务或使用`JobScheduler`。 - 监控工具:集成Android Profiler实时跟踪CPU/内存。 - 代码优化:异步处理耗时任务,避免主线程阻塞。 如引用[^4]总结,ANR日志分析流程始于"am_anr",扩展到CPU和堆栈检查,能揭示系统负载的来龙去脉[^4]。通过此方法,您可以高效诊断资源瓶颈。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值