亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > Android > 正文

Android跨進程拋異常的原理的實現

2019-10-21 21:36:11
字體:
來源:轉載
供稿:網友

今天接到了個需求,需要用到跨進程拋異常。

怎樣將異常從服務端拋到客戶端

也就是說在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端改成拋出UnsupportedOperationExceptionppublic 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.readExceptionpublic 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的頭部是一個標志位,標志了有異?;蛘邿o異常:

Android,跨進程,異常

但是我們看到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<jlong>(&data), reinterpret_cast<jlong>(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);  }  ...}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美在线一区二区三区四| 亚洲国产精品va在线| 欧美亚洲视频一区二区| 精品久久国产精品| 国产精品久久久av久久久| 日韩大陆欧美高清视频区| 成人h片在线播放免费网站| 在线视频精品一| 色av中文字幕一区| 日韩av成人在线观看| 欧美精品国产精品日韩精品| 精品呦交小u女在线| 成人羞羞国产免费| 欧美乱大交xxxxx| 不卡av在线播放| 久久资源免费视频| 欧美性少妇18aaaa视频| 日韩久久午夜影院| 亚洲一区制服诱惑| 国产精品永久免费| 国产精品一区二区在线| 国产成人一区二| 亚洲精品在线视频| 久久香蕉频线观| 在线免费观看羞羞视频一区二区| 欧美最顶级丰满的aⅴ艳星| 欧美日韩免费一区| 亚洲人成伊人成综合网久久久| 亚洲精品一区av在线播放| 久久国产精品免费视频| 欧美激情在线狂野欧美精品| 7777免费精品视频| 一区二区日韩精品| 亚洲色图欧美制服丝袜另类第一页| 456国产精品| 日本伊人精品一区二区三区介绍| 欧美黄色片在线观看| 欧美性猛xxx| 日韩免费av一区二区| 国产剧情久久久久久| 日韩电影在线观看永久视频免费网站| 欧美国产精品va在线观看| 日韩视频免费观看| 久久久国产视频91| 日韩欧美主播在线| 国产精品爽黄69| 欧美激情综合亚洲一二区| 91视频免费在线| 精品女厕一区二区三区| 亚洲高清久久久久久| 亚洲一区二区三区毛片| 亚洲综合精品一区二区| 亚洲一区制服诱惑| 亚洲精品98久久久久久中文字幕| 国产在线一区二区三区| 欧美一区二区大胆人体摄影专业网站| 久久精品免费播放| 亚洲毛片在线观看| 国产精品福利观看| 精品久久久久久久久久久久| 亚洲男人天堂手机在线| 欧洲成人午夜免费大片| 精品福利樱桃av导航| 亚洲欧美日韩直播| 亚洲mm色国产网站| 成人黄色av播放免费| 亚洲成人aaa| 亚洲男人天堂网| 日韩精品www| 欧美成人国产va精品日本一级| 日韩视频在线免费观看| 91久久精品美女| 亚洲裸体xxxx| 亚洲国产日韩欧美在线图片| 两个人的视频www国产精品| 亚洲自拍偷拍福利| 欧美黄网免费在线观看| 亚洲美腿欧美激情另类| 日韩精品久久久久久久玫瑰园| 久久综合国产精品台湾中文娱乐网| 97在线看免费观看视频在线观看| 黄色成人在线播放| 91av视频在线免费观看| 国产精品自产拍在线观看中文| 亚洲欧美精品中文字幕在线| 欧美黑人狂野猛交老妇| 国产精品嫩草视频| 中文在线资源观看视频网站免费不卡| 欧美亚洲免费电影| 国产精品一区二区三区免费视频| 日韩久久免费电影| 亚洲激情电影中文字幕| 久久国内精品一国内精品| 最近的2019中文字幕免费一页| 亚洲人成人99网站| 欧美—级a级欧美特级ar全黄| 日韩女优人人人人射在线视频| 成人激情视频免费在线| 日韩欧美中文免费| 日韩av电影中文字幕| 成人免费直播live| 欧美性理论片在线观看片免费| 精品国产网站地址| 韩国日本不卡在线| 欧美性猛交xxxx乱大交3| 国内精品中文字幕| 久久国产精品免费视频| 亚洲男人天堂手机在线| 黑人巨大精品欧美一区二区三区| 亚洲精品女av网站| 97视频在线播放| 亚洲精品永久免费精品| 成人激情视频免费在线| 欧美国产视频日韩| 国产精品视频免费在线| 色先锋久久影院av| 亚洲欧美日韩高清| 97精品伊人久久久大香线蕉| 狠狠久久五月精品中文字幕| 欧美成人精品不卡视频在线观看| 国产亚洲精品综合一区91| 色多多国产成人永久免费网站| 精品视频久久久久久| 亚洲曰本av电影| 欧美性猛交xxxxx水多| 亚洲国产精品悠悠久久琪琪| 亚洲精品国产精品国产自| 日本久久中文字幕| 91久久久久久久| 国产精品久久久久91| 九九久久综合网站| 亚洲成人999| 精品免费在线视频| 国产亚洲美女久久| 欧美视频13p| 精品欧美激情精品一区| 91大神在线播放精品| 久久精品视频在线| 91免费视频网站| 欧美性极品xxxx做受| 成人性教育视频在线观看| 亚洲日本欧美日韩高观看| 欧美日韩中文在线观看| 欧美日韩视频在线| 欧美激情视频网站| 欧美激情欧美狂野欧美精品| 69久久夜色精品国产69| 欧美电影免费观看| 亚洲欧洲日韩国产| 欧美在线一级va免费观看| 免费av一区二区| 欧美成人黄色小视频| 成人午夜在线观看| 日韩欧美大尺度| 日韩在线视频导航| 亚洲国产精品电影| 久久久亚洲精选| 国产精品视频yy9099| 亚洲国产天堂久久综合网| 国产一区二区精品丝袜| 欧美性受xxx| 成人美女免费网站视频| 日韩精品中文字| 国产精品成人一区二区|