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:

  1. If an exception occurs in the methods of Binder entity objects in the Server process, where does the exception go?
  2. 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 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.

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.