DeadObjectException in GMS::LocationClient (Android)
Asked Answered
A

1

3

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);
Antipasto answered 18/6, 2014 at 14:50 Comment(4)
So did you figured out, how you handled the problem??Rafiq
Hi, I got the same issue. Have you find out the solutionMonarchy
@MrMike nope. I left that company, and saw this briefly at another company, but the issue apparently went away for us by mid-2015. I've avoided geo code since then.Antipasto
Thank for your replyMonarchy
B
9

From the documentation DeadObjectException:

The object you are calling has died, because its hosting process no longer exists.

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...

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

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).

[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?

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

if ( mLocationClient != null && mLocationClient.isConnected() ) {   
    mLocationClient.getLastLocation();
}

b) Catch the IllegalStateException (and not the DeadObjectException)

if ( mLocationClient != null && mLocationClient.isConnected() ) {   
    try {
        mLocationClient.getLastLocation();
    } catch (IllegalStateException ex) {
        // This will catch the exception, handle as needed
    }
}
Babu answered 6/7, 2014 at 9:22 Comment(5)
I commented as an add-on to the original post (it took too much space to write here). Also note handleDeadObjectException(Exception e) handles all types of Exceptions, and there's always been explicit logic in there to log and handle with care both IllegalStateExceptions and DeadObjectExceptions, so that should address your point (b). We're beginning to think that we need to create/destroy LocationClient objects at a reasonable clip--not keeping them alive too long, not deleting them too quickly as user thrashes between activities. Switching to a bound service may help us do this.Antipasto
First, catching the exception is something you should be able to do in any case. So there shouldn't be any issue of actual crashing because of this. Second, do you create multiple location clients? maybe you could try managing only one LocationClient...Babu
Right, that I can't catch the exception is what's totally surreal about this. No, we create one LocationClient as a singleton and keep it alive forever, but I'm thinking GMS somehow let's it / something internally get recycled, hence the DOE. The thought is transitioning to a case where we still create only a single location client at any given time, but we make sure to disconnect it occasionally.Antipasto
That does not sound like a bullet proof solution to me - it might reduce the issue but won't fix it. Defining one location client and figuring out what is the issue with the exception is where I would start. Without seeing the actual code it would be very hard for me to pinpoint the problem. Should work.Babu
as an aside, we talked with some Google reps and it was probably caused by a race condition inside GooglePlayServices.Antipasto

© 2022 - 2024 — McMap. All rights reserved.