Android RemoteExceptions and Services

36,747

Solution 1

These exceptions do indeed get thrown and you should write appropriate try/catch logic to handle the situation where a remote method you invoked on a service did not complete.

As far as your investigation, you were on the right track looking through the native sources. What you may have overlooked is that android.os.RemoteException is a actually just a base class for other Binder related exceptions and that it is a subclass, android.os.DeadObjectException, which is thrown within the native code of Binder.

An activity will see this exception if it makes use of a service running in another process that dies in the middle of performing a request. I was able to prove this to myself by making the following minor changes to Marko Gargenta's AIDLDemo example.

First, make sure the service runs in its own process by updating the AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.marakana" android:versionCode="1" android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name"
        android:theme="@android:style/Theme.Light">
        <activity android:name=".AIDLDemo" android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!--ADD THE android:process TAG TO THE SERVICE-->
        <service android:name=".AdditionService" android:process=":process2"/>
    </application>
    <uses-sdk android:minSdkVersion="3" />
</manifest> 

Then modify the add method to exit prematurely:

@Override
public IBinder onBind(Intent intent) {

    return new IAdditionService.Stub() {
        /**
         * Implementation of the add() method
         */
        public int add(int value1, int value2) throws RemoteException {
            Log.d(TAG, String.format("AdditionService.add(%d, %d)", value1,
                    value2));
            
            System.exit(-1); // KILL THE PROCESS BEFORE IT CAN RESPOND
            
            return value1 + value2;
        }

    };
}

In logcat you see the service process die, the activity receive a DeadObjectException, and ultimately the system respawn the service process.

D/AdditionService( 1379): AdditionService.add(1, 1)
I/AndroidRuntime( 1379): AndroidRuntime onExit calling exit(-1)
D/Zygote  (   32): Process 1379 exited cleanly (255)
I/ActivityManager(   58): Process com.marakana:process2 (pid 1379) has died.
W/ActivityManager(   58): Scheduling restart of crashed service com.marakana/.AdditionService in 5000ms
D/AIDLDemo( 1372): onClick failed with: android.os.DeadObjectException
W/System.err( 1372): android.os.DeadObjectException
W/System.err( 1372):    at android.os.BinderProxy.transact(Native Method)
W/System.err( 1372):    at com.marakana.IAdditionService$Stub$Proxy.add(IAdditionService.java:95)
W/System.err( 1372):    at com.marakana.AIDLDemo$1.onClick(AIDLDemo.java:81)
W/System.err( 1372):    at android.view.View.performClick(View.java:2408)
W/System.err( 1372):    at android.view.View$PerformClick.run(View.java:8816)
W/System.err( 1372):    at android.os.Handler.handleCallback(Handler.java:587)
W/System.err( 1372):    at android.os.Handler.dispatchMessage(Handler.java:92)
W/System.err( 1372):    at android.os.Looper.loop(Looper.java:123)
W/System.err( 1372):    at android.app.ActivityThread.main(ActivityThread.java:4627)
W/System.err( 1372):    at java.lang.reflect.Method.invokeNative(Native Method)
W/System.err( 1372):    at java.lang.reflect.Method.invoke(Method.java:521)
W/System.err( 1372):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
W/System.err( 1372):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
W/System.err( 1372):    at dalvik.system.NativeStart.main(Native Method)
D/AIDLDemo( 1372): onServiceDisconnected() disconnected
I/ActivityManager(   58): Start proc com.marakana:process2 for service com.marakana/.AdditionService: pid=1399 uid=10037 gids={1015}
D/AdditionService( 1399): onCreate()
D/AIDLDemo( 1372): onServiceConnected() connected

I would imagine if your service was running in the same process as your activity you might never see this exception but then again if that were the case you probably wouldn't be bothering with AIDL.

Additionally, as you discovered, Android does not tunnel exceptions between processes. If you need to communicate an error back to a calling activity then you need to use other means.

Solution 2

RemoteException is thrown if the process hosting the remote object is no longer available, which usually means the process crashed.

However, the previous comment, and also the official Android documentation is wrong about DeadObjectException being the only exception ever thrown back to the client. Some types of RuntimeExceptions thrown in your AIDL service implementation will be passed back to the client and rethrown there. If you take a look at the Binder.execTransact() method, you will see that it catches RuntimeException and passes a select few back to the client.

