今天接到了个需求,需要用到跨进程抛异常。

怎样将异常从服务端抛到客户端

也就是说在Service端抛出的异常需要可以在Client端接收。印象中binder是可以传异常的,所以aidl直接走起:

// aidl文件

interface ITestExceptionAidl {

boolean testThrowException();

}

// service端实现

public class AidlService extends Service {

@Nullable

@Override

public IBinder onBind(Intent intent) {

return new ITestExceptionAidl.Stub() {

@Override

public boolean testThrowException() throws RemoteException {

if (true) {

throw new RuntimeException("TestException");

}

return true;

}

};

}

}

// client端实现

bindService(intent, new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

ITestExceptionAidl aidl = ITestExceptionAidl.Stub.asInterface(service);

try {

aidl.testThrowException();

} catch (Exception e) {

Log.e("testtest", "Exception", e);

}

}

@Override

public void onServiceDisconnected(ComponentName name) {

}

}, Context.BIND_AUTO_CREATE);

但是这个程序实际上运行起来是这样的:

01-01 05:31:55.475  4868  4880 E JavaBinder: *** Uncaught remote exception!  (Exceptions are not yet supported across processes.)

01-01 05:31:55.475  4868  4880 E JavaBinder: java.lang.RuntimeException: TestException

01-01 05:31:55.475  4868  4880 E JavaBinder:    at me.linjw.demo.ipcdemo.AidlService$1.testThrowException(AidlService.java:22)

01-01 05:31:55.475  4868  4880 E JavaBinder:    at me.linjw.demo.ipcdemo.ITestExceptionAidl$Stub.onTransact(ITestExceptionAidl.java:48)

01-01 05:31:55.475  4868  4880 E JavaBinder:    at android.os.Binder.execTransact(Binder.java:565)

看日志里面的ITestExceptionAidl$Stub.onTransact,也就是说在service端就已经被异常打断了,并没有传给client端,而且第一个大大的”Exceptions are not yet supported across processes.”是说异常不允许跨进程吗?但是我明明记得AIDL生成的代码里面就有向Parcel写入异常啊:

public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {

switch (code) {

case INTERFACE_TRANSACTION: {

reply.writeString(DESCRIPTOR);

return true;

}

case TRANSACTION_testThrowException: {

data.enforceInterface(DESCRIPTOR);

boolean _result = this.testThrowException();

reply.writeNoException(); // 这里写入的是没有抛出异常

reply.writeInt(((_result) ? (1) : (0)));

return true;

}

}

return super.onTransact(code, data, reply, flags);

}

查找Parcel的源码,其实是有writeException方法的:

public final void writeException(Exception 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;

}

writeInt(code);

StrictMode.clearGatheredViolations();

if (code == 0) {

if (e instanceof RuntimeException) {

throw (RuntimeException) e;

}

throw new RuntimeException(e);

}

writeString(e.getMessage());

...

}

可以看到其实Parcel是支持写入异常的,但是只支持Parcelable的异常或者下面这几种异常:

SecurityException

BadParcelableException

IllegalArgumentException

NullPointerException

IllegalStateException

NetworkOnMainThreadException

UnsupportedOperationException

ServiceSpecificException

如果是普通的RuntimeException,这打断写入,继续抛出。

于是我们将RuntimeException改成它支持的UnsupportedOperationException试试:

// service端改成抛出UnsupportedOperationException

ppublic class AidlService extends Service {

@Nullable

@Override

public IBinder onBind(Intent intent) {

return new ITestExceptionAidl.Stub() {

@Override

public boolean testThrowException() throws RemoteException {

if (true) {

throw new UnsupportedOperationException("TestException");

}

return true;

}

};

}

}

// client端实现还是一样,不变

bindService(intent, new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

ITestExceptionAidl aidl = ITestExceptionAidl.Stub.asInterface(service);

try {

aidl.testThrowException();

} catch (Exception e) {

Log.e("testtest", "Exception", e);

}

}

@Override

public void onServiceDisconnected(ComponentName name) {

}

}, Context.BIND_AUTO_CREATE);

这样运行的话客户端就能捕获到异常:

