Android Framework 异常调用栈解析
本文分析基于 Android 13 (T) 系统。异常是程序未按预设逻辑运行的一种提示,Java 中的异常输出通常包含一句提示语和其发生时的调用栈。多数情况下,这些提示是直接且清晰的。但如果我们将异常捕获后封装一下重新抛出,或者让它发生在跨进程通信的过程中,那么此时的调用栈信息将会变得复杂,甚至会干扰我们对最终原因的判断。以下将详解几种不同形式的异常调用栈。
1. 异常捕获后重新抛出
以下是剥离了时间、pid、tid 和 tag 后的输出示例:
*** FATAL EXCEPTION IN SYSTEM PROCESS: main
java.lang.RuntimeException: Error receiving broadcast Intent { act=android.intent.action.NEW_OUTGOING_CALL flg=0x11000010 (has extras) } in com.android.server.location.injector.SystemEmergencyHelper$1@42d2813
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$android-app-LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1800)
at android.app.LoadedApk$ReceiverDispatcher$Args$$ExternalSyntheticLambda0.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at com.android.server.SystemServer.run(SystemServer.java:966)
at com.android.server.SystemServer.main(SystemServer.java:651)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:920)
Caused by: java.lang.IllegalStateException: telephony service is null.
at android.telephony.TelephonyManager.isEmergencyNumber(TelephonyManager.java:14136)
at com.android.server.location.injector.SystemEmergencyHelper$1.onReceive(SystemEmergencyHelper.java:70)
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$android-app-LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1790)
... 10 more
可以发现其中有两段不同的调用栈,由 "Caused by" 字段进行分隔。结合以下代码,我们可以分析出此异常的转换过程:广播处理会进入到 receiver.onReceive 中,其中发生了 "telephony service is null" 的 IllegalStateException。此异常向上抛出,最终被如下代码的 1791 行捕获。捕获之后的异常会在 1800 行进行重新封装,原始异常 e 将会作为第二个参数参与 RuntimeException 的构造(赋值给 cause 字段)。因此,这个 RuntimeException 是导致进程退出的直接原因,而原始异常 IllegalStateException 则是根本原因。
1781 try {
1782 ClassLoader cl = mReceiver.getClass().getClassLoader();
1783 intent.setExtrasClassLoader(cl);
1784 // TODO: determine at registration time if caller is
1785 // protecting themselves with signature permission
1786 intent.prepareToEnterProcess(ActivityThread.isProtectedBroadcast(intent),
1787 mContext.getAttributionSource());
1788 setExtrasClassLoader(cl);
1789 receiver.setPendingResult(this);
1790 receiver.onReceive(mContext, intent);
1791 } catch (Exception e) {
1792 if (mRegistered && ordered) {
1793 if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
1794 "Finishing failed broadcast to " + mReceiver);
1795 sendFinished(mgr);
1796 }
1797 if (mInstrumentation == null ||
1798 !mInstrumentation.onException(mReceiver, e)) {
1799 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
1800 throw new RuntimeException(
1801 "Error receiving broadcast " + intent
1802 + " in " + mReceiver, e);
1803 }
1804 }
306 public Throwable(String message, Throwable cause) { //第二个参数赋值给 cause 字段
307 fillInStackTrace();
308 detailMessage = message;
309 this.cause = cause;
310 }
从调用栈的打印来看,它会首先将直接导致崩溃的异常调用栈打印出来,之后会递归地将 cause 的异常调用栈打印出来(因为 cause 也可能有自己的 cause)。
另外需要注意的是,IllegalStateException 的调用栈最下方有 "… 10 more" 的字样。它表示的其实就是 RuntimeException 的调用栈(除去最后一帧)。因为异常在向上抛出的过程中被捕获,因此捕获位置往上的调用栈是不变的。我们把这 10 帧补齐,IllegalStateException 的完整调用栈便如下所示:
Caused by: java.lang.IllegalStateException: telephony service is null.
at android.telephony.TelephonyManager.isEmergencyNumber(TelephonyManager.java:14136)
at com.android.server.location.injector.SystemEmergencyHelper$1.onReceive(SystemEmergencyHelper.java:70)
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$android-app-LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1790)
at android.app.LoadedApk$ReceiverDispatcher$Args$$ExternalSyntheticLambda0.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at com.android.server.SystemServer.run(SystemServer.java:966)
at com.android.server.SystemServer.main(SystemServer.java:651)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:920)
2. Binder 同步通信时发生的异常
以下是剥离了时间、pid、tid 和 tag 后的输出:
FATAL EXCEPTION: main
PID: 3264
java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.hashCode()' on a null object reference
at android.os.Parcel.createExceptionOrNull(Parcel.java:3017)
at android.os.Parcel.createException(Parcel.java:2995)
at android.os.Parcel.readException(Parcel.java:2978)
at android.os.Parcel.readException(Parcel.java:2920)
at android.app.IActivityManager$Stub$Proxy.attachApplication(IActivityManager.java:5148)
at android.app.ActivityThread.attach(ActivityThread.java:7644)
at android.app.ActivityThread.main(ActivityThread.java:7943)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:942)
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.server.am.HostingRecord.getHostingTypeIdStatsd(HostingRecord.java:234)
at com.android.server.am.ActivityManagerService.attachApplicationLocked(ActivityManagerService.java:5102)
at com.android.server.am.ActivityManagerService.attachApplication(ActivityManagerService.java:5115)
at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2339)
at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2655)
这种调用栈中有 Parcel.readException 字样,且 "Caused by" 后面跟的是 "Remote stack trace",它们通常是由 Binder 同步通信时对端进程中的异常所导致。对端进程发生的异常拆分为 3 个部分,序列化地发回给本进程:
- code:表示该异常的类型
- msg:异常的具体描述
- remoteStackTrace:异常发生时的调用栈
这 3 部分信息在本进程中组合成了两个 Exception 对象。一个由 code 和 msg 构造,如下 2978 行所示,它是造成进程退出的直接原因;另一个由 remoteStackTrace 构造,如下 2981 行所示,它是造成进程退出的根本原因(2983 行将它赋值给 e 的 cause)。
2972 public final void readException(int code, String msg) {
2973 String remoteStackTrace = null;
2974 final int remoteStackPayloadSize = readInt();
2975 if (remoteStackPayloadSize > 0) {
2976 remoteStackTrace = readString();
2977 }
2978 Exception e = createException(code, msg);
2979 // Attach remote stack trace if availalble
2980 if (remoteStackTrace != null) {
2981 RemoteException cause = new RemoteException(
2982 "Remote stack trace:\n" + remoteStackTrace, null, false, false);
2983 ExceptionUtils.appendCause(e, cause);
2984 }
2985 SneakyThrow.sneakyThrow(e);
2986 }
回到上面这个例子,它真实的含义是:本 App 进程希望通过 attachApplication 接口和 system_server 进程通信,但是 system_server 在处理这个请求时,发生了 NullPointerException。System_server 将这个异常发回给 App 进程,最终导致了 App 进程的退出。
其实我觉得现有的调用栈输出是有瑕疵的。它将原本属于同一个异常的 msg 和 stackTrace 拆分开来,会给开发者带来困扰。按照正确的理解,上面的调用栈显示为如下格式会更加清晰:
android.os.RemoteException: Binder transaction failed
at android.os.Parcel.createExceptionOrNull(Parcel.java:3017)
at android.os.Parcel.createException(Parcel.java:2995)
at android.os.Parcel.readException(Parcel.java:2978)
at android.os.Parcel.readException(Parcel.java:2920)
at android.app.IActivityManager$Stub$Proxy.attachApplication(IActivityManager.java:5148)
at android.app.ActivityThread.attach(ActivityThread.java:7644)
at android.app.ActivityThread.main(ActivityThread.java:7943)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:942)
Caused by: java.lang.NullPointerException in remote process: Attempt to invoke virtual method 'int java.lang.String.hashCode()' on a null object reference
at com.android.server.am.HostingRecord.getHostingTypeIdStatsd(HostingRecord.java:234)
at com.android.server.am.ActivityManagerService.attachApplicationLocked(ActivityManagerService.java:5102)
at com.android.server.am.ActivityManagerService.attachApplication(ActivityManagerService.java:5115)
at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2339)
at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2655)
不过需要注意,并非对端进程处理 binder 通信时发生的任何异常都可以传回,只有如下这 9 类异常可以:
| Exception | Code |
|---|---|
| Parcelable Exceptions in BootClassLoader | EX_PARCELABLE |
| SecurityException | EX_SECURITY |
| BadParcelableException | EX_BAD_PARCELABLE |
| IllegalArgumentException | EX_ILLEGAL_ARGUMENT |
| NullPointerException | EX_NULL_POINTER |
| IllegalStateException | EX_ILLEGAL_STATE |
| NetworkOnMainThreadException | EX_NETWORK_MAIN_THREAD |
| UnsupportedOperationException | EX_UNSUPPORTED_OPERATION |
| ServiceSpecificException | EX_SERVICE_SPECIFIC |
当对端进程将异常传回后,对端进程恢复正常。仔细思考这样设计也是很合理的。作为 Server 进程,它在什么时候执行,该执行些什么都不由自己掌控,而是由 Client 进程发起。因此抛出异常本质上与 Client 进程相关,让一个 Client 进程的行为导致 Server 进程退出显然是不合理的。此外,Server 进程可能关联着多个 Client,不能由于一个 Client 的错误行为而影响本可以正常获取服务的其他 Client。
除了上述 9 种异常以外,其余的异常将由对端进程的 JavaBBinder::onTransact 来处理,最终会通过 LOGE 将该异常输出。值得注意的是,异常中的 Exception 输出完后进程恢复,而 Error 则会导致进程退出。
410 jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
411 code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);
412
413 if (env->ExceptionCheck()) {
414 ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred());
415 binder_report_exception(env, excep.get(),
416 "*** Uncaught remote exception! "
417 "(Exceptions are not yet supported across processes.)");
418 res = JNI_FALSE;
419 }
*** Uncaught remote exception! (Exceptions are not yet supported across processes.)
java.lang.OutOfMemoryError: Failed to allocate a 280361534 byte allocation with 25165820 free bytes and 258MB until OOM, target footprint 29165820, growth limit 536870912
at java.util.Arrays.copyOf(Arrays.java:3136)
at java.util.Arrays.copyOf(Arrays.java:3106)
at java.util.ArrayList.grow(ArrayList.java:275)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:249)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:241)
at java.util.ArrayList.add(ArrayList.java:467)
at android.os.Parcel.readStringList(Parcel.java:3093)
at android.content.IntentFilter.<init>(IntentFilter.java:2377)
at android.content.IntentFilter$1.createFromParcel(IntentFilter.java:2269)
at android.content.IntentFilter$1.createFromParcel(IntentFilter.java:2267)
at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2241)
at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2669)
at android.os.Binder.execTransactInternal(Binder.java:1221)
at android.os.Binder.execTransact(Binder.java:1163)
3. Binder 异步通信时发生的异常
对于普通的异步通信,Client 进程发送完后就不会再管了,所以 Server 端在收到通信后处理时发生的异常不会回传。最终所有的异常都会交由 JavaBBinder::onTransact 进行处理,处理的原则和上面一样:Exception 输出完后进程恢复,Error 则会导致进程退出。
不过有一类 Binder 异步通信的异常非常隐晦,如果不了解内部原理基本无法理解。示例如下:
FATAL EXCEPTION: Thread-3
Process: com.android.systemui, PID: 31695
java.lang.RuntimeException: Error receiving broadcast Intent { act=android.bluetooth.device.action.BOND_STATE_CHANGED flg=0x10 (has extras) } in com.android.bluetooth.BluetoothManager$BluetoothBroadcastReceiver@c5b7352
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$android-app-LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1920)
at android.app.LoadedApk$ReceiverDispatcher$Args$$ExternalSyntheticLambda0.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:240)
at android.os.Looper.loop(Looper.java:351)
at android.os.HandlerThread.run(HandlerThread.java:67)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Looper android.os.HandlerThread.getLooper()' on a null object reference
at com.android.bluetooth.a2dp.A2dpService.getOrCreateStateMachine(A2dpService.java:2101)
at com.android.bluetooth.a2dp.A2dpService.connect(A2dpService.java:515)
at com.android.bluetooth.btservice.AdapterService.connectEnabledProfiles(AdapterService.java:1548)
at com.android.bluetooth.btservice.AdapterService.connectAllEnabledProfiles(AdapterService.java:4962)
at com.android.bluetooth.btservice.AdapterService$AdapterServiceBinder.connectAllEnabledProfiles(AdapterService.java:3076)
at com.android.bluetooth.btservice.AdapterService$AdapterServiceBinder.connectAllEnabledProfiles(AdapterService.java:3057)
at android.bluetooth.IBluetooth$Stub.onTransact(IBluetooth.java:1750)
at android.os.Binder.execTransactInternal(Binder.java:1331)
at android.os.Binder.execTransact(Binder.java:1268)
其实同步通信除了通过 Binder 同步模式实现,还可以通过两个 Binder 异步通信实现。而这也正是上面调用栈形成的原因。Systemui 进程接收到广播后,会执行相应广播的 onReceive 方法。此次广播处理会尝试连接蓝牙。而异常发生的关键点,就在如下代码中:
final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
service.connectAllEnabledProfiles(this, mAttributionSource, recv);
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
connectAllEnabledProfiles 是异步的 Binder 请求,它的通信对端是 com.android.bluetooth 进程。Systemui 发送完 connectAllEnabledProfiles 的异步请求后会继续往下执行,但是 awaitResultNoInterrupt 会将线程挂起,等待对端进程的回复。对端进程的回复同样是一个异步通信,这样程序便通过 SynchronousResultReceiver 和两次异步通信,模仿了同步通信的过程。
com.android.bluetooth 进程接收到异步请求后,会执行如下代码。如果程序没有异常,最终 receiver.send 会将返回值发回给 systemui 进程。但如果程序发生了 RuntimeException,receiver.propagateException 会将异常发回给 systemui。
public void connectAllEnabledProfiles(BluetoothDevice device,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
receiver.send(connectAllEnabledProfiles(device, source));
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
发回给 systemui 的异常最终会在哪里抛出呢?答案是上面代码的 getValue 方法中:
public T getValue(T defaultValue) {
if (mException != null) {
throw mException;
}
if (mObject == null) {
return defaultValue;
}
return mObject;
}
至此我们可以知道,上述调用栈中的 caused by 部分(截取如下)其实是 Binder 异步通信后对端进程(com.android.bluetooth)发生的异常。而整个调用栈中没有任何的 remote 字样,所以非常容易让人误以为是 systemui 进程中发生的异常。大家以后碰到这种调用栈时,一定要小心。
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Looper android.os.HandlerThread.getLooper()' on a null object reference
at com.android.bluetooth.a2dp.A2dpService.getOrCreateStateMachine(A2dpService.java:2101)
at com.android.bluetooth.a2dp.A2dpService.connect(A2dpService.java:515)
at com.android.bluetooth.btservice.AdapterService.connectEnabledProfiles(AdapterService.java:1548)
at com.android.bluetooth.btservice.AdapterService.connectAllEnabledProfiles(AdapterService.java:4962)
at com.android.bluetooth.btservice.AdapterService$AdapterServiceBinder.connectAllEnabledProfiles(AdapterService.java:3076)
at com.android.bluetooth.btservice.AdapterService$AdapterServiceBinder.connectAllEnabledProfiles(AdapterService.java:3057)
at android.bluetooth.IBluetooth$Stub.onTransact(IBluetooth.java:1750)
at android.os.Binder.execTransactInternal(Binder.java:1331)
at android.os.Binder.execTransact(Binder.java:1268)
4. 异常排查实战指南
在实际开发中,面对复杂的异常调用栈,建议遵循以下步骤进行排查:
- 识别异常类型:首先查看异常堆栈的最顶层,确定是直接崩溃还是远程异常。如果是 RemoteException 且包含 Remote stack trace,需立即定位到对端进程。
- 追踪 Cause 链:利用 Caused by 字段向下追溯,找到根本原因。注意忽略中间层的包装异常(如 RuntimeException 包裹),关注最底层的业务逻辑错误。
- 检查跨进程边界:遇到 Binder 调用失败时,区分同步与异步场景。特别是使用 SynchronousResultReceiver 等机制模拟同步时,务必确认对端进程是否已正确捕获并回传异常。
- 验证权限与状态:许多异常(如 IllegalStateException)源于服务未初始化或权限不足。检查 Service 生命周期及 Manifest 配置。
- 日志关联分析:结合 Logcat 中的 Tag 和时间戳,对比 SystemServer 与 Client 进程的日志,确认异常发生的时间顺序。
5. 总结
Android 系统的异常处理机制涉及本地 JVM 层与 Native 层、进程间通信等多个层面。理解异常调用栈的生成规则,特别是跨进程异常的回传限制与封装方式,对于系统级问题的定位至关重要。开发者应熟悉常见异常类型的传播路径,避免被表面现象误导,从而高效解决系统稳定性问题。