The RuntimeExceptions that receive this special treatment are listed below. You can also check Parcel.writeException to verify. That method is used by the Binder class to marshal the exception into a Parcel and transfer it back to the client, where it will be rethrown as part of Parcel.readException.

  • SecurityException
  • BadParcelableException
  • IllegalArgumentException
  • NullPointerException
  • IllegalStateException
  • NetworkOnMainThreadException
  • UnsupportedOperationException

I stumbled on this behavior by accident, I was seeing unexpected exceptions on the client side, and my service was not crashing when it should on an IllegalStateException. Full writeup: https://blog.classycode.com/dealing-with-exceptions-in-aidl-9ba904c6d63

Solution 3

The "Exceptions are not yet supported across processes" is the key here. RemoteException's are indeed thrown, but not directly at the point of the error occurring. EVERY uncaught RuntimeException thrown in your remote service while an aidl call is being handled will result in an Exception being received by your application, and those which are not explicitly passed over instead trigger the generic RemoteException (telling you an exception occurred in the remote process).

I recommend having generic exception catch blocks in your service for each binder method implementation, and deciding at that point whether to let the service keep running or kill it, and return an error code or throw the exception (possibly one that is supported as being passed across the IPC).

Share:
36,747
Nils Pipenbrinck
Author by

Nils Pipenbrinck

nah - won't tell anything about me..

Updated on November 03, 2020

Comments

  • Nils Pipenbrinck
    Nils Pipenbrinck over 3 years

    So I've written a Service and an Activity for the Android OS.

    My service is running in it's own process, so all the communication between my Activities and the Service happens via IPC. I use the standard Android .aidl mechanism for this.

    So far everything works fine. However, the AIDL generates all method stubs using "throws RemoteException" so I have to handle them.

    I did a quick grep on the entire Android source-code and only found three cases where this exception is ever thrown. These are in a different service that I don't connect with.

    I checked the C-sources as well because in theory RemoteExceptions can be generated using the JNI interface.. Nothing turned up.

    I have the impression that everyone just handles them like this:

      try {
    
        mService.someMethodCall (someArguments);
    
      } catch (RemoteException e) {
    
        e.printStackTrace();
    
      }
    

    This is not solid code, and I don't want something like this in my code-base.

    Besides that: I tried to throw a RemoteException via IPC myself and all I got was a stack-trace and a system log message that tells me that exceptions aren't supported yet. My application never saw the exception and the services that threw the exception ended up in a very strange state (halfway working) :-(

    The questions are:

    • Do these exceptions ever get thrown?

    • Has anyone ever seen such a try-catch block catching a RemoteException?

    • Could it be that they don't exist and that we are just forced to deal with them because the "throws RemoteException" is dead code or a left-over inside the AIDL compiler?

    Disclamer: I haven't read the entire source-code. I used Grep to find the occurrences of RemoteException, so I may have missed some due to different whitespace usage.

  • Nils Pipenbrinck
    Nils Pipenbrinck almost 14 years
    Hi Tim. What a nice answer! My service was running in a different process, and I've seen the DeadObjectExceptions here and there during stress-testing. Alway when I used the shell and killed the service-process while a binded application was running. The DeadObjectException have always been on my todo-list. Now that I know that they are just a subclass of RemoteException I know what to do.. Thank you a lot. I leave the question open for another day in case someone is even more insightful! Cheers, Nils
  • Tim Kryger
    Tim Kryger almost 14 years
    @Nils Pipenbrinck Glad I could help out. Also, now that you have accepted my answer, could I trouble you to award the bounty as well?
  • Nils Pipenbrinck
    Nils Pipenbrinck almost 14 years
    I wasn't aware that you you have to award it. I thought it was tied to the answer.. :-)
  • Mr_and_Mrs_D
    Mr_and_Mrs_D about 11 years
    Could you please update the link to native code of binder ? This one times out consistently (grab this opportunity to correct a couple typos also cause this answer is really exemplary)
  • Rolf
    Rolf over 9 years
  • NehaK
    NehaK over 5 years
    Hi pls help me with this issue, stackoverflow.com/questions/52370890/…