Android 7系统异常问题排查(七)应用异常(下)—APK Java层崩溃

Android 7系统异常问题排查(七)应用异常(下)—APK Java层崩溃

系列目录:第一篇:异常机制全景图 | 第二篇:Kernel Panic 与系统重启 | 第三篇:Tombstone 机制 | 第四篇:System Server Watchdog | 第五篇:System Server 崩溃 | 第六篇:ANR 机制 | 第七篇:Java 层崩溃 | 第八篇:Trace 机制 | 第九篇:日志系统 | 第十篇:实战方法论


一、Java 层崩溃:用户最熟悉的异常

“抱歉,XXX 已停止运行”——这是 Android 用户最熟悉的对话框之一,也是 App 开发者最常处理的异常。与 ANR(主线程不响应)不同,Java 崩溃是主线程或子线程遇到了未捕获的异常,进程直接终止

崩溃 vs ANR 对比

维度ANRJava Crash
触发原因主线程长时间阻塞未捕获异常(NPE、数组越界等)
用户感知"应用无响应"对话框"已停止运行"对话框
进程状态仍存活但无响应进程被系统杀死
日志traces.txt + dropbox(anr)dropbox(crash) + logcat
恢复方式用户选择"等待"或"确定"用户重新打开 App

二、崩溃处理链路全景

2.1 从异常抛出到进程终止

App 代码抛出异常(如 NullPointerException) │ ▼ JVM 沿调用栈向上查找 catch 块 │ ├─ 找到匹配的 catch → 异常被捕获 → 正常处理(不崩溃) │ └─ 未找到 catch → 到达线程顶层 │ ▼ Thread.dispatchUncaughtException() │ ▼ ThreadGroup.uncaughtException() │ ▼ RuntimeInit.KillApplicationHandler.uncaughtException() │ ├─ 1. 打印 FATAL EXCEPTION 到 logcat ├─ 2. 通知 AMS.handleApplicationCrash() │ └─ AMS 收集 CrashInfo → 写入 DropBox │ └─ AMS 弹出 FC 对话框(AppErrorDialog) ├─ 3. Process.killProcess(Process.myPid()) └─ 4. System.exit(10)

2.2 关键源码:KillApplicationHandler

// frameworks/base/core/java/com/android/internal/os/RuntimeInit.javaprivatestaticclassKillApplicationHandlerimplementsThread.UncaughtExceptionHandler{@OverridepublicvoiduncaughtException(Threadt,Throwablee){try{// 防止递归崩溃(handler 自身异常)if(mCrashing)return;mCrashing=true;// 1. 确保异常信息记录到 logcatensureLogging(t,e);// 2. 通知 AMSif(mApplicationObject!=null){ActivityManagerNative.getDefault().handleApplicationCrash(mApplicationObject,newApplicationErrorReport.CrashInfo(e));}}catch(Throwablet2){// 连 AMS 都联系不上时,至少保留日志if(t2instanceofDeadObjectException){// System Server 也在崩溃,放弃通知}}finally{// 3. 杀死当前进程Process.killProcess(Process.myPid());System.exit(10);}}}

三、AMS 端处理流程

3.1 handleApplicationCrash()

// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.javapublicvoidhandleApplicationCrash(IBinderapp,ApplicationErrorReport.CrashInfocrashInfo){// 1. 找到崩溃进程的信息ProcessRecordr=findAppProcess(app,"Crash");finalStringprocessName=app==null?"system_server":r==null?"unknown":r.processName;// 2. 写入 DropBoxManagerServiceaddErrorToDropBox("crash",r,processName,null,null,null,null,null,crashInfo);// 3. 处理崩溃进程的关闭crashApplication(r,crashInfo);}voidcrashApplication(ProcessRecordr,ApplicationErrorReport.CrashInfocrashInfo){// 记录崩溃时间r.crashing=true;r.crashCount++;// 决定是否弹出 FC 对话框if(r.crashCount<=2){// 前两次崩溃:弹出对话框让用户知道Messagemsg=mHandler.obtainMessage(SHOW_ERROR_MSG);msg.obj=newAppErrorResult.Data(r,/* ... */);mHandler.sendMessage(msg);}else{// 连续多次崩溃:不再弹框(避免骚扰用户)// 直接杀进程}}

3.2 FC 对话框(AppErrorDialog)

