On Android we have a class that wraps a LocationClient object from GMS (Google Mobile Services). (Note that LocationClient implements com.google.android.gms.common.GooglePlayServicesClient).
Unfortunately the LocationClient object has a habit of throwing DeadObjectExceptions (e.g. when we invoke locationClient.getLastLocation()), which we detect through several of our logging mechanisms. What's weird, however, is that LocationClient isn't documented as throwing DeadObjectExceptions, and furthermore I'm only able to catch said DeadObjectExceptions ~ 1/40th of the time they occur o_0. We have no repro for this issue and I've personally never seen it, however it occurs for a large number of our users.
Other notes:
[a] what is the line "Caused by: java.lang.IllegalStateException: android.os.DeadObjectException" about? Those two Exceptions types do not have an ancestor-descendant relationship
[b] I posted to the Android forum, but of course they rejected my post as 'wrong forum,' and there's no GMS forum so I'm totally out of luck.
In summary, the question is: GMS is triggering this oddly uncatchable exception, so what's up with that and what can I do?
Here's a stack trace:
com.myapp.android.service.AsyncExecutionException
at com.myapp.android.service.AsyncService$ExceptionThrower.run(MyApp:120)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4794)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:556)
at dalvik.system.NativeStart.main(NativeStart.java)
Caused by: java.lang.IllegalStateException: android.os.DeadObjectException
at com.google.android.gms.internal.ey.getLastLocation()
at com.google.android.gms.internal.ez.getLastLocation()
at com.google.android.gms.location.LocationClient.getLastLocation()
***at com.myapp.GoogleLocationProvider.getLastLocation(MyApp:92)***
at com.myapp.LocationProducer.getLocation(MyApp:183)
at com.myapp.LocationProducer.getLocationHeader(MyApp:194)
at com.myapp.API.onExecute(MyApp:344)
...
at java.lang.Thread.run(Thread.java:856)
Caused by: android.os.DeadObjectException
at android.os.BinderProxy.transact(Binder.java)
at com.google.android.gms.internal.ex$a$a.a()
at com.google.android.gms.internal.ey.getLastLocation()
at com.google.android.gms.internal.ez.getLastLocation()
***at com.google.android.gms.location.LocationClient.getLastLocation()***
at com.myapp.GoogleLocationProvider.getLastLocation(MyApp:92)
at com.myapp.LocationProducer.getLocation(MyApp:183)
at com.myapp.LocationProducer.getLocationHeader(MyApp:194)
...
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
at java.util.concurrent.FutureTask.run(FutureTask.java:137)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
at java.lang.Thread.run(Thread.java:856)
------------ ADDENDUM ------------- Here is our actual code. You'll notice we always check whether mLocationClient.isConnected() beforehand, so that's not the issue. It's possible we're getting extremely unlucky and mLocationObject dies between invoking isOnConnected() and getLastLocation(), however that seems improbable to me. I suppose I can start to log before, between, and after the calls and find out.
LocationClient mLocationClient; // populated somewhere
public Location getLastLocation() {
if (!mLocationClient.isConnected()) {
return null;
}
Location location = null;
try {
location = mLocationClient.getLastLocation();
} catch (Exception e) {
if (!handleDeadObjectException(e)) {
throw e;
}
}
return location;
}
// logs, attempts to handle depending on user configuration
private boolean handleDeadObjectException(Exception e);
From the documentation DeadObjectException:
Meaning, you are trying to reach an object in a different process that is not available anymore. For example, if you bind to a service that runs in a different process (i.e. Google Mobile Services) the IBinder you use is a local object that "represents" an object in the remote process. When the remote object is not available any more, and you are trying to use the local IBinder object, you will get the DeadObjectException.
So...
The two exceptions are not connected in any way. The IllegalStateException is the actual exception and the DeadObjectException is the root exception.
Since gms.location.LocationClient.getLastLocation() does not want to declare throw elements that expose inner implementations - working with binders and such - it simply don't. But when an exception such as DeadObjectException happens it still wants to throw and so it uses a runtime exception IllegalStateException (which doesn't need throw declaration).
:(
When working with the GMS LocationClient you need to check if LocationClient.isConnected() before interacting with the client. Note that sometimes LocationClient.isConnected() will return true but following invocation to LocationClient.getLastLocation() might still throw java.lang.IllegalStateException: android.os.DeadObjectException and the reason for that is threading issues and race conditions where the client was connected when you checked but then connection got lost before your actual action.
What you should do is a) Check if client is connected
b) Catch the IllegalStateException (and not the DeadObjectException)