第一讲:崩溃问题初探——ANR、Force Close 与 logcat 定位技巧

各位同学好,我是你们的讲师。今天咱们正式进入《Android系统稳定性问题实战排查》的第一课。

说到系统稳定性,大家最先想到的是什么?我猜十有八九是「崩溃」。没错,App 闪退、系统卡死、弹窗提示「应用无响应」——这些就是咱们今天要聊的主角:ANRForce Close

我个人习惯把这两类问题称为「稳定性双煞」。你想想看,用户正刷着短视频、付着款、打着游戏,突然屏幕卡住或者直接闪退,那体验得多糟糕。所以,搞懂它们是什么、怎么快速定位,是每个 Android 工程师的必修课。

一、什么是 ANR?

ANR 的全称是 Application Not Responding,翻译过来就是「应用无响应」。说白了,就是系统觉得你的 App 太忙了,忙到连用户的操作都顾不上处理。

系统是怎么判断的呢?它有一个超时机制。当以下四种情况发生时,系统会触发 ANR:

  • 主线程(UI 线程) 在 5 秒内没有处理完输入事件(比如点击、触摸)
  • BroadcastReceiver 在 10 秒内没有执行完 onReceive()
  • Service 在 20 秒内没有执行完 onCreate()、onStartCommand() 或 onBind()
  • ContentProvider 在 10 秒内没有完成 publish

嗯,这里要注意:ANR 不是崩溃,它只是系统在告诉你「你的 App 卡住了」。但用户看到的是弹窗,体验上跟崩溃差不多。

核心要点:ANR 的本质是主线程被阻塞。只要主线程在做耗时操作(网络请求、大文件读写、复杂计算),就随时可能触发 ANR。

我在项目中遇到过好几次 ANR,印象最深的一次是某个第三方 SDK 在 onReceive() 里做了数据库批量插入,结果广播超时直接弹窗。后来我们加了个异步线程,问题就解决了。

二、什么是 Force Close?

Force Close,简称 FC,就是咱们常说的「闪退」或「强制关闭」。它跟 ANR 不同,ANR 是系统主动弹窗,而 FC 是 App 自己挂了——通常是抛出了一个未捕获的异常

最常见的 FC 原因有哪些?我列几个:

  • NullPointerException(空指针)—— 这玩意儿排第一,几乎每个 Android 开发都踩过坑
  • ClassCastException(类型转换异常)
  • IndexOutOfBoundsException(数组越界)
  • Resources.NotFoundException(资源找不到)
  • SecurityException(权限问题)

为什么会发生 FC?因为 Android 应用运行在 Dalvik 或 ART 虚拟机 上,当代码抛出异常且没有被 try-catch 捕获时,虚拟机会把异常抛给默认的 UncaughtExceptionHandler,然后直接终止进程。

避坑指南:我曾经接手过一个项目,线上崩溃率高达 3%。排查后发现,大部分 FC 都是因为「在子线程更新 UI」导致的。记住:Android 不允许在非 UI 线程操作 View,否则必崩。

三、如何从 logcat 中快速定位第一行异常堆栈?

好了,理论讲完了,咱们来点实战的。遇到崩溃问题,第一件事就是看 logcat。但 logcat 日志动辄几千行,怎么快速找到关键信息?

我个人习惯用以下三个步骤:

  1. 过滤关键字:在 logcat 中搜索 FATAL EXCEPTIONANR。这两个关键字是崩溃和 ANR 的「身份证」。
  2. 找到第一行堆栈:异常堆栈通常以 java.lang.XXXException 开头,比如 java.lang.NullPointerException。这一行就是异常类型和描述。
  3. 看调用链:从第一行往下看,找到你代码中的包名路径。系统框架的堆栈可以忽略,重点关注 com.yourcompany.xxx 开头的行。

举个例子,假设 logcat 中有这样一段:

2025-01-15 10:23:45.678 12345-12345/com.example.app E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.app, PID: 12345
    java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
        at com.example.app.MainActivity.onCreate(MainActivity.java:25)
        at android.app.Activity.performCreate(Activity.java:7890)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3456)
        ... 15 more

看到没?第一行是 FATAL EXCEPTION: main,说明是主线程崩溃。第二行是进程信息。第三行是异常类型和描述——NullPointerException,说你在一个空对象上调用了 setText()。再往下看,MainActivity.java:25 就是出问题的代码位置。

小技巧:如果你用 Android Studio,可以直接在 logcat 窗口输入 FATAL 过滤。或者用命令行:adb logcat -s "AndroidRuntime:E",只显示崩溃日志。

对于 ANR,logcat 中会输出类似这样的信息:

2025-01-15 10:25:00.123 12345-12345/com.example.app I/ActivityManager: ANR in com.example.app
    PID: 12345
    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.)
    Load: 12.34 / 8.56 / 5.67
    CPU usage from 0ms to 5000ms later:
    100% 12345/com.example.app: 99% user + 1% kernel / faults: 1234 minor 5 major

这里的关键信息是 ReasonCPU usage。Reason 告诉你为什么 ANR,CPU usage 告诉你哪个线程在吃 CPU。如果看到某个线程 CPU 占用 99%,那基本就是它干的。

四、实战排查思路总结

好了,咱们来捋一捋今天的重点:

问题类型 触发条件 logcat 关键字 第一行定位方法
ANR 主线程超时(5s/10s/20s) ANR in 看 Reason 和 CPU usage
Force Close 未捕获的异常 FATAL EXCEPTION 看异常类型和堆栈第一行

我个人建议,遇到崩溃问题先别慌。打开 logcat,搜 FATALANR,找到第一行异常堆栈,然后顺着调用链往下查。80% 的问题都能在 5 分钟内定位到根因。

我记得刚入行那会儿,看到 logcat 里密密麻麻的日志就头大。后来带我的老工程师说了一句话,我一直记着:「别被日志吓到,你只需要找到那个 'FATAL' 或者 'ANR',剩下的都是线索。」

嗯,今天就先讲到这里。下一讲咱们会深入分析 ANR 的 trace 文件,教你怎么从 /data/anr/traces.txt 里挖出更多信息。到时候见。