Binder的异常之分析

Binder的异常之分析

在Adroid的世界里,每个程序是互相独立运行的。若要请求协助就需要Binder机制,而此Binder就是一种Client –Server的设计。其中间运行机制不在这里详细描述,间单来讲Binder就是一种程序沟通接口,运用的基本原理就是共享内存加代理模式。因此每个程序是不会知道对方的运作流程,只能知道有求必应这功能。但是若对方一发生异常,虽然使用方无法得知,但也不能置之不理。这篇心得就是要来分析当Server在处理Client端需求发生异常时,Binder是如何来处理。

 

 

  1. 如果服务对象的方法中发生异常,Binder将如何处理异常?

首先分为Server和Client来分析。

1.1 Server

 

AIDL工具生成的Stub抽象类主要用于方法分发。如果报出异常的话,异常将首先会报给Stub类的onTransact方法。以如下foo方法为例,如果其内部发生异常,则该异常将会被onTransact方法感知。由上图可以知道,处理异常的部分会有两个地方,一个是Binder.execTransactInternal、另一个是JavaBBinder::onTransact。接下来就分别分析一下这两个地方。

 

48行是呼叫foo的位置,不过内部发生的异常并没有在onTransact中被处理,因此会继续回传给Binder.execTransactInternal方法。

[file]: /frameworks/base/core/java/android/os/Binder.java

[method]: execTransactInternal

 

1021行是呼叫onTransact的位置。如果异常没有在此方法中被处理,将会进一步抛出给execTransact,最终进入JNI方法:JavaBBinder::onTransact

异常的传递关系正如本节开始的那幅图所示,其最终的处理分为了两种情况。

    1. 由Binder.execTransactInternal来处理异常

以下是Binder会处理的相关异常和异常码。

Exception

Code

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

在execTransactInternal的程序代码中,在1022行发现只catch两种exception。一个是RemoteException、另一个是RuntimeException。其他的Exception就继续传到JNI(JavaBBinder::onTransact)去处理。在execTransactInternal的程序代码的1039行发现使用Parcel来写入所catch到的RemoteException或RuntimeException。接下来就来看看Parcel的writeException方法。

[file]: /frameworks/base/core/java/android/os/Parcel.java

[method]: writeException

 

由程序代码可以看到如上8种异常,Server进程会将异常的信息串行化写入Parcel对象,然后经由Driver发送给Client进程。而其他的Exception就在1897行打包为RuntimeException传到JNI(JavaBBinder::onTransact)处理。

 

    1. 由JavaBBinder::onTransact来处理异常

[file]: /frameworks/base/core/jni/android_util_Binder.cpp

[method]: onTransact

 

由程序代码可知368行是呼叫Java层execTransact方法的位置。当该方法抛出异常时,371行的异常检测将为true,而其后的373行会将异常打印出来。需要注意的是,这些信息并不会被送回Client进程只会写log。

以下是范例。

 

综合以上两种处理情况,可以推断所有服务对象方法中发生的异常都会被处理。一种是将异常信息发送给对端进程,另一种是将异常信息在本进程输出。而这些处理都不会使Server进程退出。

作为Server进程,它在什么时候执行,该执行些什么都不由自己掌控,而是由Client进程控制。因此抛出异常本质上与Client进程相关,让一个Client进程的行为导致Server进程退出显然是不合理的。此外,Server进程可能关联着千百个Client,不能由于一个Client的错误行为而影响本可以正常获取服务的其他Client。因此Server的方法一旦因为某个Client的失误导致发生异常,实在不能因为这样的异常而把Server终止掉。

1.2 Client

Client端受到的影响完全取决于Server端如何处理异常。上文中已经说明,Server进程会分两种情况来处理异常:一种是将八种特定异常信息发送给Client进程,另一个种是将异常信息在Server进程中只有输出不传送。以下按照这两个情况分别讨论Client进程受到的影响。

1.2.1 从Parcel对象中读回异常信息

Client端利用Binder proxy呼叫完Sever端的方法后会经由Parcel来读取异常讯息。

 

由上面的范例可以看到Server进程通过Parcel[_reply]对象发送的异常信息最终会在79行被读回。

[file]: /frameworks/base/core/java/android/os/Parcel.java

[method]: readException

 

首先会从readExceptionCode去读取Exception code,此Exception code就是Binder Catch到的exception时利用Parcel的writeException方法写进,可参考如上writeException程序代码的1892行。这些code除了如之前所提到的8种异常之外,一律都是0。因此这里会做个判断,若不是0的话就把相关的message和code继续往下处理,若是0的话就不处理。

[file]: /frameworks/base/core/java/android/os/Parcel.java

[method]: readException(int code, String msg)

 

由程序代码可知,根据异常code,msg和stack tracey在2039行重新构建出Exception对象,在2053行抛出。由于readException方法并没有用throws修饰,所以如果该异常是Checked Exception(譬如RemoteExceptionIOException)就不能够直接抛出,否则会产生编译错误。因此这里采用了SneakyThrow来进行。

 

结合Server端的异常处理可以知道Client端只会读到8种RuntimeException中的一种。由于RuntimeException属于Unchecked Exception,因此编译过程并不会去检查对它的处理。换句话说,在使用代理对象的方式通常只会去catch RemoteException,而不会去catch RuntimeException。如此一来,Parcel读回来的RuntimeException将会导致Client进程退出。以下为示例,注意这些Log是在Client进程中输出的。

 

1.2.2 Binder 自行处理异常

先从JNI遇到异常处理开始分析,前面有提到一旦检测到有异常,Binder只是做log而已。

[file]: /frameworks/base/core/jni/android_util_Binder.cpp

[method]: onTransact

 

[…]

 