// frameworks/base/services/core/java/com/android/server/am/AppErrorDialog.javaclassAppErrorDialogextendsBaseErrorDialog{// 显示内容:// 标题:应用名// 内容:"抱歉,XXX 已停止运行。"// 按钮:[确定]protectedvoidonCreate(BundlesavedInstanceState){// 从 ProcessRecord 和 CrashInfo 中提取信息setTitle(mProc.info.loadLabel(mContext.getPackageManager()));setMessage(mContext.getString(com.android.internal.R.string.application_error,mProc.processName));setButton(DialogInterface.BUTTON_POSITIVE,mContext.getText(com.android.internal.R.string.ok),...);}}

3.3 crashCount 保护机制

第一次崩溃 → 弹出 FC 对话框 第二次崩溃 → 弹出 FC 对话框 第三次及以上 → 不再弹框,静默杀进程 设计目的:防止 App 启动即崩溃,反复弹框骚扰用户

四、Crash 日志解读

4.1 logcat 中的 FATAL EXCEPTION

E/AndroidRuntime(12345): FATAL EXCEPTION: main E/AndroidRuntime(12345): Process: com.example.app, PID: 12345 E/AndroidRuntime(12345): java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference E/AndroidRuntime(12345): at com.example.app.MainActivity.loadData(MainActivity.java:42) E/AndroidRuntime(12345): at com.example.app.MainActivity.onCreate(MainActivity.java:28) E/AndroidRuntime(12345): at android.app.Activity.performCreate(Activity.java:5990) E/AndroidRuntime(12345): ...

格式解析

异常类名:异常消息 at 包名.类名.方法名(文件名:行号) ... Caused by: 异常类名:异常消息 ← 异常链(如果有) at ...

4.2 DropBox 中的 Crash 信息

adb shell dumpsys dropbox data_app_crash--print

输出包含更完整的信息:

Tag: data_app_crash Process: com.example.app Flags: 0x28... Package: com.example.app Subject: com.example.app Build: Android/aosp_arm64/generic:7.0/... java.lang.NullPointerException: Attempt to invoke ... at com.example.app.MainActivity.loadData(MainActivity.java:42) ...

4.3 混淆后的堆栈还原

生产环境的 APK 通常经过 ProGuard/R8 混淆,堆栈中显示的是混淆后的类名和方法名:

at a.b.c.a(Unknown Source)

需要配合mapping.txt文件还原:

# 使用 retrace 工具还原retrace mapping.txt stacktrace.txt>decoded.txt# 或者使用 Android SDK 自带的 proguardgui# 工具路径:sdk/tools/proguard/bin/proguardgui.sh

五、常见崩溃类型速查

5.1 NullPointerException

日志特征:NullPointerException: Attempt to invoke ... on a null object reference 常见原因: - 对象未初始化就使用 - findViewById() 返回 null(布局 ID 不匹配) - getIntent().getExtras() 返回 null - 多线程并发导致的空指针 定位方法: - 从堆栈第一行找到变量名 - 前向追踪该变量的赋值路径

5.2 ClassCastException

日志特征:ClassCastException: XXX cannot be cast to YYY 常见原因: - Fragment 类型转换错误 - Context 类型转换错误 - 泛型擦除导致的类型错误 定位方法: - 检查 XML 中声明的 Fragment 类名 - 检查 getSystemService() 的传入参数

5.3 IndexOutOfBoundsException

日志特征:IndexOutOfBoundsException: Invalid index X, size is Y 常见原因: - 空列表直接 get(0) - 多线程并发修改集合 - RecyclerView 的 Adapter 数据与 UI 不同步 定位方法: - 堆栈行号定位到具体操作 - 检查该列表的数据来源和修改时机

5.4 WindowManager.BadTokenException

日志特征:BadTokenException: Unable to add window -- token null is not valid 常见原因: - Activity 已销毁后尝试 show Dialog - 使用 ApplicationContext 启动 Dialog - 在 onCreate() 中尝试 PopupWindow 定位方法: - Dialog.show() 必须在 Activity 可见时调用 - 或使用 DialogFragment 管理生命周期

5.5 DeadObjectException / RemoteException

日志特征:DeadObjectException / RemoteException 常见原因: - 跨进程调用时对端进程已死亡 - Service 被系统杀死后仍尝试调用 - Binder 连接断开 定位方法: - 检查是否在 ServiceConnection.onServiceDisconnected() 中做了清理 - 使用 DeathRecipient 监听 Binder 死亡

5.6 SecurityException

