Enhancing performance on Android devices

by
Tags:
Category:

Mobile devices are experiencing rapid gains in memory size as well as processing power, but they’re certainly not in the same league as hardware we’re typically developing software for. At my recent talk for the 2010 ETE conference, the final segment of the presentation focused on some of the performance techniques I’d utilized to enhance the overall user experience. Since the talk ran a bit long, I didn’t have the opportunity to go into all the details I was hoping to so I decided to post one in particular here.

The Android platform’s primary development language is Java (there’s also the ability to utilize native code, but this post’s focus is on Java). The Java source is compiled and then converted to run on the Dalvik virtual machine. While again not a focus of this post, it’s important to recognize, at least at the time of this writing, that you don’t have the capability to specify things like heap size, garbage collector selection, or any of the ergonomics we take for granted in other environments. On the other hand, the platform does offer a rich feature set to execute background work in a concurrent manner; the talk showed the application’s usage of AsyncTask, Looper, Handler, and Message constructs. In light of being able to go so far as arbitrary Thread creation, how does one balance between saturation and diminished throughput?

Seasoned developers will be glad to find that the java.util.concurrent packages are at your disposal for Android development. In particular, the ETE app makes use of the ExecutorService. I coded its usage as a Singleton:

package com.chariotsolutions.ete.util;
import java.lang.ref.SoftReference;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorServiceSingleton {
    private SoftReference
        executorServiceReference =
           new SoftReference(
                 createExecutorService());
    private static class SingletonHolder {
       private static final ExecutorServiceSingleton INSTANCE =
          new ExecutorServiceSingleton();
    }
    private ExecutorServiceSingleton(){}
    public static ExecutorServiceSingleton instance() {
       return SingletonHolder.INSTANCE;
    }
    public ExecutorService getExecutorService() {
      ExecutorService executorService =
         executorServiceReference.get();
      if (executorService == null) {
         // (the reference was cleared)
         executorService = createExecutorService();
         executorServiceReference =
           new SoftReference(executorService);
      }
      return executorService;
    }
    private ExecutorService createExecutorService() {
        // future devices (or perhaps existing netbooks?)
        // will certainly be multi-core:
        return Executors.newFixedThreadPool(
               Runtime.getRuntime().availableProcessors()+2);
    }
}

A couple points from the code:

  • The ExecutorService is wrapped in a SoftReference. In light of the inherent operating constraints, should the resources get tight, the ExecutorService reference will be reclaimed (after all, its usage is only for new data parsing and for searching).
  • Using the SingletonHolder approach, we get the added benefit of lazy initialization, another nod toward efforts of being a good citizen on the device.
  • The createExecutorService method logic asks the runtime for the number of available processors; future devices will most certainly feature multiple cores.

In conclusion, the Android platform offers some really nice approaches for background and/or concurrent processing. Properly utilizing such techniques will certainly enhance the user experience, and after all, it’s all about the user.

Special thanks to Ahsen Jaffer for contributing the “Map” feature of the application!