由程序代码看来,log完讯息之后在376行就指定res为JNI_FALSE,因此JavaBBinder::onTransact最终返回UNKNOWN_TRANSACTION。就继续往回追踪。

呼叫JavaBBinder::onTransact的是BBinder::transact。

[file]: /frameworks/native/libs/binder/Binder.cpp

[method]: transact

 

UNKNOWN_TRANSACTION 继续往回传。BBinder::transact会是由IPCThreadState::executeCommand来呼叫。

[file]: /frameworks/native/libs/binder/IPCThreadState.cpp

[method]: executeCommand

 

[…]

 

1213行从transact取回error之后在1230行会将该error写入Parcel对象中,并呼叫sendReply传回Client进程。

[file]: /frameworks/native/libs/binder/IPCThreadState.cpp

[method]: sendReply

 

825行将含有error data的parcel [程序里的reply]透过writeTransactionData方法交由Binder driver处理,之后在透过waitForResponse方法等Binder driver的响应。

[file]: /frameworks/native/libs/binder/IPCThreadState.cpp

[method]: writeTransactionData

由于1038行data中读取的err是UNKNOWN_TRANSACTION,所以最终写入驱动的数据并不是data本身,而是data中的err值。之后就再也没把err的信息往其他地方传送了。因此这些非如前面所提的8种异常一到此就习事宁人了。而Client端和Server端都完好如初的还活着,只差别在Client对Server的需求并不会有反应。

 

到此还没有描述到跟RemoteException的相关信息,在1.1节中也只知道8种特定异常和其他异常被包成RuntimeException的处理方式。而RemoteException又为何在Client端使用时被强迫要Catch,这一定有设计上的道理。接下来就来看看RemoteException是如何被触发?

2. RemoteException 设计

RemoteException是一种Checked Exception。必须按照如下两种方式中的一种来处理,否则会在编译时报错。

  • 用try catch代码块来捕获异常。

  • 将该方法用throws关键词修饰,告诉调用者此方法可能会抛出该异常。

Checked Exception会对程序员的代码有强制要求。之所以这么做,是因为该类异常希望程序员可以提前预知并做好准备,它们本可以被处理,用不着让进程退出。

以下是Oracle官网的解释,表示对于那些进程可以从中恢复的异常,都应该把它声明为Checked Exception。像远程呼叫/网络请求/IO请求之类的异常,这些异常多是数据无法获取,但并不意味着进程到了无法继续运行的地步,因此用Checked Exception最为合适。

Generally speaking, do not throw a RuntimeException or create a subclass of RuntimeException simply because you don’t want to be bothered with specifying the exceptions your methods can throw.

Here’s the bottom line guideline: If a client can reasonably be expected to recover from an exception, make it a checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception.

对于Android Q而言,RemoteException的子类有三个:

  • DeadObjectException

  • TransactionTooLargeException

  • DeadSystemException,是DeadObjectException的子类

2.1 DeadObjectException

[file]: /frameworks/base/core/java/android/os/DeadObjectException.java

 

由批注可以知道,此异常是用来反应Server端已经挂掉了。事实上DeadObjectException不仅仅是远程进程挂掉这一种情况可以触发,binder线程池耗尽也会触发该异常。

 

接下来就来看看在甚么的地方下会触发DeadObjectException。因为是Client端在使用Server端的方法要去Catch这个RemoteException,所以就从Proxy要transact Binder request这一点来看。Binder porxy传送Binder request的地方最终会到JNI android_os_BinderProxy_transact。

[file]: /frameworks/base/core/jni/android_util_Binder.cpp

[method]: android_os_BinderProxy_transact

 

可以发现1334行就是Exception的触发点并回传JNI_FALSE。可以发现这方法有一个参数是用来控制是否要丢RemoteException,代表这方法会按照1319行所回传的error会有不一样的Exception。这里只要探讨RemoteException,所以只会看有关RemoteException的信息。

[file]: /frameworks/base/core/jni/android_util_Binder.cpp

[method]: signalExceptionForError

 

 

由程序代码的780和806行可知,Java层接收到的DeadObjectException有两处:

  • BpBinder::transact返回值为DEAD_OBJECT。

  • BpBinder::transact返回值为FAILED_TRANSACTION,但是本次传输的Parcel小于200K。

BpBinder:transact返回的error是藉由IPCThreadState的waitForResponse方法去跟Binder driver询问而所指定的。

[file]: /frameworks/native/libs/binder/IPCThreadState.cpp

[method]: waitForResponse

 

837行就是透过talkWithDriver方法去跟Binder driver要的err 状态。

2.1.1 DEAD_OBJECT

[file]: /frameworks/native/libs/binder/IPCThreadState.cpp

[method]: waitForResponse

 

BR_DEAD_REPLY是由Binder driver所指定的,在855行可以看到只要从Binder driver得到BR_DEAD_REPLY就会把要回传的err设为DEAD_OBJECT。之后就会在JNI那一层触发DeadObjectException。

2.1.2 FAILED_TRANSACTION

[file]: /frameworks/native/libs/binder/IPCThreadState.cpp

[method]: waitForResponse

 

同样的Binder driver若回传BR_FAILED_REPLY就指定err为FAILED_TRANSACTION。

2.2 TransactionTooLargeException

由signalExceptionForError程序代码可知收到BR_FAILED_REPLY且此次传输的数据大于200K,则Java层会接收到TransactionTooLargeException的异常。

2.3 DeadSystemException

[file]: /frameworks/base/core/java/android/os/RemoteException.java

[method]: rethrowFromSystemServer

 

特别包在RemoteException中,表示当Client端收到DeadObjectException可以呼叫这方法重新丢一个DeadSystemException,代表DeadSystemException也是一种RemoteException。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值