February 2015 Archives

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.

The Perils of Legacy Android Development

One of the least awesome things to do is to make last-minute changes to an ecosystem that is known to be working when installing on a customer site.  Nonetheless, sometimes you're stuck and you have to scramble.  This was the case for us yesterday when we needed an inexpensive Android tablet to use at a customer site.  So we acquired a Samsung Galaxy Tab 3 "Lite".  This is a fairly non-spectacular, dated tablet, which runs Android 4.2.2, and probably always will.  Samsung appears to have abandoned support for this device, so it is not likely to graduate from Jelly Bean to Kitkat without rooting the device and installing a custom ROM.

Still, our app isn't horribly fancy, so we anticipated that things would probably work fine.  We were mistaken.

Even installation from the Play Store failed.  We tracked this down to probably happening because we specified a signing algorithm of SHA256withRSA.  This works on later versions of Android (or maybe just on other devices) but not the tablet we picked up; the whole app downloads, and then the installation fails with an error about the package file not being signed correctly.  The fix here is to specify -sigalg SHA1withRSA instead.

The next problem we encountered (after manually installing the app) was in annotation processing from within Jackson.  We share our model objects between our cloud REST service and our Android application.  Android makes using the XML annotations that Jackson handles somewhat of a pain; these annotations are in the javax.xml.bind.annotations package, which Android itself does not supply.  Further, dexing forbids providing this package, unless you specify the --core-library switch... which itself is cautioned against by Google in the strongest possible terms.

The curious thing is that this never seemed to matter in the Android 4.4 and later world; though we did nothing to strip out annotations, our app always ran fine, though we occasionally got an exception the first time Jackson scanned for annotations.  In 4.2, we got the exception every time, which prevented deserialization from working at all.  It isn't clear why it works in  >= 4.4 and not <= 4.3, though there are some potential indicators that earlier versions of Android were a little more aggressive about classloading annotation classes.

Fortunately, Jackson > 1.8 provides the means to disable annotation scanning entirely to avoid this whole JAXB binding issue.  For Jackson 2.4, it's just a matter of calling objectMapper.disable(MapperFeature.USE_ANNOTATIONS).  Of course this means that your JAXB annotations won't do anything and Jackson won't know anything about them, but in our case that was acceptable, because we are deserializing only, and Jackson is fairly intelligent about its default deserialization behavior in the absence of annotations.

Speaking of annotations, the next problem was a curious error coming out of Joda Time, again caused by an annotation, org.joda.convert.ToString.  This appears to be an annotation that Joda uses internally on its AbstractInstant, and again, it was something that we never had any problems handling on 4.4 and above.  We got a NoClassDefFoundError for the annotation class when deserializing an object using a DateTime type, giving some credence to our weak hypothesis about Android 4.2 being somewhat more aggressive about loading annotation classes than later versions of Android (and the Sun and OpenJDK JVMs as well).  The fix in this case was to add the dependency on joda-convert, which contains the classes needed to resolve those annotations.

Once we got past these three problems, we hit the dreaded NetworkOnMainThreadException.  In response to certain events, we post messages to Roboguice's event bus, and it turns out that these events may be received on the UI thread, at least in Android 4.2.2.  (It's also possible that they can be received on the UI thread in later versions of Android as well, but performing a network task from the UI thread is not a fatal exception that kills your app in later Android releases.)  In our case, when Preferences change, we want to refresh some data from our server, which we get by performing a REST GET request, which would kill our app on 4.2.2.  The easiest workaround for us is to publish the event to the eventBus from inside a Roboguice SafeAsyncTask, thereby ensuring it is never received on the UI thread.  (A possibly better solution would be for the entity that subscribes to the event to perform its work inside a SafeAsyncTask, but this proved to be far more work than we wanted to do in the near term.)

Then, just when everything appeared to finally be working, one last problem snuck up on us.  It seems that Android 4.2.2 has some issues with HTTP(s) Keep-Alive management.  Our app makes frequent GET requests, polling the server for new data (eventually we'll switch to a more event-driven model).  It seems that Jelly Bean isn't terribly inclined to close sockets when it is done with them if keep-alives are turned on, even if those connections are never reused.  The end result of this is a lot of sockets left open that aren't doing anything and aren't needed, which eventually become so numerous that new requests cannot be created, causing an exception to be thrown about too many open files (EMFILE).  We use Jersey to handle our web resources, and working around this problem is a matter of adding webResource.header("Connection","close"), which asks the server NOT to keep the connection alive so the sockets will end up getting closed in a timely manner.

After a lot of head-scratching, visits to StackOverflow, and making many changes, we have a functional app on Android 4.2.2.  In the end, it was a useful exercise, because some abnormalities were uncovered... so there is probably something to be said for keeping around a weird old piece of hardware for use in testing; chances are you'll probably experience at least one customer to try to deploy something that is less-than-optimal.

About this Archive

This page is an archive of entries from February 2015 listed from newest to oldest.

January 2015 is the previous archive.

April 2015 is the next archive.

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



Powered by Movable Type 5.02