Server 端的处理
如果Binder 程序示例之 Java 篇中的 sayHello 服务端方法在执行过程中抛出异常会怎么样?
cpp
public void sayhello() throws android.os.RemoteException {
cnt1++;
Log.i(TAG, "sayhello : cnt = "+cnt1);
throw new RuntimeException("testexception");
}
为了搞清楚服务端的异常处理,我先回顾一下服务端收到远程调用时的调用栈:
如果 sayhello 函数内部抛出了异常,代码执行的流程如下:
java
//IHelloService 内部 stub 类成员方法
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
//......
case TRANSACTION_sayhello:
{
data.enforceInterface(descriptor);
this.sayhello();
reply.writeNoException();
return true;
}
//省略其他无关 case
//.....
}
}
在 IHelloService 内部 stub 类中的 onTransact 方法并没有捕获异常,我们抛出的异常会传递给上一层方法 execTransactInternal:
java
private boolean execTransactInternal(int code, long dataObj, long replyObj, int flags,
int callingUid) {
// Make sure the observer won't change while processing a transaction.
final BinderInternal.Observer observer = sObserver;
final CallSession callSession =
observer != null ? observer.callStarted(this, code, UNSET_WORKSOURCE) : null;
Parcel data = Parcel.obtain(dataObj);
Parcel reply = Parcel.obtain(replyObj);
// theoretically, we should call transact, which will call onTransact,
// but all that does is rewind it, and we just got these from an IPC,
// so we'll just call it directly.
boolean res;
// Log any exceptions as warnings, don't silently suppress them.
// If the call was FLAG_ONEWAY then these exceptions disappear into the ether.
final boolean tracingEnabled = Binder.isTracingEnabled();
try {
if (tracingEnabled) {
final String transactionName = getTransactionName(code);
Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":"
+ (transactionName != null ? transactionName : code));
}
//这里抛出异常
res = onTransact(code, data, reply, flags);
// 仅 Catch RemoteException RuntimeException 两个大类的异常
} catch (RemoteException|RuntimeException e) {
if (observer != null) {
observer.callThrewException(callSession, e);
}
if (LOG_RUNTIME_EXCEPTION) {
Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
}
// oneway 直接打印错误信息
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 { // 非 oneway ,将异常写入 reply
// Clear the parcel before writing the exception
reply.setDataSize(0);
reply.setDataPosition(0);
// 将异常写入 reply
reply.writeException(e);
}
res = true;
} finally {
if (tracingEnabled) {
Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
}
if (observer != null) {
// The parcel RPC headers have been called during onTransact so we can now access
// the worksource uid from the parcel.
final int workSourceUid = sWorkSourceProvider.resolveWorkSourceUid(
data.readCallingWorkSourceUid());
observer.callEnded(callSession, data.dataSize(), reply.dataSize(), workSourceUid);
}
}
checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
reply.recycle();
data.recycle();
// Just in case -- we are done with the IPC, so there should be no more strict
// mode violations that have gathered for this thread. Either they have been
// parceled and are now in transport off to the caller, or we are returning back
// to the main transaction loop to wait for another incoming transaction. Either
// way, strict mode begone!
StrictMode.clearGatheredViolations();
return res;
}
接着我们来看 Exception 是怎么写入 Parcle:
java
// frameworks/base/core/java/android/os/Parcel.java
public final void writeException(@NonNull Exception e) {
int code = 0;
// Exception 转换成整型的 code
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;
}
// 写入 Exception 对应的 code, 而不是 Exception 对象
writeInt(code);
StrictMode.clearGatheredViolations();
if (code == 0) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new RuntimeException(e);
}
// 接着写入 Exception 的 msg
writeString(e.getMessage());
final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
> WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
sLastWriteExceptionStackTrace = timeNow;
final int sizePosition = dataPosition();
writeInt(0); // Header size will be filled in later
StackTraceElement[] stackTrace = e.getStackTrace();
final int truncatedSize = Math.min(stackTrace.length, 5);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < truncatedSize; i++) {
sb.append("\tat ").append(stackTrace[i]).append('\n');
}
writeString(sb.toString());
final int payloadPosition = dataPosition();
setDataPosition(sizePosition);
// Write stack trace header size. Used in native side to skip the header
writeInt(payloadPosition - sizePosition);
setDataPosition(payloadPosition);
} else {
writeInt(0);
}
switch (code) {
case EX_SERVICE_SPECIFIC:
writeInt(((ServiceSpecificException) e).errorCode);
break;
case EX_PARCELABLE:
// Write parceled exception prefixed by length
final int sizePosition = dataPosition();
writeInt(0);
writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
final int payloadPosition = dataPosition();
setDataPosition(sizePosition);
writeInt(payloadPosition - sizePosition);
setDataPosition(payloadPosition);
break;
}
}
可以看出,这里是把 Exception 转换成 code 与 msg 写入 Parcel,而不是直接写 Exception 对象。
execTransactInternal 只处理了 RemoteException RuntimeException 两个大类的异常,其他未被 catch 的异常仍会被抛给上一级函数处理。
根据上文给出的服务端收到远程调用时的调用栈
,异常会传递给 execTransact 函数,这个函数内部不会处理异常,接下来异常会进一步传递给 JNI层的 onTransact:
cpp
// frameworks/base/core/jni/android_util_Binder.cpp
status_t onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) override
{
JNIEnv* env = javavm_to_jnienv(mVM);
ALOGV("onTransact() on %p calling object %p in env %p vm %p\n", this, mObject, env, mVM);
IPCThreadState* thread_state = IPCThreadState::self();
const int32_t strict_policy_before = thread_state->getStrictModePolicy();
//printf("Transact from %p to Java code sending: ", this);
//data.print();
//printf("\n");
jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);
//这里会检查剩余的异常
if (env->ExceptionCheck()) {
//直接打印异常信息
ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred());
report_exception(env, excep.get(),
"*** Uncaught remote exception! "
"(Exceptions are not yet supported across processes.)");
res = JNI_FALSE;
}
//......
}
剩余的异常都会在这里打印出来
Client 端的处理
接下来我们来看看 Client 端的处理:
java
@Override public void sayhello() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_sayhello, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().sayhello();
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
在客户端会从 Parcel reply
中读出异常信息:
java
// frameworks/base/core/java/android/os/Parcel.java
public final void readException() {
// 读出 code
int code = readExceptionCode();
if (code != 0) {
// 读出 msg
String msg = readString();
// 构建出 Exception 对象
readException(code, msg);
}
}
public final int readExceptionCode() {
int code = readInt();
if (code == EX_HAS_REPLY_HEADER) {
int headerSize = readInt();
if (headerSize == 0) {
Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
} else {
// Currently the only thing in the header is StrictMode stacks,
// but discussions around event/RPC tracing suggest we might
// put that here too. If so, switch on sub-header tags here.
// But for now, just parse out the StrictMode stuff.
StrictMode.readAndHandleBinderCallViolations(this);
}
// And fat response headers are currently only used when
// there are no exceptions, so return no error:
return 0;
}
return code;
}
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);
try {
Throwable rootCause = ExceptionUtils.getRootCause(e);
if (rootCause != null) {
rootCause.initCause(cause);
}
} catch (RuntimeException ex) {
Log.e(TAG, "Cannot set cause " + cause + " for " + e, ex);
}
}
//抛出异常
SneakyThrow.sneakyThrow(e);
}
private Exception createException(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);
}
return new RuntimeException("Unknown exception code: " + code
+ " msg " + msg);
}
总的来说,就是读出 code 与 msg,然后构建新的 Exception 对象,最后抛出异常。