Binder异常处理
Base on: Android 13
Branch: android-13.0.0_r30
跨进程异常
Android跨进程通讯中,只支持传递以下几种异常
- SecurityException
- BadParcelableException
- IllegalArgumentException
- NullPointerException
- IllegalStateException
- NetworkOnMainThreadException
- UnsupportedOperationException
- ServiceSpecificException
- 实现Parcelable的异常
如果进程A调用进程B中的方法,进程B抛出的是不支持跨进程的异常,则只会在进程B中处理,有可能导致B崩溃,但不会传递给进程A,并且会打印如下提示:
E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.)
Binder异常示例
以跨进程访问Service为例,看一下处理异常的流程。
客户端端访问服务端的方法,两边的代码分别如下,详细代码略。
客户端:
findViewById(R.id.func_a).setOnClickListener(v -> {
Log.d(TAG, "call funcA");
try {
iServiceStub.funcA();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
});
服务端:
class StubImpl extends IMyService.Stub {
String TAG = "MyService";
//...
@Override
public String funcB() {
Log.d(TAG, "Call funcB");
throw new RuntimeException();
// return "I am funcB";
}
}
由于RuntimeException不支持跨进程传递,所以在服务端抛出以下异常提示,但服务端并没有FC。
*** Uncaught remote exception! (Exceptions are not yet supported across processes.)
java.lang.RuntimeException
at com.demoapp.binderserverdemo.StubImpl.funcB(MyService.java:43)
at com.demoapp.binderserverdemo.IMyService$Stub.onTransact(IMyService.java:71)
at android.os.Binder.execTransactInternal(Binder.java:1285)
at android.os.Binder.execTransact(Binder.java:1249)
主要处理流程实际上在Binder.execTransactInternal()中。
服务端
Binder.execTransactInternal()
异常处理如下:
(1) 如果是oneway(异步)通信
- 服务端抛出RemoteException
日志:Log.w(TAG, “Binder call failed.”, e);
- 服务端抛出其它异常
日志:Log.w(TAG, “Caught a RuntimeException from the binder stub implementation.”, e);
(2) 非oneway(同步)通信
调用reply.writeException(e);将异常写入到返回值中。
try {
// ...
if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {
AppOpsManager.startNotedAppOpsCollection(callingUid);
try {
res = onTransact(code, data, reply, flags);
} finally {
AppOpsManager.finishNotedAppOpsCollection();
}
} else {
res = onTransact(code, data, reply, flags);
}
} catch (RemoteException|RuntimeException e) {
//...
if ((flags & FLAG_ONEWAY) != 0) {
if (e instanceof RemoteException) {
Log.w(TAG, "Binder call failed.", e);
} else {
Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
}
} else {
// Clear the parcel before writing the exception.
reply.setDataSize(0);
reply.setDataPosition(0);
reply.writeException(e);
}
res = true;
}
接下来看一下reply.writeException(e)的处理流程。
Parcel.writeException()
首先通过getExceptionCode(e)获取code值,见如下分析。
对于code = 0的异常,直接在服务端抛出RuntimeException异常。
对于其它异常,将异常信息写入到Parcel对象中,用于之后跨进程传输。
public final void writeException(@NonNull Exception e) {
AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
int code = getExceptionCode(e);
writeInt(code);
StrictMode.clearGatheredViolations();
if (code == 0) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new RuntimeException(e);
}
writeString(e.getMessage());
final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
> WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
sLastWriteExceptionStackTrace = timeNow;
writeStackTrace(e);
} else {
writeInt(0);
}
//...
}
Parcel.getExceptionCode()
就是在这里定义了支持跨进程传递的几种异常。
每类异常都用一个int code值表示,不支持跨进程传递的异常的code = 0。
public static int getExceptionCode(@NonNull Throwable e) {
int code = 0;
if (e instanceof Parcelable
&& (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
// We only send Parcelable exceptions that are in the
// BootClassLoader to ensure that the receiver can unpack them
code = EX_PARCELABLE;
} else if (e instanceof SecurityException) {
code = EX_SECURITY;
} else if (e instanceof BadParcelableException) {
code = EX_BAD_PARCELABLE;
} else if (e instanceof IllegalArgumentException) {
code = EX_ILLEGAL_ARGUMENT;
} else if (e instanceof NullPointerException) {
code = EX_NULL_POINTER;
} else if (e instanceof IllegalStateException) {
code = EX_ILLEGAL_STATE;
} else if (e instanceof NetworkOnMainThreadException) {
code = EX_NETWORK_MAIN_THREAD;
} else if (e instanceof UnsupportedOperationException) {
code = EX_UNSUPPORTED_OPERATION;
} else if (e instanceof ServiceSpecificException) {
code = EX_SERVICE_SPECIFIC;
}
return code;
}
客户端
如果服务端抛出一个SecurityException类型的异常,该异常支持跨进程传递,打印如下堆栈:
FATAL EXCEPTION: main
Process: com.demoapp.binderclientdemo, PID: 6797
java.lang.SecurityException
at android.os.Parcel.createExceptionOrNull(Parcel.java:3011)
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 com.demoapp.binderserverdemo.IMyService$Stub$Proxy.funcB(IMyService.java:123)
at com.demoapp.binderclientdemo.MainActivity.lambda$onCreate$2$com-demoapp-binderclientdemo-MainActivity(MainActivity.java:76)
从堆栈中我们首先定位到IMyService$Stub$Proxy中。
IMyService$Stub$Proxy.funcB()
@Override public java.lang.String funcB() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_funcB, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().funcB();
}
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
_reply是服务端返回的数据,从该数据中读取出异常信息。
Parcel.readException()
if (code != 0)则继续调用readException(code, msg)。
public final void readException() {
int code = readExceptionCode();
if (code != 0) {
String msg = readString();
readException(code, msg);
}
}
Parcel.readException(code, msg)
异常实际上是在这里构建并抛出的。
public final void readException(int code, String msg) {
String remoteStackTrace = null;
final int remoteStackPayloadSize = readInt();
if (remoteStackPayloadSize > 0) {
remoteStackTrace = readString();
}
Exception e = createException(code, msg);
// Attach remote stack trace if availalble
if (remoteStackTrace != null) {
RemoteException cause = new RemoteException(
"Remote stack trace:\n" + remoteStackTrace, null, false, false);
ExceptionUtils.appendCause(e, cause);
}
SneakyThrow.sneakyThrow(e);
}
Parcel.createException()
private Exception createException(int code, String msg) {
Exception exception = createExceptionOrNull(code, msg);
return exception != null
? exception
: new RuntimeException("Unknown exception code: " + code + " msg " + msg);
}
Parcel.createExceptionOrNull()
在这里通过code确定异常类,再通过msg初始化异常。
public Exception createExceptionOrNull(int code, String msg) {
switch (code) {
case EX_PARCELABLE:
if (readInt() > 0) {
return (Exception) readParcelable(Parcelable.class.getClassLoader());
} else {
return new RuntimeException(msg + " [missing Parcelable]");
}
case EX_SECURITY:
return new SecurityException(msg);
case EX_BAD_PARCELABLE:
return new BadParcelableException(msg);
case EX_ILLEGAL_ARGUMENT:
return new IllegalArgumentException(msg);
case EX_NULL_POINTER:
return new NullPointerException(msg);
case EX_ILLEGAL_STATE:
return new IllegalStateException(msg);
case EX_NETWORK_MAIN_THREAD:
return new NetworkOnMainThreadException();
case EX_UNSUPPORTED_OPERATION:
return new UnsupportedOperationException(msg);
case EX_SERVICE_SPECIFIC:
return new ServiceSpecificException(readInt(), msg);
default:
return null;
}
}