Showing posts with label optimization. Show all posts
Showing posts with label optimization. Show all posts

Wednesday, April 21, 2010

Android Data Caching

One of the applications I'm building for the Android platform is meant to be used in remote regions of the developing world. While cellular coverage is getting more ubiquitous across the globe, there will inevitably be dead zones. With this in mind, all data-driven aspects of the application are being designed to degrade gracefully when there is no data connection available. One way in which this is being handled is by pre-caching data whenever possible.

Solving this problem in an application-specific manner is fairly straightforward. There are essentially 3 steps:
  • Identify those items that you can cache - Since much of the information for this application is geo-coded, deciding what to cache is usually pretty straightforward (I can, for example, pull in all the data points tagged within a given radius of the device's current position).
  • Watch for data connectivity changes and download cacheable data whenever a connection is available - registering a broadcast receiver to listen for status change messages makes this fairly trivial
  • Store data locally that needs to be sent and send it whenever you have a data connection - again, a broadcast receiver makes this simple.

Beyond this core functionality, I've also built some ancillary features to allow users to better control their bandwidth usage in the event that they're not on an unlimited plan (a user can opt to only perform cache downloads if they have a WiFi connection as opposed to a cellular connection and they can choose what types of data they want to cache).

While this works well for the application, the real problem I'd like to address is doing this in a generic, reusable fashion. I haven't done so yet, but I am planning on building a library that can handle both cache population and data upload. The library would handle the nuts and bolts of listening to status changes and initiating downloads/uploads based on user preferences while delegating the decision of what to download (and how to do it) to user-supplied helper classes. These helpers can extend a base CacheAwareHelper class that, on invocation can first check to see if the data is already in cache and, if not, then invoke the method that actually performs the fetch of the data from the remote location. The result, in addition to being returned to the calling method, can then be cached.

This is still just a thought and I'm sure the details will flex a bit once I start writing the real design and implementation. I am still thinking about how to handle cache invalidation such that things are only evicted when they absolutely need to be. Stay tuned for a follow-up post once the library is done. At that time I'll include a few diagrams to show how it works. If anyone is actually reading this blog, I'd love to hear some feature suggestions in the comments as I'm planning on releasing this library as open-source.

Sunday, February 21, 2010

Android Optimization

Since the Android platform uses the Java programming language and, at a high level, seems to support a large portion of the language, it is easy to forget that one is still developing for an embedded device. I am, by no means, an expert -- in fact, this is my first foray into the world of embedded programming but there is no harm in outlining the little bit I've learned if it has a slight chance at helping someone else. This is the first platform on which I've really had to think of "micro" level optimizations (rather than macro, algorithmic optimization). It definitely adds another dimension to the analysis one must undertake when attempting to solve a problem.

To understand the best way in which to write Android programs, one first must understand a bit about the virtual machine at the heart of the system: The Dalvik VM. I won't go into details about the internals of the VM, but suffice it to say that the fact that it is made to run on devices with limited memory and CPUs, the compiler makes some choices in bytecode generation that differ from those that would be made by Sun's compiler.

In starting development on my Android project, I came across this page on the developer resources site. While the page listed a number of things that I would not have even thought of -- the way in which enhanced for loops, for example, result in the implicit allocation of an Iterator -- I was glad that the first bullet point was "Optimize Judiciously." This advice is probably the most important on the page. They even goes as far as including one of my favorite quotes on the subject:

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
—Donald E. Knuth


The dangers of premature optimization are by no means limited to the realm of embedded programming (though they may be even more precarious since some of the optimizations done in this space may seem odd to developers accustomed to working in the desktop world) and I've seen them derail or over-complicate a number of projects in the past. Maintainability and understandability are just as important as correctness when writing software.

Pontificating aside, here is the process I am taking with respect to optimization:

1. Devise the most efficient algorithm for the logic I'm performing: this is no different than I would do on any other platform (just because you have more resources doesn't mean your software should use all of them).

2. Make the "easy" optimizations the first time around: this includes things like declaring variables using concrete classes rather than interfaces, only using the enhanced for loop when an iterator would be needed anyway, etc

3. Make the code as simple and clean as possible: again, this is no different than any other platform

4. Refactor often: this is probably the most important step. At this point, I've refactored so much of the application that I've basically rewritten it twice over. After each refactoring round, the code is cleaner, more efficient and more maintainable. I have paid special attention to the view portion of the code as small changes there can have a huge impact on performance.

There is nothing novel about the approach outlined above. My only point in calling it out is that the sequence is important. I don't start the refactoring phase until I'm sure that the algorithm is correct and the code implementing the logic is as simple as it can be. This helps keep things maintainable. I also endeavor to keep my layers and object dependencies clean. Performance "optimization" is not an license to throw away decades of solid software design principles. Keeping the components well-defined an clean may seem like you're leaving a few more CPU cycles of performance gain on the table but, in the long run, one is probably still better off with quality code that (so long as it performs well enough -- and what "enough" is varies depending on the application) versus spaghetti code that uses the fewest possible CPU cycles.