Code analysis based on Android 10.0 (Q)
Binder communication between two processes is called the Client process. The Client process invokes the methods of each proxy object, essentially a cross-process communication. If this method is a synchronous method (non-Oneway modifier), the call process will go through the following phases.
As far as the application engineer is concerned, he will only see the tip of the iceberg floating on the surface of the ocean. But the absence of perception does not mean absence. If an exception occurs in these intermediate processes, it will eventually need to be handled.
This paper will focus on two issues:
- If an exception occurs in the methods of Binder entity objects in the Server process, where does the exception go?
- The nature and category of RemoteExceptions.
1. If an exception occurs in a Binder entity object’s methods, where does the exception go?
1.1 What Is The Impact on the Server?
Stub abstract classes generated by the AIDL tool are mainly used for method distribution. Therefore, if an exception is reported in an entity method, the exception will first be reported to the Stub class’s onTransact method. For example, if an exception occurs inside the intMethod method below, the exception will be sensed by the onTransact method.
out/soong/.intermediates/frameworks/base/core/tests/coretests/FrameworksCoreTests/android_common/xref/srcjars.xref/frame works/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 }
Copy the code
121 lines is called intMethod position, its internal exceptions that occur and not be processed in the onTransact, so will continue to report to the Binder. ExecTransactInternal method.
/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 =
1005observer ! =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 }
Copy the code
Line 1021 is where onTransact is called. If the exception is not handled in this method, it is further thrown to execTransact and eventually into the JNI method: JavaBBinder::onTransact.
The transitive relationship of exceptions, as shown in the diagram at the beginning of this section, is ultimately handled in two cases.
1.1.1 by Binder. ExecTransactInternal to handle exceptions
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 |
/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());
Copy the code
For the above eight exceptions, the Server process serializes the exception information into a Parcel object and sends it to the Client process via the driver.
1.1.2 JavaBBinder::onTransact handles exceptions
/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(a);363 const int32_t strict_policy_before = thread_state->getStrictModePolicy(a);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 }
Copy the code
With the exception of 8 as described in 1.1, all exceptions are handled by JavaBBinder::onTransact.
Line 368 is where the Java layer execTransact method is called. When this method throws an exception, line 371 exception detection will be true, and the next 373 lines will print the exception. Note that this information is not sent back to the Client process. As an example, these logs are printed in the Server process.
The 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 Hangl at 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 Hangl at 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)Copy the code
As you can see from the example above, the real exception thrown by the Server process is RemoteException, and RuntimeException is just a wrapper around 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 }
Copy the code
Back to the Binder execTransactInternal, encapsulation for RemoteException1898 guild will be thrown again, and for not more than eight RuntimeException, 1896 lines will also throw again.
Combining these two processing scenarios, you can infer that all exceptions that occur in the Binder entity object methods are handled. Either sends the exception information to the peer process or outputs the exception information to the local process. None of this will cause the Server process to exit.
It makes sense to think about it. As a Server process, it has no control over when it executes and what it executes, but the Client process does. Therefore, throwing exceptions is intrinsically related to the Client process, and it is not reasonable for a Client process to cause the Server process to exit. In addition, the Server process may be associated with hundreds of clients, and the error of one Client cannot affect other clients that could otherwise obtain services.
1.2 What Is the Impact of the Client?
The impact on the Client depends entirely on how the Server handles the exception. As mentioned above, the Server process can handle exceptions in two ways: one is to send the exception information to the Client process, and the other is to output the exception information to the Client process. In the following two cases, the Client process is affected.
1.2.1 Reading Exception Messages from a Parcel Object
out/soong/.intermediates/frameworks/base/core/tests/coretests/FrameworksCoreTests/android_common/xref/srcjars.xref/frame works/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 }
Copy the code
The exception message sent by the Server process via the Parcel object is finally read back at line 454.
/frameworks/base/core/java/android/os/Parcel.java
1983 public final void readException(a) {
1984 int code = readExceptionCode();
1985 if(code ! =0) {
1986 String msg = readString();
1987 readException(code, msg);
1988 }
1989 }
Copy the code
/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 }
Copy the code
The Client reconstructs the Exception object from the Exception code, MSG, and Stack trace and throws it. Since the readException method is not modified with throws, you cannot throw it directly if it is a Checked Exception (such as RemoteException, IOException) or it will result in a compilation error. So SneakyThrow is used to circumvent it.
Combined with the first case of server-side exception handling, you can see that the Client will only read one of the eight RuntimeExceptions. Because RuntimeException is an Unchecked Exception, the compilation process does not check its handling. In other words, programmers call proxy objects in a try catch block, but usually only catch remoteExceptions, not runtimeExceptions (for many programmers, no enforcement equals no action). A RuntimeException read back from a Parcel will cause the Client process to exit. As an example, note that these logs are printed in the Client process.
Timestamp: 02-27 19:20:16. 623 Process: com. Google. Android. The dialer PID: 9782 Thread: the main Java. Lang. NullPointerException: Attempt to get length of null array at 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)Copy the code
The Log said process com. Google. Android. The dialer errors and exit. But if understand Binder exception mechanism, can know the root cause is not com. Google. Android. The dialer process, and in the process of system_server.
NullPointerException is not an exception that occurs in the dialer process, but an exception that it reads from a Parcel object. By running the IActivityTaskManager$Stub$proxy. activityPaused command, you can see that the dialer process is communicating with the system_server process. Therefore, NullPointerException is thrown by the system_server process. Combined with the Remote Stack Trace, you can see that this exception occurs when arraySet.add is called.
With this information in mind, the next step in debugging should not be limited to the dialer, but to the system_server process. Otherwise will make a headache cure head, foot – healing problem.
1.2.2 Reading Back Error messages from Parcel Objects
/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 returnres ! = JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION;403 }
Copy the code
For exceptions other than the eight RuntimeExceptions, the Server process hands them over to JavaBBinder::onTransact. 1.1.2 only indicates that the Server process may output exceptions in this process, but does not mention the possible impact on the Client process.
Line 376 assigns res to JNI_FALSE, so JavaBBinder::onTransact returns 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 }
Copy the code
UNKNOWN_TRANSACTION will eventually passed IPCThreadState: : executeCommand, and assigned to the following 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 }
Copy the code
Line 1230 writes the error to the Parcel object and drives it back to the Client process.
/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 }
Copy the code
/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(a);1039 if (err == NO_ERROR) {
1040 tr.data_size = data.ipcDataSize(a);1041 tr.data.ptr.buffer = data.ipcData(a);1042 tr.offsets_size = data.ipcObjectsCount(*)sizeof(binder_size_t);
1043 tr.data.ptr.offsets = data.ipcObjects(a);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 }
Copy the code
SendReply calls writeTransactionData. Because the ERR read in line 1038 data is UNKNOWN_TRANSACTION, the final data written to the driver is not data itself, but err values in data.
Will accept data from the Client end, the err from IPCThreadState: : waitForResponse has been back to 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 }
Copy the code
The err returned on line 1319 is UNKNOWN_TRANSACTION, so it will be returned directly on line 1331 without calling signalExceptionForError to throw an exception.
out/soong/.intermediates/frameworks/base/core/tests/coretests/FrameworksCoreTests/android_common/xref/srcjars.xref/frame works/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 }
Copy the code
The err returned is _status on line 450, and if the proxy has a default implementation, the intMethod of the default implementation is eventually called. Otherwise readInt at line 455 will be called to get the return value. Since there is no data in the _reply object at this point, the int value read back is 0.
What if the data returned is not a primitive type, but a reference type? Take the following method as an example.
out/soong/.intermediates/frameworks/base/core/tests/coretests/FrameworksCoreTests/android_common/xref/srcjars.xref/frame works/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 }
Copy the code
If the data returned is of reference type, there is no data in _reply, so the return value will eventually be null.
To summarize, the Client process will not be aware of the remaining exceptions (except for the eight runtimeexceptions) returned by the Server process. The final return value of the proxy object method is determined by its type, with the base type returning 0 and the reference type returning NULL.
2. Nature and category of RemoteException
RemoteException does not inherit from RuntimeException, so it is a Checked Exception. When a program throws a Checked Exception, it must handle it in one of two ways, or it will report an error during compilation.
- Try catch blocks are used to catch exceptions.
- Decorates the method with the throws keyword, telling the callers that the method may throw the exception.
Checked Exceptions enforce programmer code. This is done because this class of exceptions expects the programmer to anticipate and prepare for them, and they could have been handled without causing the process to exit.
You should declare any Exception that the process can recover from as Checked Exception. For exceptions like remote calls/network requests /IO requests, they usually indicate that the data is not available, but they do not mean that the process is unable to continue running, so Checked Exceptions are most appropriate.
Generally speaking, do not throw a
RuntimeException
or create a subclass ofRuntimeException
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.
For Android Q, there are three subclasses of RemoteException:
- DeadObjectException
- TransactionTooLargeException
- DeadSystemException, a subclass of 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 {
Copy the code
DeadObjectException indicates that the Server process has died and the Client process is still trying to communicate. This is the meaning of the comment, but not the essence of the problem. In fact, deadObjectExceptions are not only triggered when a peer process dies, but also when a binder thread pool runs out.
/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 }
Copy the code
The Java layer on the Client side receives exceptions from runtimeExceptions built from the exception information in the Parcel object and from the BpBinder:: Transact return values on Line 1334 above.
/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 }
Copy the code
As you can see from lines 780 and 806, deadobjectExceptions received by the Java layer come from two sources:
- BpBinder:: Transact Returns DEAD_OBJECT.
- BpBinder:: Transact The returned value is FAILED_TRANSACTION, but the number of parcels transmitted is less than 200K.
2.1.1 When is DEAD_OBJECT returned?
/frameworks/native/libs/binder/IPCThreadState.cpp
854 case BR_DEAD_REPLY:
855 err = DEAD_OBJECT;
856 goto finish;
Copy the code
When the Binder driver returns CMD BR_DEAD_REPLY to user space, the JNI layer’s signalExceptionForError will receive DEAD_OBJECT. As a result, DEAD_OBJECT is actually born in the driver.
There are many places in the driver that return BR_DEAD_REPLY, and this article won’t list them all. Here is a typical example.
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 }
Copy the code
When node->proc is null, the Binder entity exits the process. So line 2987 will return BR_DEAD_REPLY.
2.1.2 When is FAILED_TRANSACTION Returned?
/frameworks/native/libs/binder/IPCThreadState.cpp
858 case BR_FAILED_REPLY:
859 err = FAILED_TRANSACTION;
860 goto finish;
Copy the code
When the Binder driver returns CMD BR_FAILED_REPLY to user space, the JNI layer’s signalExceptionForError will receive FAILED_TRANSACTION. Thus, where FAILED_TRANSACTION is actually born is also in the driver.
There are many places in the driver that return BR_FAILED_REPLY, and this article will not list them all. Here is a typical example.
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 }
Copy the code
Binder_alloc_new_buf is used to allocate the binder_buffer required for this communication, which may be subject to many errors.
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);
Copy the code
The code above shows the error returned when the binder buffer ran out of space: -enospc, which eventually returns to user space as BR_FAILED_REPLY.
When the amount of data in this communication is less than 200K, the final exception thrown to the Java layer is DeadObjectException. This exception only indicates that the binder buffer of the peer process is exhausted, not that the peer process exits.
2.2 TransactionTooLargeException
As the Binder driver returned BR_FAILED_REPLY and the transmission of data is larger than 200 k, Java layer will receive TransactionTooLargeException error.
Note that Binder drivers have many ways to return BR_FAILED_REPLY, and not finding a suitable binder_buffer to transfer data to is just one of them.
2.3 DeadSystemException
/frameworks/base/core/java/android/os/RemoteException.java
58 @UnsupportedAppUsage
59 public RuntimeException rethrowFromSystemServer(a) {
60 if (this instanceof DeadObjectException) {
61 throw new RuntimeException(new DeadSystemException());
62 } else {
63 throw new RuntimeException(this);
64 }
65 }
Copy the code
When a Client catches a RemoteException with a catch block, the method can rethrow the exception using rethrowFromSystemServer if the binder communicates with system_server on the other end.
When the original exception was a DeadObjectException, the new exception thrown is a wrapped DeadSystemException.