01-01 05:49:46.770 19937 19937 E testtest: RemoteException

01-01 05:49:46.770 19937 19937 E testtest: java.lang.UnsupportedOperationException: TestException

01-01 05:49:46.770 19937 19937 E testtest:      at android.os.Parcel.readException(Parcel.java:1728)

01-01 05:49:46.770 19937 19937 E testtest:      at android.os.Parcel.readException(Parcel.java:1669)

01-01 05:49:46.770 19937 19937 E testtest:      at me.linjw.demo.ipcdemo.ITestExceptionAidl$Stub$Proxy.testThrowException(ITestExceptionAidl.java:77)

01-01 05:49:46.770 19937 19937 E testtest:      at me.linjw.demo.ipcdemo.MainActivity$3.onServiceConnected(MainActivity.java:132)

01-01 05:49:46.770 19937 19937 E testtest:      at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1465)

01-01 05:49:46.770 19937 19937 E testtest:      at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:1482)

01-01 05:49:46.770 19937 19937 E testtest:      at android.os.Handler.handleCallback(Handler.java:751)

01-01 05:49:46.770 19937 19937 E testtest:      at android.os.Handler.dispatchMessage(Handler.java:95)

01-01 05:49:46.770 19937 19937 E testtest:      at android.os.Looper.loop(Looper.java:154)

01-01 05:49:46.770 19937 19937 E testtest:      at android.app.ActivityThread.main(ActivityThread.java:6097)

01-01 05:49:46.770 19937 19937 E testtest:      at java.lang.reflect.Method.invoke(Native Method)

01-01 05:49:46.770 19937 19937 E testtest:      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1052)

01-01 05:49:46.770 19937 19937 E testtest:      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:942)

跨进程传递异常的原理

好,知道了如何去跨进程传递异常之后,然后我们来看看异常到底是如何传递过去的。

让我们再来看看异常写入的代码:

// 有异常的情况

public final void writeException(Exception 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;

}

writeInt(code);

StrictMode.clearGatheredViolations();

if (code == 0) {

if (e instanceof RuntimeException) {

throw (RuntimeException) e;

}

throw new RuntimeException(e);

}

writeString(e.getMessage());

// 之后还有一些写入堆栈的操作,比较多,这里可以不看

}

public final void writeNoException() {

if (StrictMode.hasGatheredViolations()) {

// 如果StrictMode收集到了写违规行为会走这里,我们可以不关注它

writeInt(EX_HAS_REPLY_HEADER);

...

} else {

// 一般情况下会走这里

writeInt(0);

}

}

这里给每种支持的异常都编了个号码,它会往Parcel写入。而0代表的是没有发生异常。然后再看看读取异常的代码:

public boolean testThrowException() throws android.os.RemoteException {

android.os.Parcel _data = android.os.Parcel.obtain();

android.os.Parcel _reply = android.os.Parcel.obtain();

boolean _result;

try {

_data.writeInterfaceToken(DESCRIPTOR);

mRemote.transact(Stub.TRANSACTION_testThrowException, _data, _reply, 0);

_reply.readException();

_result = (0 != _reply.readInt());

} finally {

_reply.recycle();

_data.recycle();

}

return _result;

}

// android.os.Parcel.readException

public final void readException() {

int code = readExceptionCode();

if (code != 0) {

String msg = readString();

//在这个方法里面创建异常并且抛出

readException(code, msg);

}

}

然后这里有个需要注意的点就是异常必须是写在Parcel的头部的,也就是说如果没有异常,我们先要将0写到头部,然后再将返回值继续往后面写入。如果有异常,我们要先将异常编码写入头部,然后就不需要再写入返回值了。

这样,在客户端读取的时候读取的头部就能知道到底有没有异常,没有异常就继续读取返回值,有异常就将异常读取出来并且抛出。

// service端代码

boolean _result = this.testThrowException();

reply.writeNoException(); // 先写入异常

reply.writeInt(((_result) ? (1) : (0))); // 再写入返回值

// client端代码

mRemote.transact(Stub.TRANSACTION_testThrowException, _data, _reply, 0);

_reply.readException(); // 先读取异常,有异常的话readException方法里面会直接抛出

