Android builds: dev & prod

01 Feb 2011

The release checklist

I've been working with Android a lot lately, and something I hadn't quite figured out was configuration management for different environments. Well until yesterday, so I thought I'd blog about it!

The official checklist for publishing an app to the market unfortunately shows a very waterfall approach. While it gives crucial info about what you need to do, it also includes scary headings such as Before you do the final compile! For the sake of everyone including our users, I hope we're all doing incremental releases.

So let's have a look at the minimal list of what differs between a dev build and a prod release. Of course you might have a few more changes, such as debug-only menu entries to hide in the prod version, or another "key" for a third-party library.

Android manifest Java code XML layout
  • Turn off debugging
  • Choose a version code
  • Choose a version name
  • Remove all calls to Log
  • Use a release Google maps API key

Both Eclipse and IntelliJ now have good Android plugins, and technically you can publish your app with a click of a button. However you still have to make all these changes before you publish, and probably revert them afterwards. And.. how do keep track of the different values? That all sounds pretty risky.... So I started my quest for an automated debug & release build that would satisfy this criteria:

A quick online search revealed a few different approaches:

Runtime config

I first saw a few posts describing how you can access the application's certificate to determine if your app was compiled as a debug or release version. While this definitely works in some cases, for example to switch features on and off in Java, it also has some limitations. For instance, @MapViews@ have to be created programatically to be able to switch the API key. More importantly, this does not cover changes to the @Manifest.xml@ which is packaged in the APK and read-only from then on.

The release build

Some other people seem to use the dev configuration by default, hardcoded in the source, and have a special "release" build used for publishing. This build usually copies the source somewhere else, and uses regular expressions to change the config values to the appropriate settings. This time this can cover the whole range of changes, but has a few downsides. First the regular expressions can be brittle and require frequent updating, but mostly I don't really like having a separate process that only gets tested when publishing.

Config templating

Ah, good old config templating! As with most projects, this one seemed to be the most reasonable approach. However how to apply it to an Android project? In the past I had used environment-specific property files in Java, or templated config files in .Net.... well it turns out an approach that worked well for me is to combine both!

From the table at the start of this post, it seems we have 3 types of values to generate: Java constants, to be used in code Resource strings, to be used in XML layouts * Manifest values

Therefore I proceeded to create 3 template files, that look a little like this:

templates/BuildValues.java

public class BuildValues {
    public static final boolean IS_DEBUG = @app.debug@;
    public static final String ANALYTICS_KEY = "@analytics.key@";
}

templates/build_values.xml

<resources>
    <string name="maps_api_key">@maps.api.key@</string>
</resources>

templates/AndroidManifest.xml

android:versionCode="@app.version.code@"
android:versionCode="@app.version.name@"

I originally thought the real Manifest could reference resource strings as well, for example using @int/version_code, but unfortunately some values have to be hardcoded to be accepted by the Android Market. In the end, templating the AndroidManifest turned out to be a good choice to handle free/paid versions, as I will explain in an upcoming post!

In these 3 files, you'll notice that all the environment-specific values were replaced by tokens. The real values are stored in property files - one for each type of build:

debug.properties / release.properties

app.version.code = 12
app.version.name = 1.5
maps.api.key = ABQIA-gwDtZb71
analytics.key = U-999999-9

One custom build, please!

The only thing missing now was to process these templates and copy them to the right place! Lucky that Android comes with a default Ant build file... if you don't have one yet it's very easy to create one. I just added the following target to generate the templated version. This will generate the resulting files in the source tree where you need them, so it's a good idea to have the version control system ignore them.

<target name="process-template">
    <filterset id="build-tokens">
        <filter token="app.version.name" value="${app.version.name}"/>
        <filter token="app.version.code" value="${app.version.code}"/>
        <filter token="maps.api.key" value="${google.maps.apikey}"/>
    </filterset>
    <copy file="./templates/BuildValues.java" todir="./src/your/app" overwrite="true">
        <filterset refid="build-tokens" />
    </copy>
    <copy file="./templates/build_values.xml" todir="./res/values" overwrite="true">
        <filterset refid="build-tokens" />
    </copy>
    <copy file="./templates/AndroidManifest.xml" todir="." overwrite="true">
        <filterset refid="build-tokens" />
    </copy>
</target>

The last remaining trick was to load the right properties depending on the type of build we want to create.

<target name="full-debug" depends="read-debug-properties, process-template, debug" />
<target name="full-release" depends="read-release-properties, process-template, clean, release" />

<target name="read-debug-properties">
    <property file="$./debug.properties" />
</target>

<target name="read-release-properties">
    <property file="./release.properties" />
</target>

That's it! You can now run full-debug and full-release straight from the Ant plugin of your IDE, and it will create a version of your app customised for the right type of build. The only thing left was to hook up IntelliJ to run the debug target when clicking "Run", and here it is:

IntelliJ launch deploy IntelliJ run ant

All in all, this approach really only required a few lines of code, but it definitely made my life easier! I hope it can help anyone trying to automated the Android release process. I'll write a follow up very soon on how to use the same process to handle the "free" and "paid" versions of the same app!

Comments