Binder-异常处理机制

Post on Jul 18, 2022 by Wei Lin

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;
    }
}