_result = (0 != _reply.readInt()); // 再读取返回值

也就是Parcel的头部是一个标志位,标志了有异常或者无异常:

但是我们看到AIDL生成的代码都是写入的无异常,那我们抛出的异常是怎么传过去的呢?还记得这个打印吗?

01-01 05:31:55.475  4868  4880 E JavaBinder: *** Uncaught remote exception!  (Exceptions are not yet supported across processes.)

01-01 05:31:55.475  4868  4880 E JavaBinder: java.lang.RuntimeException: TestException

01-01 05:31:55.475  4868  4880 E JavaBinder:    at me.linjw.demo.ipcdemo.AidlService$1.testThrowException(AidlService.java:22)

01-01 05:31:55.475  4868  4880 E JavaBinder:    at me.linjw.demo.ipcdemo.ITestExceptionAidl$Stub.onTransact(ITestExceptionAidl.java:48)

01-01 05:31:55.475  4868  4880 E JavaBinder:    at android.os.Binder.execTransact(Binder.java:565)

我们去android.os.Binder.execTransact这里找找看, onTransact方法实际就是在这里被调用的

private boolean execTransact(int code, long dataObj, long replyObj, int flags) {

Parcel data = Parcel.obtain(dataObj);

Parcel reply = Parcel.obtain(replyObj);

boolean res;

try {

res = onTransact(code, data, reply, flags);

} catch (RemoteException|RuntimeException e) {

...

reply.setDataPosition(0);

reply.writeException(e);

res = true;

} catch (OutOfMemoryError e) {

RuntimeException re = new RuntimeException("Out of memory", e);

reply.setDataPosition(0);

reply.writeException(re);

res = true;

}

checkParcel(this, code, reply, "Unreasonably large binder reply buffer");

reply.recycle();

data.recycle();

return res;

}

看,这里如果catch到了方法,也就是说我们服务端有抛出异常,就会在catch代码块里面先就Parcel的游标重置回0,然后往Parcel头部写入异常。

好,到了这里其实整个流程就差不多了,但是我发现我没有看到那个”Exceptions are not yet supported across processes.”字符串,这个不支持的提示又是哪里来的呢?

让我们再回忆下代码,在遇到不支持的异常类型的时候, writeException也会抛出异常:

public final void writeException(Exception 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;

}

writeInt(code);

StrictMode.clearGatheredViolations();

// code为0,代表不支持这种异常,继续把异常抛出或者创建RuntimeException抛出

if (code == 0) {

if (e instanceof RuntimeException) {

throw (RuntimeException) e;

}

throw new RuntimeException(e);

}

...

}

由于这个writeException,已经是在catch代码块里面运行的了,没有人再去catch它,于是就会打断这个流程,直接跳出。形成了一个Uncaught remote exception。

最后我们找到/frameworks/base/core/jni/android_util_Binder.cpp的onTransact方法,这里通过jni调到Java的execTransact方法,调用完之后进行ExceptionCheck,如果发现有异常的话就report_exception:

virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) {

JNIEnv* env = javavm_to_jnienv(mVM);

IPCThreadState* thread_state = IPCThreadState::self();

const int32_t strict_policy_before = thread_state->getStrictModePolicy();

jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,

code, reinterpret_cast(&data), reinterpret_cast(reply), flags);

if (env->ExceptionCheck()) {

jthrowable excep = env->ExceptionOccurred();

// 就是这里啦

report_exception(env, excep,

"*** Uncaught remote exception! "

"(Exceptions are not yet supported across processes.)");

res = JNI_FALSE;

env->DeleteLocalRef(excep);

}

...

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

