Android跨進(jìn)程拋異常的原理的實(shí)現(xiàn)
今天接到了個(gè)需求,需要用到跨進(jìn)程拋異常。
怎樣將異常從服務(wù)端拋到客戶端
也就是說(shuō)在Service端拋出的異常需要可以在Client端接收。印象中binder是可以傳異常的,所以aidl直接走起:
// aidl文件
interface ITestExceptionAidl {
boolean testThrowException();
}
// service端實(shí)現(xiàn)
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端實(shí)現(xiàn)
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);
但是這個(gè)程序?qū)嶋H上運(yùn)行起來(lái)是這樣的:
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,也就是說(shuō)在service端就已經(jīng)被異常打斷了,并沒(méi)有傳給client端,而且第一個(gè)大大的”Exceptions are not yet supported across processes.”是說(shuō)異常不允許跨進(jìn)程嗎?但是我明明記得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(); // 這里寫入的是沒(méi)有拋出異常
reply.writeInt(((_result) ? (1) : (0)));
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
查找Parcel的源碼,其實(shí)是有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());
...
}
可以看到其實(shí)Parcel是支持寫入異常的,但是只支持Parcelable的異?;蛘呦旅孢@幾種異常:
- SecurityException
- BadParcelableException
- IllegalArgumentException
- NullPointerException
- IllegalStateException
- NetworkOnMainThreadException
- UnsupportedOperationException
- ServiceSpecificException
如果是普通的RuntimeException,這打斷寫入,繼續(xù)拋出。
于是我們將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端實(shí)現(xiàn)還是一樣,不變
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);
這樣運(yùn)行的話客戶端就能捕獲到異常:
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)
跨進(jìn)程傳遞異常的原理
好,知道了如何去跨進(jìn)程傳遞異常之后,然后我們來(lái)看看異常到底是如何傳遞過(guò)去的。
讓我們?cè)賮?lái)看看異常寫入的代碼:
// 有異常的情況
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收集到了寫違規(guī)行為會(huì)走這里,我們可以不關(guān)注它
writeInt(EX_HAS_REPLY_HEADER);
...
} else {
// 一般情況下會(huì)走這里
writeInt(0);
}
}
這里給每種支持的異常都編了個(gè)號(hào)碼,它會(huì)往Parcel寫入。而0代表的是沒(méi)有發(fā)生異常。然后再看看讀取異常的代碼:
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();
//在這個(gè)方法里面創(chuàng)建異常并且拋出
readException(code, msg);
}
}
然后這里有個(gè)需要注意的點(diǎn)就是異常必須是寫在Parcel的頭部的,也就是說(shuō)如果沒(méi)有異常,我們先要將0寫到頭部,然后再將返回值繼續(xù)往后面寫入。如果有異常,我們要先將異常編碼寫入頭部,然后就不需要再寫入返回值了。
這樣,在客戶端讀取的時(shí)候讀取的頭部就能知道到底有沒(méi)有異常,沒(méi)有異常就繼續(xù)讀取返回值,有異常就將異常讀取出來(lái)并且拋出。
// service端代碼 boolean _result = this.testThrowException(); reply.writeNoException(); // 先寫入異常 reply.writeInt(((_result) ? (1) : (0))); // 再寫入返回值 // client端代碼 mRemote.transact(Stub.TRANSACTION_testThrowException, _data, _reply, 0); _reply.readException(); // 先讀取異常,有異常的話readException方法里面會(huì)直接拋出 _result = (0 != _reply.readInt()); // 再讀取返回值
也就是Parcel的頭部是一個(gè)標(biāo)志位,標(biāo)志了有異常或者無(wú)異常:

但是我們看到AIDL生成的代碼都是寫入的無(wú)異常,那我們拋出的異常是怎么傳過(guò)去的呢?還記得這個(gè)打印嗎?
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)
我們?nèi)ndroid.os.Binder.execTransact這里找找看, onTransact方法實(shí)際就是在這里被調(diào)用的
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到了方法,也就是說(shuō)我們服務(wù)端有拋出異常,就會(huì)在catch代碼塊里面先就Parcel的游標(biāo)重置回0,然后往Parcel頭部寫入異常。
好,到了這里其實(shí)整個(gè)流程就差不多了,但是我發(fā)現(xiàn)我沒(méi)有看到那個(gè)”Exceptions are not yet supported across processes.”字符串,這個(gè)不支持的提示又是哪里來(lái)的呢?
讓我們?cè)倩貞浵麓a,在遇到不支持的異常類型的時(shí)候, writeException也會(huì)拋出異常:
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,代表不支持這種異常,繼續(xù)把異常拋出或者創(chuàng)建RuntimeException拋出
if (code == 0) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new RuntimeException(e);
}
...
}
由于這個(gè)writeException,已經(jīng)是在catch代碼塊里面運(yùn)行的了,沒(méi)有人再去catch它,于是就會(huì)打斷這個(gè)流程,直接跳出。形成了一個(gè)Uncaught remote exception。
最后我們找到/frameworks/base/core/jni/android_util_Binder.cpp的onTransact方法,這里通過(guò)jni調(diào)到Java的execTransact方法,調(diào)用完之后進(jìn)行ExceptionCheck,如果發(fā)現(xiàn)有異常的話就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);
}
...
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android Activity的4種啟動(dòng)模式圖文介紹
這篇文章主要給大家介紹了關(guān)于Android Activity的4種啟動(dòng)模式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Android 自定義通用的loadingview實(shí)現(xiàn)代碼
本篇文章主要介紹了Android 自定義通用的loadingview實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-01-01
簡(jiǎn)單說(shuō)說(shuō)Android中如何使用攝像頭和相冊(cè)
本篇文章主要介紹了簡(jiǎn)單說(shuō)說(shuō)Android中如何使用攝像頭和相冊(cè),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
Android編程之電池電量信息更新的方法(基于BatteryService實(shí)現(xiàn))
這篇文章主要介紹了Android編程之電池電量信息更新的方法,主要基于BatteryService實(shí)現(xiàn)該功能,以實(shí)例形式分析了Android獲取電池電量的具體步驟與實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-11-11

