日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[芦半山]Binder的异常机制

發(fā)布時間:2023/12/20 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [芦半山]Binder的异常机制 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文中代碼分析基于Android 10.0 (Q)

兩個進程之間若是要進行Binder通信,那么發(fā)起通信的一端我們就稱它為Client進程。Client進程調(diào)用每一個代理對象的方法,本質(zhì)上都是一次跨進程通信。如果這個方法是同步方法(非oneway修飾),那么此調(diào)用過程將會經(jīng)歷如下幾個階段。

對應用工程師而言,他只會看到浮在海面的冰山一角,至于隱藏在海面下的系統(tǒng)調(diào)用和跨進程通信,是無需他感知的。但無需感知并不代表不存在。這些中間過程若是發(fā)生了異常,終歸是需要被處理的。

本文將重點闡述兩個問題:

  • 如果Server進程中Binder實體對象的方法發(fā)生異常,該異常將會去向何處?

  • RemoteException的本質(zhì)和類別。

  • 1. 如果Binder實體對象的方法中發(fā)生異常,該異常將會去向何處?

    1.1 Server端有什么影響?

    AIDL工具生成的Stub抽象類主要用于方法派發(fā)。因此實體方法中如果報出異常的話,異常將首先會報給Stub類的onTransact方法。以如下intMethod方法為例,如果其內(nèi)部發(fā)生異常,則該異常將會被onTransact方法感知。

    out/soong/.intermediates/frameworks/base/core/tests/coretests/FrameworksCoreTests/android_common/xref/srcjars.xref/frameworks/base/core/tests/coretests/src/android/os/IAidlTest.java

    106 @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException 107 { 108 java.lang.String descriptor = DESCRIPTOR; 109 switch (code) 110 { 111 case INTERFACE_TRANSACTION: 112 { 113 reply.writeString(descriptor); 114 return true; 115 } 116 case TRANSACTION_intMethod: 117 { 118 data.enforceInterface(descriptor); 119 int _arg0; 120 _arg0 = data.readInt(); 121 int _result = this.intMethod(_arg0); 122 reply.writeNoException(); 123 reply.writeInt(_result); 124 return true; 125 }

    121行是調(diào)用intMethod的位置,其內(nèi)部發(fā)生的異常并沒有在onTransact中被處理,因此會繼續(xù)上報給Binder.execTransactInternal方法。

    /frameworks/base/core/java/android/os/Binder.java

    1000 private boolean execTransactInternal(int code, long dataObj, long replyObj, int flags, 1001 int callingUid) { 1002 // Make sure the observer won't change while processing a transaction. 1003 final BinderInternal.Observer observer = sObserver; 1004 final CallSession callSession = 1005 observer != null ? observer.callStarted(this, code, UNSET_WORKSOURCE) : null; 1006 Parcel data = Parcel.obtain(dataObj); 1007 Parcel reply = Parcel.obtain(replyObj); 1008 // theoretically, we should call transact, which will call onTransact, 1009 // but all that does is rewind it, and we just got these from an IPC, 1010 // so we'll just call it directly. 1011 boolean res; 1012 // Log any exceptions as warnings, don't silently suppress them. 1013 // If the call was FLAG_ONEWAY then these exceptions disappear into the ether. 1014 final boolean tracingEnabled = Binder.isTracingEnabled(); 1015 try { 1016 if (tracingEnabled) { 1017 final String transactionName = getTransactionName(code); 1018 Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":" 1019 + (transactionName != null ? transactionName : code)); 1020 } 1021 res = onTransact(code, data, reply, flags); 1022 } catch (RemoteException|RuntimeException e) { 1023 if (observer != null) { 1024 observer.callThrewException(callSession, e); 1025 } 1026 if (LOG_RUNTIME_EXCEPTION) { 1027 Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e); 1028 } 1029 if ((flags & FLAG_ONEWAY) != 0) { 1030 if (e instanceof RemoteException) { 1031 Log.w(TAG, "Binder call failed.", e); 1032 } else { 1033 Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e); 1034 } 1035 } else { 1036 // Clear the parcel before writing the exception 1037 reply.setDataSize(0); 1038 reply.setDataPosition(0); 1039 reply.writeException(e); 1040 } 1041 res = true; 1042 } finally { 1043 if (tracingEnabled) { 1044 Trace.traceEnd(Trace.TRACE_TAG_ALWAYS); 1045 } 1046 if (observer != null) { 1047 // The parcel RPC headers have been called during onTransact so we can now access 1048 // the worksource uid from the parcel. 1049 final int workSourceUid = sWorkSourceProvider.resolveWorkSourceUid( 1050 data.readCallingWorkSourceUid()); 1051 observer.callEnded(callSession, data.dataSize(), reply.dataSize(), workSourceUid); 1052 } 1053 } 1054 checkParcel(this, code, reply, "Unreasonably large binder reply buffer"); 1055 reply.recycle(); 1056 data.recycle(); 1057 1058 // Just in case -- we are done with the IPC, so there should be no more strict 1059 // mode violations that have gathered for this thread. Either they have been 1060 // parceled and are now in transport off to the caller, or we are returning back 1061 // to the main transaction loop to wait for another incoming transaction. Either 1062 // way, strict mode begone! 1063 StrictMode.clearGatheredViolations(); 1064 return res; 1065 }

    1021行是調(diào)用onTransact的位置。如果異常沒有在此方法中被處理,將會進一步拋出給execTransact,最終進入JNI方法:JavaBBinder::onTransact。

    異常的傳遞關(guān)系正如本節(jié)開始的那幅圖所示,其最終的處理分為了兩種情況。

    1.1.1 由Binder.execTransactInternal來處理異常

    ExceptionCode
    SecurityExceptionEX_SECURITY
    BadParcelableExceptionEX_BAD_PARCELABLE
    IllegalArgumentExceptionEX_ILLEGAL_ARGUMENT
    NullPointerExceptionEX_NULL_POINTER
    IllegalStateExceptionEX_ILLEGAL_STATE
    NetworkOnMainThreadExceptionEX_NETWORK_MAIN_THREAD
    UnsupportedOperationExceptionEX_UNSUPPORTED_OPERATION
    ServiceSpecificExceptionEX_SERVICE_SPECIFIC

    /frameworks/base/core/java/android/os/Parcel.java

    1868 public final void writeException(@NonNull Exception e) { 1869 int code = 0; 1870 if (e instanceof Parcelable 1871 && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) { 1872 // We only send Parcelable exceptions that are in the 1873 // BootClassLoader to ensure that the receiver can unpack them 1874 code = EX_PARCELABLE; 1875 } else if (e instanceof SecurityException) { 1876 code = EX_SECURITY; 1877 } else if (e instanceof BadParcelableException) { 1878 code = EX_BAD_PARCELABLE; 1879 } else if (e instanceof IllegalArgumentException) { 1880 code = EX_ILLEGAL_ARGUMENT; 1881 } else if (e instanceof NullPointerException) { 1882 code = EX_NULL_POINTER; 1883 } else if (e instanceof IllegalStateException) { 1884 code = EX_ILLEGAL_STATE; 1885 } else if (e instanceof NetworkOnMainThreadException) { 1886 code = EX_NETWORK_MAIN_THREAD; 1887 } else if (e instanceof UnsupportedOperationException) { 1888 code = EX_UNSUPPORTED_OPERATION; 1889 } else if (e instanceof ServiceSpecificException) { 1890 code = EX_SERVICE_SPECIFIC; 1891 } 1892 writeInt(code); 1893 StrictMode.clearGatheredViolations(); 1894 if (code == 0) { 1895 if (e instanceof RuntimeException) { 1896 throw (RuntimeException) e; 1897 } 1898 throw new RuntimeException(e); 1899 } 1900 writeString(e.getMessage());

    針對如上8種異常,Server進程會將異常的信息序列化寫入Parcel對象,然后經(jīng)由驅(qū)動發(fā)送給Client進程。

    1.1.2 由JavaBBinder::onTransact來處理異常

    /frameworks/base/core/jni/android_util_Binder.cpp

    355 status_t onTransact( 356 uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) override 357 { 358 JNIEnv* env = javavm_to_jnienv(mVM); 359 360 ALOGV("onTransact() on %p calling object %p in env %p vm %p\n", this, mObject, env, mVM); 361 362 IPCThreadState* thread_state = IPCThreadState::self(); 363 const int32_t strict_policy_before = thread_state->getStrictModePolicy(); 364 365 //printf("Transact from %p to Java code sending: ", this); 366 //data.print(); 367 //printf("\n"); 368 jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, 369 code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags); 370 371 if (env->ExceptionCheck()) { 372 ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred()); 373 report_exception(env, excep.get(), 374 "*** Uncaught remote exception! " 375 "(Exceptions are not yet supported across processes.)"); 376 res = JNI_FALSE; 377 }

    除了1.1中所述的8中異常外,其余所有異常都交由JavaBBinder::onTransact來處理。

    368行是調(diào)用Java層execTransact方法的位置。當該方法拋出異常時,371行的異常檢測將為true,而其后的373行會將異常打印出來。需要注意的是,這些信息并不會被發(fā)送回Client進程。以下為示例,這些Log是在Server進程中輸出的。

    2020-04-15 21:54:00.454 1433-1453/com.hangl.androidemptypage:server E/JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.)java.lang.RuntimeException: android.os.RemoteException: Test by Hanglat android.os.Parcel.writeException(Parcel.java:1898)at android.os.Binder.execTransactInternal(Binder.java:1039)at android.os.Binder.execTransact(Binder.java:994)Caused by: android.os.RemoteException: Test by Hanglat com.hangl.androidemptypage.ServerB$ServiceB.sendMsg(ServerB.java:24)at com.hangl.androidemptypage.IServiceB$Stub.onTransact(IServiceB.java:64)at android.os.Binder.execTransactInternal(Binder.java:1021)at android.os.Binder.execTransact(Binder.java:994)

    從上述示例中可以看出,此次Server進程真正拋出的異常為RemoteException,而RuntimeException只是對RemoteException的一層封裝。

    /frameworks/base/core/java/android/os/Parcel.java

    1894 if (code == 0) { 1895 if (e instanceof RuntimeException) { 1896 throw (RuntimeException) e; 1897 } 1898 throw new RuntimeException(e); 1899 }

    回到Binder.execTransactInternal,對于RemoteException1898行會將其進行封裝再次拋出,而對于非以上8種的RuntimeException,1896行也會將其再次拋出。

    綜合以上兩種處理情況,可以推斷所有Binder實體對象方法中發(fā)生的異常都會被處理。無非一種是將異常信息發(fā)送給對端進程,另一種是將異常信息在本進程輸出。而這些處理都不會使Server進程退出。

    仔細思考這樣設計也是很合理的。作為Server進程,它在什么時候執(zhí)行,該執(zhí)行些什么都不由自己掌控,而是由Client進程控制。因此拋出異常本質(zhì)上與Client進程相關(guān),讓一個Client進程的行為導致Server進程退出顯然是不合理的。此外,Server進程可能關(guān)聯(lián)著千百個Client,不能由于一個Client的錯誤行為而影響本可以正常獲取服務的其他Client。

    1.2 Client端有什么影響?

    Client端受到的影響完全取決于Server端如何處理異常。上文中已經(jīng)闡明,Server進程會分兩種情況來處理異常:一種是將異常信息發(fā)送給Client進程,另一個種是將異常信息在本進程中輸出。以下按照這兩個情況分別討論Client進程受到的影響。

    1.2.1 從Parcel對象中讀回異常信息

    out/soong/.intermediates/frameworks/base/core/tests/coretests/FrameworksCoreTests/android_common/xref/srcjars.xref/frameworks/base/core/tests/coretests/src/android/os/IAidlTest.java

    442 @Override public int intMethod(int a) throws android.os.RemoteException 443 { 444 android.os.Parcel _data = android.os.Parcel.obtain(); 445 android.os.Parcel _reply = android.os.Parcel.obtain(); 446 int _result; 447 try { 448 _data.writeInterfaceToken(DESCRIPTOR); 449 _data.writeInt(a); 450 boolean _status = mRemote.transact(Stub.TRANSACTION_intMethod, _data, _reply, 0); 451 if (!_status && getDefaultImpl() != null) { 452 return getDefaultImpl().intMethod(a); 453 } 454 _reply.readException(); 455 _result = _reply.readInt(); 456 } 457 finally { 458 _reply.recycle(); 459 _data.recycle(); 460 } 461 return _result; 462 }

    Server進程通過Parcel對象發(fā)送的異常信息最終在454行被讀回。

    /frameworks/base/core/java/android/os/Parcel.java

    1983 public final void readException() { 1984 int code = readExceptionCode(); 1985 if (code != 0) { 1986 String msg = readString(); 1987 readException(code, msg); 1988 } 1989 }

    /frameworks/base/core/java/android/os/Parcel.java

    2033 public final void readException(int code, String msg) { 2034 String remoteStackTrace = null; 2035 final int remoteStackPayloadSize = readInt(); 2036 if (remoteStackPayloadSize > 0) { 2037 remoteStackTrace = readString(); 2038 } 2039 Exception e = createException(code, msg); 2040 // Attach remote stack trace if availalble 2041 if (remoteStackTrace != null) { 2042 RemoteException cause = new RemoteException( 2043 "Remote stack trace:\n" + remoteStackTrace, null, false, false); 2044 try { 2045 Throwable rootCause = ExceptionUtils.getRootCause(e); 2046 if (rootCause != null) { 2047 rootCause.initCause(cause); 2048 } 2049 } catch (RuntimeException ex) { 2050 Log.e(TAG, "Cannot set cause " + cause + " for " + e, ex); 2051 } 2052 } 2053 SneakyThrow.sneakyThrow(e); 2054 }

    Client端根據(jù)異常code,msg和stack trace重新構(gòu)建出Exception對象,并將其拋出。由于readException方法并沒有用throws修飾,所以如果該異常是Checked Exception(譬如RemoteException,IOException)就不能夠直接拋出,否則會產(chǎn)生編譯錯誤。因此這里采用了SneakyThrow來進行規(guī)避。

    結(jié)合Server端異常處理的第一種情況,可以知道Client端只會讀到8種RuntimeException中的一種。由于RuntimeException屬于Unchecked Exception,因此編譯過程并不會去檢查對它的處理。換句話說,程序員在調(diào)用代理對象的方式時雖然會用try catch代碼塊,但通常只會去catch RemoteException,而不會去catch RuntimeException(對于很多程序員而言,不強制等于不做)。這樣一來,Parcel讀回來的RuntimeException將會導致Client進程退出。以下為示例,注意這些Log是在Client進程中輸出的。

    Timestamp: 02-27 19:20:16.623 Process: com.google.android.dialer PID: 9782 Thread: main java.lang.NullPointerException: Attempt to get length of null arrayat android.os.Parcel.createException(Parcel.java:2077)at android.os.Parcel.readException(Parcel.java:2039)at android.os.Parcel.readException(Parcel.java:1987)at android.app.IActivityTaskManager$Stub$Proxy.activityPaused(IActivityTaskManager.java:4489)at android.app.servertransaction.PauseActivityItem.postExecute(PauseActivityItem.java:64)at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:177)at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)at android.os.Handler.dispatchMessage(Handler.java:107)at android.os.Looper.loop(Looper.java:214)at android.app.ActivityThread.main(ActivityThread.java:7356)at java.lang.reflect.Method.invoke(Native Method)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) Caused by: android.os.RemoteException: Remote stack trace:at android.util.ArraySet.add(ArraySet.java:422)at com.android.server.wm.AppWindowToken.setVisibility(AppWindowToken.java:579)at com.android.server.wm.ActivityRecord.setVisibility(ActivityRecord.java:1844)at com.android.server.wm.ActivityStackSupervisor.realStartActivityLocked(ActivityStackSupervisor.java:774)at com.android.server.wm.ActivityStackSupervisor.startSpecificActivityLocked(ActivityStackSupervisor.java:979)

    上述Log表示進程com.google.android.dialer出現(xiàn)錯誤并退出。但如果了解Binder的異常機制,就會知道問題的根源不在com.google.android.dialer進程,而在system_server進程。

    NullPointerException并不是dialer進程發(fā)生的異常,而是它從Parcel對象中讀取的異常。通過IActivityTaskManager$Stub$Proxy.activityPaused可知,此時dialer進程正在和system_server進程通信。因此該NullPointerException是system_server進程中拋出的異常。再結(jié)合Remote stack trace,可知此異常是在調(diào)用ArraySet.Add時出現(xiàn)的。

    結(jié)合這些信息,接下來調(diào)試的方向就不應該局限在dialer中,而是著眼于system_server進程。否則就會犯了頭疼醫(yī)頭,腳痛醫(yī)腳的問題。

    1.2.2 從Parcel對象中讀回Error信息

    /frameworks/base/core/jni/android_util_Binder.cpp

    368 jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, 369 code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags); 370 371 if (env->ExceptionCheck()) { 372 ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred()); 373 report_exception(env, excep.get(), 374 "*** Uncaught remote exception! " 375 "(Exceptions are not yet supported across processes.)"); 376 res = JNI_FALSE; 377 } ...... 402 return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION; 403 }

    對于8種RuntimeException之外的其余異常,Server進程會將它們交給JavaBBinder::onTransact來處理。1.1.2中只說明了Server進程會在本進程中輸出異常,但并未提及可能對Client進程產(chǎn)生的影響。

    376行將res賦值為JNI_FALSE,因此JavaBBinder::onTransact最終返回UNKNOWN_TRANSACTION。

    /frameworks/native/libs/binder/Binder.cpp

    123 status_t BBinder::transact( 124 uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) 125 { 126 data.setDataPosition(0); 127 128 status_t err = NO_ERROR; 129 switch (code) { 130 case PING_TRANSACTION: 131 reply->writeInt32(pingBinder()); 132 break; 133 default: 134 err = onTransact(code, data, reply, flags); 135 break; 136 } 137 138 if (reply != nullptr) { 139 reply->setDataPosition(0); 140 } 141 142 return err; 143 }

    UNKNOWN_TRANSACTION最終會傳入IPCThreadState::executeCommand中,并賦值給如下的error。

    /frameworks/native/libs/binder/IPCThreadState.cpp

    1228 if ((tr.flags & TF_ONE_WAY) == 0) { 1229 LOG_ONEWAY("Sending reply to %d!", mCallingPid); 1230 if (error < NO_ERROR) reply.setError(error); 1231 sendReply(reply, 0); 1232 } else { 1233 LOG_ONEWAY("NOT sending reply to %d!", mCallingPid); 1234 }

    1230行會將該error寫入Parcel對象中,并通過驅(qū)動傳回Client進程。

    /frameworks/native/libs/binder/IPCThreadState.cpp

    821 status_t IPCThreadState::sendReply(const Parcel& reply, uint32_t flags) 822 { 823 status_t err; 824 status_t statusBuffer; 825 err = writeTransactionData(BC_REPLY, flags, -1, 0, reply, &statusBuffer); 826 if (err < NO_ERROR) return err; 827 828 return waitForResponse(nullptr, nullptr); 829 }

    /frameworks/native/libs/binder/IPCThreadState.cpp

    1025 status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, 1026 int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer) 1027 { 1028 binder_transaction_data tr; 1029 1030 tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */ 1031 tr.target.handle = handle; 1032 tr.code = code; 1033 tr.flags = binderFlags; 1034 tr.cookie = 0; 1035 tr.sender_pid = 0; 1036 tr.sender_euid = 0; 1037 1038 const status_t err = data.errorCheck(); 1039 if (err == NO_ERROR) { 1040 tr.data_size = data.ipcDataSize(); 1041 tr.data.ptr.buffer = data.ipcData(); 1042 tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t); 1043 tr.data.ptr.offsets = data.ipcObjects(); 1044 } else if (statusBuffer) { 1045 tr.flags |= TF_STATUS_CODE; 1046 *statusBuffer = err; 1047 tr.data_size = sizeof(status_t); 1048 tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer); 1049 tr.offsets_size = 0; 1050 tr.data.ptr.offsets = 0; 1051 } else { 1052 return (mLastError = err); 1053 } 1054 1055 mOut.writeInt32(cmd); 1056 mOut.write(&tr, sizeof(tr)); 1057 1058 return NO_ERROR; 1059 }

    sendReply會調(diào)用writeTransactionData,由于1038行data中讀取的err是UNKNOWN_TRANSACTION,所以最終寫入驅(qū)動的數(shù)據(jù)并不是data本身,而是data中的err值。

    Client端接受數(shù)據(jù)后,會將該err從IPCThreadState::waitForResponse一直傳回給android_os_BinderProxy_transact。

    /frameworks/base/core/jni/android_util_Binder.cpp

    1319 status_t err = target->transact(code, *data, reply, flags); 1320 //if (reply) printf("Transact from Java code to %p received: ", target); reply->print(); 1321 1322 if (kEnableBinderSample) { 1323 if (time_binder_calls) { 1324 conditionally_log_binder_call(start_millis, target, code); 1325 } 1326 } 1327 1328 if (err == NO_ERROR) { 1329 return JNI_TRUE; 1330 } else if (err == UNKNOWN_TRANSACTION) { 1331 return JNI_FALSE; 1332 } 1333 1334 signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize()); 1335 return JNI_FALSE; 1336 }

    1319行返回的err是UNKNOWN_TRANSACTION,因此最終會在1331行直接返回,而不會調(diào)用signalExceptionForError來拋出異常。

    out/soong/.intermediates/frameworks/base/core/tests/coretests/FrameworksCoreTests/android_common/xref/srcjars.xref/frameworks/base/core/tests/coretests/src/android/os/IAidlTest.java

    442 @Override public int intMethod(int a) throws android.os.RemoteException 443 { 444 android.os.Parcel _data = android.os.Parcel.obtain(); 445 android.os.Parcel _reply = android.os.Parcel.obtain(); 446 int _result; 447 try { 448 _data.writeInterfaceToken(DESCRIPTOR); 449 _data.writeInt(a); 450 boolean _status = mRemote.transact(Stub.TRANSACTION_intMethod, _data, _reply, 0); 451 if (!_status && getDefaultImpl() != null) { 452 return getDefaultImpl().intMethod(a); 453 } 454 _reply.readException(); 455 _result = _reply.readInt(); 456 } 457 finally { 458 _reply.recycle(); 459 _data.recycle(); 460 } 461 return _result; 462 }

    返回的err表現(xiàn)為450行的_status,如果該代理有默認的實現(xiàn),那么最終會調(diào)用默認實現(xiàn)的intMethod。否則將會調(diào)用455行的readInt來獲取返回值。由于此時的_reply對象中并無數(shù)據(jù),因此讀回的int值為0。

    那如果返回的數(shù)據(jù)不是基本類型,而是引用類型呢?以下面方法舉例。

    out/soong/.intermediates/frameworks/base/core/tests/coretests/FrameworksCoreTests/android_common/xref/srcjars.xref/frameworks/base/core/tests/coretests/src/android/os/IAidlTest.java

    482 if ((0!=_reply.readInt())) { 483 _result = android.os.AidlTest.TestParcelable.CREATOR.createFromParcel(_reply); 484 } 485 else { 486 _result = null; 487 }

    若是返回的數(shù)據(jù)為引用類型,由于_reply中無數(shù)據(jù),因此返回值最終為null。

    總結(jié)一下,對于Server進程中返回的其余異常(除8種RuntimeException以外),Client進程將不會感知到。最終代理對象方法的返回值將由其類型決定,基本類型返回0,引用類型返回null。

    2. RemoteException的本質(zhì)和類別

    RemoteException不繼承于RuntimeException,因此它是一種Checked Exception。對于程序中拋出的Checked Exception,它必須按照如下兩種方式中的一種來處理,否則會在編譯時報錯。

    • 用try catch代碼塊來捕獲異常。

    • 將該方法用throws關(guān)鍵字修飾,告訴調(diào)用者們此方法可能會拋出該異常。

    Checked Exception會對程序員的代碼有強制要求。之所以這么做,是因為該類異常希望程序員可以提前預知并做好準備,它們本可以被處理,用不著讓進程退出。

    以下是Oracle官網(wǎng)的解釋,表示對于那些進程可以從中恢復的異常,都應該把它聲明為Checked Exception。像遠程調(diào)用/網(wǎng)絡請求/IO請求之類的異常,它們反映的多是數(shù)據(jù)無法獲取,但并不意味著進程到了無法繼續(xù)運行的地步,因此用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

    /frameworks/base/core/java/android/os/DeadObjectException.java

    20 /** 21 * The object you are calling has died, because its hosting process 22 * no longer exists. 23 */ 24 public class DeadObjectException extends RemoteException {

    DeadObjectException反映的是Server進程已經(jīng)掛掉,而Client進程仍舊嘗試通信的錯誤。這句話是注釋的含義,但不是問題的本質(zhì)。事實上DeadObjectException不僅僅有對端進程掛掉這一種情況可以觸發(fā),binder線程池耗盡也會觸發(fā)該異常。

    /frameworks/base/core/jni/android_util_Binder.cpp

    1319 status_t err = target->transact(code, *data, reply, flags); 1320 //if (reply) printf("Transact from Java code to %p received: ", target); reply->print(); 1321 1322 if (kEnableBinderSample) { 1323 if (time_binder_calls) { 1324 conditionally_log_binder_call(start_millis, target, code); 1325 } 1326 } 1327 1328 if (err == NO_ERROR) { 1329 return JNI_TRUE; 1330 } else if (err == UNKNOWN_TRANSACTION) { 1331 return JNI_FALSE; 1332 } 1333 1334 signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize()); 1335 return JNI_FALSE; 1336 }

    Client端Java層接收到的異常有兩處來源,一處是根據(jù)Parcel對象中異常信息構(gòu)建出的RuntimeException,另一處就是如上1334行根據(jù)BpBinder::transact返回值構(gòu)建出的各種異常。

    /frameworks/base/core/jni/android_util_Binder.cpp

    741 void signalExceptionForError(JNIEnv* env, jobject obj, status_t err, 742 bool canThrowRemoteException, int parcelSize) 743 { 744 switch (err) { ...... 778 case DEAD_OBJECT: 779 // DeadObjectException is a checked exception, only throw from certain methods. 780 jniThrowException(env, canThrowRemoteException 781 ? "android/os/DeadObjectException" 782 : "java/lang/RuntimeException", NULL); 783 break; 784 case UNKNOWN_TRANSACTION: 785 jniThrowException(env, "java/lang/RuntimeException", "Unknown transaction code"); 786 break; 787 case FAILED_TRANSACTION: { 788 ALOGE("!!! FAILED BINDER TRANSACTION !!! (parcel size = %d)", parcelSize); 789 const char* exceptionToThrow; 790 char msg[128]; 791 // TransactionTooLargeException is a checked exception, only throw from certain methods. 792 // FIXME: Transaction too large is the most common reason for FAILED_TRANSACTION 793 // but it is not the only one. The Binder driver can return BR_FAILED_REPLY 794 // for other reasons also, such as if the transaction is malformed or 795 // refers to an FD that has been closed. We should change the driver 796 // to enable us to distinguish these cases in the future. 797 if (canThrowRemoteException && parcelSize > 200*1024) { 798 // bona fide large payload 799 exceptionToThrow = "android/os/TransactionTooLargeException"; 800 snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize); 801 } else { 802 // Heuristic: a payload smaller than this threshold "shouldn't" be too 803 // big, so it's probably some other, more subtle problem. In practice 804 // it seems to always mean that the remote process died while the binder 805 // transaction was already in flight. 806 exceptionToThrow = (canThrowRemoteException) 807 ? "android/os/DeadObjectException" 808 : "java/lang/RuntimeException"; 809 snprintf(msg, sizeof(msg)-1, 810 "Transaction failed on small parcel; remote process probably died"); 811 } 812 jniThrowException(env, exceptionToThrow, msg); 813 } break; ...... 861 } 862 }

    由780和806行可知,Java層接收到的DeadObjectException有兩處來歷:

    • BpBinder::transact返回值為DEAD_OBJECT。

    • BpBinder::transact返回值為FAILED_TRANSACTION,但是本次傳輸?shù)腜arcel小于200K。

    2.1.1 在什么情況下返回DEAD_OBJECT?

    /frameworks/native/libs/binder/IPCThreadState.cpp

    854 case BR_DEAD_REPLY: 855 err = DEAD_OBJECT; 856 goto finish;

    當Binder驅(qū)動返回給用戶空間的cmd為BR_DEAD_REPLY時,JNI層的signalExceptionForError將會接收到DEAD_OBJECT。由此可知,DEAD_OBJECT真正出生的地方是在驅(qū)動里。

    驅(qū)動中返回BR_DEAD_REPLY的地方有很多,本文不會一一列舉。這里只展示其中典型的一例。

    2972 static struct binder_node *binder_get_node_refs_for_txn( 2973 struct binder_node *node, 2974 struct binder_proc **procp, 2975 uint32_t *error) 2976 { 2977 struct binder_node *target_node = NULL; 2978 2979 binder_node_inner_lock(node); 2980 if (node->proc) { 2981 target_node = node; 2982 binder_inc_node_nilocked(node, 1, 0, NULL); 2983 binder_inc_node_tmpref_ilocked(node); 2984 node->proc->tmp_ref++; 2985 *procp = node->proc; 2986 } else 2987 *error = BR_DEAD_REPLY; 2988 binder_node_inner_unlock(node); 2989 2990 return target_node; 2991 }

    當node->proc為null時,表明該Binder實體所在的進程已經(jīng)退出。因此2987行會返回BR_DEAD_REPLY。

    2.1.2 在什么情況下返回FAILED_TRANSACTION?

    /frameworks/native/libs/binder/IPCThreadState.cpp

    858 case BR_FAILED_REPLY: 859 err = FAILED_TRANSACTION; 860 goto finish;

    當Binder驅(qū)動返回給用戶空間的cmd為BR_FAILED_REPLY時,JNI層的signalExceptionForError將會接收到FAILED_TRANSACTION。由此可知,FAILED_TRANSACTION真正出生的地方也是在驅(qū)動里。

    驅(qū)動中返回BR_FAILED_REPLY的地方有很多,本文不會一一列舉。這里只展示其中典型的一例。

    3267 t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size, 3268 tr->offsets_size, extra_buffers_size, 3269 !reply && (t->flags & TF_ONE_WAY)); 3270 if (IS_ERR(t->buffer)) { 3271 /* 3272 * -ESRCH indicates VMA cleared. The target is dying. 3273 */ 3274 return_error_param = PTR_ERR(t->buffer); 3275 return_error = return_error_param == -ESRCH ? 3276 BR_DEAD_REPLY : BR_FAILED_REPLY; 3277 return_error_line = __LINE__; 3278 t->buffer = NULL; 3279 goto err_binder_alloc_buf_failed; 3280 }

    binder_alloc_new_buf用于分配此次通信所需的binder_buffer,其中可能會出現(xiàn)諸多錯誤。

    387 if (is_async && 388 alloc->free_async_space < size + sizeof(struct binder_buffer)) { 389 binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, 390 "%d: binder_alloc_buf size %zd failed, no async space left\n", 391 alloc->pid, size); 392 return ERR_PTR(-ENOSPC); 393 } ...... 442 binder_alloc_debug(BINDER_DEBUG_USER_ERROR, 443 "allocated: %zd (num: %zd largest: %zd), free: %zd (num: %zd largest: %zd)\n", 444 total_alloc_size, allocated_buffers, 445 largest_alloc_size, total_free_size, 446 free_buffers, largest_free_size); 447 return ERR_PTR(-ENOSPC);

    上述代碼展現(xiàn)了binder buffer空間耗盡時返回的錯誤:-ENOSPC,此錯誤最終會以BR_FAILED_REPLY的方式返回用戶空間。

    當此次通信的數(shù)據(jù)量<200K時,最終往Java層拋出的異常為DeadObjectException。而這次異常只表示對端進程的binder buffer耗盡,并非表示對端進程退出了。

    2.2 TransactionTooLargeException

    當Binder驅(qū)動返回BR_FAILED_REPLY且此次傳輸?shù)臄?shù)據(jù)大于200K,則Java層會接收到TransactionTooLargeException的錯誤。

    需要注意的是,Binder驅(qū)動中返回BR_FAILED_REPLY的地方有很多,找不到合適的binder_buffer來傳輸數(shù)據(jù)只是其中的一種。

    2.3 DeadSystemException

    /frameworks/base/core/java/android/os/RemoteException.java

    58 @UnsupportedAppUsage 59 public RuntimeException rethrowFromSystemServer() { 60 if (this instanceof DeadObjectException) { 61 throw new RuntimeException(new DeadSystemException()); 62 } else { 63 throw new RuntimeException(this); 64 } 65 }

    當Client用catch代碼塊捕獲RemoteException時,如果此次binder通信的對端為system_server,該方法便可以使用rethrowFromSystemServer重新拋出異常。

    當原本的異常為DeadObjectException時,那么新拋出的異常就是封裝過的DeadSystemException。

    ? 回復「?籃球的大肚子」進入技術(shù)群聊

    回復「1024」獲取1000G學習資料

    總結(jié)

    以上是生活随笔為你收集整理的[芦半山]Binder的异常机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。