Android's Message.obtain vs Handler#obtainMessage from SafeAsyncTask

Like many application developers, we use RoboGuice to make a lot of things nicer and easier to deal with under Android.  One of the nice bits we use is SafeAsyncTask, which gives you quite a nice way of running a background task with convenient hooks for dealing with success, failure, pre-execution and finally/post-execution.

SafeAsyncTask in turn wants to run your onSuccess, onException, and onFinally via a Handler you provide.  This is done for you under the hood, but the gist of it is that it passes a Callable to a Handler which contains a decrement to a CountDownLatch in its finally block; meanwhile, the calling method waits on the latch (so it blocks until the background task is completed on its handler).

In our Activity, we have a polling task that we want to run every 2 seconds, and we achieve this by starting a HandlerThread, connecting to that a Handler, and using sendMessageDelayed with a simple Message with a constant int indicating what we want done.  Then in our handleMessage method, we fire off a subclass of SafeAsyncTask to do the poll, passing in the polling Handler and the Message we got. Inside the SafeAsyncTask's onFinally(), we recycled our Message using Message.obtain on the old message, and then we invoke handler.sendMessageDelayed using the new (recycled) message instance.

But this did not give us in any way the behavior we expected or wanted.  Instead of seeing this sequence of events:

onFinally() [two second pause]
onFinally() [two second pause]

we in fact saw THIS sequence of events:

onFinally() [two second pause]
onFinally() [from the handler thread this time, two second pause]

It appears that though Message.obtain should have left us with a clean Message to use that should have been indistinguishable (as far as we are concerned) from the original Message, in fact it gave us a Message with a callback to run the current onFinally() method, recycled from the same Message that was used to run onFinally in a Handler by SafeAsyncTask when the callback really should have been nulled.

I'm somewhat inclined to call this a defect in Android, but I would need to research the semantics of how Message.obtain(message) is expected to work, look at the Android source in a little more detail, and see precisely what went wrong inside the obtained message to fire onFinally() instead of handleMessage() via normal message delivery before I would be willing to make that claim strongly.

Our workaround was to instead call handler.obtainMessage(message.what), which does give us a nice, clean message that doesn't attempt to call back to onFinally() again, and it results in ordinary message delivery as we expected.

About this Entry

This page contains a single entry by spatula published on February 11, 2015 6:02 PM.

The Perils of Legacy Android Development was the previous entry in this blog.

The Quest for Solar - part 1 is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.



Powered by Movable Type 5.02