日志特征:SecurityException: Permission Denial 常见原因: - AndroidManifest.xml 缺少权限声明 - Android 6.0+ 运行时权限未授予 - 访问其他 App 的私有组件 定位方法: - 检查需要的权限是否声明 - 检查运行时权限申请流程

六、App Native Crash

App 通过 JNI 调用的 Native 代码崩溃时,处理流程与纯 Native 进程类似:

6.1 识别 App Native Crash

logcat 特征: 05-01 12:00:00.000 12345 12345 F libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 12345 (com.example.app) 05-01 12:00:00.500 12345 12345 F DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 05-01 12:00:00.500 12345 12345 F DEBUG: pid: 12345, tid: 12345, name: com.example.app >>> com.example.app <<< 05-01 12:00:00.500 12345 12345 F DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0

6.2 Tombstone 中识别 App

在 tombstone 中通过以下字段区分是系统进程还是 App:

pid: 12345, tid: 12345, name: com.example.app >>> com.example.app <<<

App 的进程名格式通常是包名(如com.example.app),与系统进程(如surfaceflingermediaserver)的命名风格不同。

6.3 App Native Crash 的特点

  • 也会生成 tombstone 文件
  • AMS 的处理依赖于 KillApplicationHandler 能否正常执行(可能来不及)
  • 与 Java Crash 一样会弹出 FC 对话框(如果 AMS 来得及处理)

七、第三方崩溃收集原理

很多 App 会集成崩溃收集 SDK(如 Bugly、Firebase Crashlytics),其原理是利用UncaughtExceptionHandler的替换机制:

// 典型的三方崩溃收集原理publicclassCrashHandlerimplementsThread.UncaughtExceptionHandler{privateThread.UncaughtExceptionHandlermDefaultHandler;publicvoidinit(Contextcontext){// 1. 保存系统默认 handlermDefaultHandler=Thread.getDefaultUncaughtExceptionHandler();// 2. 替换为自己的 handlerThread.setDefaultUncaughtExceptionHandler(this);}@OverridepublicvoiduncaughtException(Threadt,Throwablee){// 3. 先收集崩溃信息(上传到服务器)collectCrashInfo(e);uploadToServer();// 4. 再交给系统默认 handler 处理// 不调用这行 = FC 对话框不弹出,但进程也不会被系统杀// 需要自己 Process.killProcess()if(mDefaultHandler!=null){mDefaultHandler.uncaughtException(t,e);}else{Process.killProcess(Process.myPid());}}}

关键注意事项

  • 必须在mDefaultHandler.uncaughtException()之前完成信息收集和上传
  • 上传操作必须是同步的(因为进程即将死亡)
  • 如果 handler 本身抛异常,会导致系统 handler 不被调用

八、实战定位流程

1. 确认崩溃类型 → logcat 搜索 "FATAL EXCEPTION" → 或 "Fatal signal"(Native crash) 2. 提取崩溃信息 → 异常类名 + 消息 → 崩溃线程名(main? 子线程?) → 堆栈顶部的 App 代码行 3. DropBox 补充信息 → dumpsys dropbox data_app_crash --print 4. 混淆还原(如果必要) → retrace mapping.txt stacktrace.txt 5. 分析根因 → 空指针:追溯变量的初始化和赋值 → 类型转换:检查实际的运行时类型 → 数组越界:检查索引计算逻辑 → 权限问题:确认 AndroidManifest 和运行时权限 6. 修复验证 → 编写单元测试覆盖崩溃路径 → 在相同条件下复现验证

九、总结

  1. KillApplicationHandler 是所有 Java 崩溃的统一出口:异常到达线程顶层 → handler 收集信息 → 通知 AMS → 杀死进程。
  2. FC 对话框由系统端控制:App 只负责死亡,AMS 负责弹框和崩溃计数。
  3. crashCount 保护:连续崩溃超过 2 次后不再弹框,避免骚扰用户。
  4. 混淆堆栈需要 mapping.txt 还原:集成发布流程中务必保留 mapping 文件。
  5. App Native Crash 同样生成 tombstone:但 App 开发者通常直接用 logcat 定位,tombstone 作为补充。
  6. 三方崩溃 SDK 通过替换 UncaughtExceptionHandler 实现:先收集后转交,确保系统默认流程不被中断。

本文基于 AOSP 7(Android Nougat)源码编写。下一篇将介绍系统追踪工具——Trace 机制与性能诊断。