binder.java 565_Android跨进程抛异常的原理的实现相关推荐

  1. 【Binder】Android 跨进程通信原理解析

    前言 在Android开发的过程中,用到跨进程通信的地方非常非常多,我们所使用的Activity.Service等组件都需要和AMS进行跨进程通信,而这种跨进程的通信都是由Binder完成的. 甚至一 ...

  2. Java 异常处理(标准抛异常、异常处理、多异常、Finally、多线程异常处理、获取异常的堆栈信息、链试异常、自定义异常)

    使用 catch 处理异常(标准抛异常) public class Main {public static void main (String args[]) {int array[]={20,20, ...

  3. java工具类应该抛异常吗,java学习阶段一 工具类(异常)

    java学习阶段一 工具类(异常) 介绍 异常:运行期间出现的错误 背离程序本身意图的表现 基本知识 异常的分类 根类 Throwable Error 程序无法处理的错误 表示运行应用程序中教严重的问 ...

  4. java timeout超时不抛异常_springCloud 请求超时解决方案 java.net.SocketTimeOut Exception: Read time out 异常解决...

    <1>经过日志发现 当控制层访问微服务的响应时间超过5秒spring 例:app 2017-04-14 14:07:28.684  INFO 25898 --- [nio-8081-exe ...

  5. Binder跨进程通信原理(三):Binder IPC实现原理

    1. 动态内核可加载模块 && 内存映射 正如上一章所说, 跨进程通信是需要内核空间做支持的. 传统的 IPC 机制如 管道, Socket, 都是内核的一部分, 因此通过内核支持来实 ...

  6. Android跨进程通信Binder机制与AIDL实例

    文章目录 进程通信 1.1 进程空间划分 1.2 跨进程通信IPC 1.3 Linux跨进程通信 1.4 Android进程通信 Binder跨进程通信 2.1 Binder简介 2.2 Binder ...

  7. Android进阶——Android跨进程通讯机制之Binder、IBinder、Parcel、AIDL

    前言 Binder机制是Android系统提供的跨进程通讯机制,这篇文章开始会从Linux相关的基础概念知识开始介绍,从基础概念知识中引出Binder机制,归纳Binder机制与Linux系统的跨进程 ...

  8. Android跨进程通讯机制之Binder、IBinder、Parcel、AIDL

    https://blog.csdn.net/qq_30379689/article/details/79451596 前言 Binder机制是Android系统提供的跨进程通讯机制,这篇文章开始会从L ...

  9. Android之使用AIDL时的跨进程回调—Server回调Client

    首先建立在server端建立两个aidl文件 ITaskCallback.aidl 用于存放要回调client端的方法 package com.cmcc.demo.server; interface ...

  10. Binder Java层实现(一):IBinder/IInterface/Binder/Stub

    要点 面向对象思想的引入将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体(本地对象)位于一个进程中,而它的引用( ...

最新文章

  1. js异步提交form表单的解决方案
  2. markdown to html
  3. sql group by 取每组符合条件_从零学SQL-经典面试题
  4. Unable to open a test connection to the given database.
  5. qt-designer使用教程2--调用退出
  6. 我们希望读者能从这个BLOG获得什么?
  7. .NET项目迁移到.NET Core操作指南
  8. 电脑的发展史_互联网发展史 硅谷传奇之 IBM
  9. 移除文件资源管理器侧边栏中的Creative Cloud Files
  10. ubuntu文件名乱码(转载)
  11. leetcode 799. 香槟塔 (Champagne Tower)
  12. 进入大数据时代,目前我国大数据的发展趋势怎么样
  13. mysql事件探查器_SQL2005事件探查器中的Reads数据很大是怎么回事?
  14. 几何图形及计算公式查询
  15. ZOJ Problem Set - 4043 Virtual Singers(2018acm 青岛赛区热身赛)
  16. jsr 正则验证_使用JSR-303进行校验 @Valid
  17. 适合穷人挣钱最快的方法
  18. TCO2017 Semifinal 部分题解
  19. 【C++】类和对象——拷贝构造函数
  20. 服务器被入侵当做挖矿肉鸡

热门文章

  1. Vue基础及一些常用指令
  2. Linux系统中设置静态ip地址
  3. rk3399固件烧录方法介绍
  4. Android7.1编译SDK报错解决方法总结
  5. 【比赛】NOIP2017 列队
  6. 【BZOJ1226】学校食堂(动态规划,状态压缩)
  7. openoffice转换pdf 异常问题查找处理 errorCode 525
  8. 第51条:精简initialize与load的实现代码
  9. uestc 方老师的分身 II
  10. Android--xml布局文件中使用include