Android: Build multiple versions of a project

This is a question that comes up once in a while.

For example, say you’d like to offer a full and a demo version, which isn’t really uncommon. Now, the Android Market enforces that each application package needs to use a unique package name, so you can’t just recompile your .apk with a DEMO=true variable set. That probably sounds easier than it is.

The package name is specified inside your AndroidManifest.xml file, but if you change it, you’ll probably notice two things:

  1. The activities etc. you declared in your manifest are flagged as not found, because you specified them in relative form (.MainActivity); those references are now based on the new package name, but the namespace of your Java classes hasn’t changed. That’s easy enough to fix, just reference all components with their full name.
  2. The R.java class that Android generates for you will be generated using the application namespace. Since that has no changed, so has the location of the R class. The imports all across your app still reference the old name though. This is the main stumble block.

What I used to do is have two git branches, developing the normal (full) version on the master branch, and having a second demo-packaging branch with the adjustments necessary to build the demo version, including the updated imports. That branch would be rebased against master whenever I release was in order.

However, I now got tired dealing with the merge conflicts that would inevitably occur whenever I had to change the import sections during developing the master branch, which was basically every time.

So I decided to try my luck with using Android’s ant-based build system, and if necessary, automate everything with search&replace. Fortunately, it turns out that this isn’t really necessary, for the most part: aapt, which is used to generate the R.java, takes a –custom-package option. Using that, you can compile your demo version in org.example.myapp.demo, and still have the R class generated into org.example.myapp.R, where all your imports point to.

Unfortunately, the option is pretty new – it only seems available in API Level 7, i.e. the 2.1 SDK, so you’ll need to build against that (of course, you’re app can still work on older versions).

Here’s a short step to step description of what I did:

  1. Start with the default Android build.xml file. If you don’t have one yet because you created your project through Eclipse, you can just run android create project with the appropriate options in a temporary directory, and copy the build.xml and build.properties files.
  2. As per the comments in this file, add import=false to the <setup> tag in this file, and copy the contents of {SDK_DIR}/platforms/android-2.1/templates/android_rules.xml below it. Unfortunately, there doesn’t seem to be a good way to customize this more granular fashion. Actually, copying only individual targets that you’d like to modify does work, but now you’re depending on implementation details of the rules file, which probably isn’t the better option.
  3. Look for the -resource-src target and add two new arguments to the exec call:
                <arg value="--custom-package" />
                <arg value="${application.namespace}" />

    Define application.namesace in your build.properties file. Set it to the namespace that is shared across all different versions, and that you use in your code when referencing your R class.

  4. You should now be able to build different versions of your app by simply changing the namespace declared in the manifest. Still, that can be automated. Personally, I have removed AndroidManifest.xml itself from version control, and am generating a copy with the appropriate namespace based on a template, with ant asking me about what version I want to build:
    <target name="-query-build-type">
        <input
            message="What version should I build?"
            validargs="default,donate"
            addproperty="opt.version" />
        <condition property="opt.package" value="${application.namespace}.d" else="${application.namespace}">
                <equals arg1="${opt.version}" arg2="donate" />
        </condition>
    </target>
    
    <target name="copy-templates">
        <filter token="__PACKAGE_NAME__" value="${opt.package}" />
        <delete file="AndroidManifest.xml" quiet="true" />
            <copy file="AndroidManifest.xml.template" tofile="AndroidManifest.xml" filtering="true" />
    </target>
    

    I’ve added -query-build-type as the first dependency to the compile target, and copy-templates as a dependency before the -resource-src target.

  5. In closing, I do need to complain about who utterly inappropriate XML is as a language for build files that humans are going to write by hand. I don’t get why people keep using that approach.

Update 2011-02-28: aapt now seems to have (not sure in which version this was introduced) a –rename-manifest-package option which can be used to greatly simplify that second step: No more search & replace.

Update 2011-03-01: Have a look at my new approach to do this, by not using ant at all for the build.

7 thoughts on “Android: Build multiple versions of a project

  1. Current version of main_rules.xml has target, which is defined as , and it does not seem to be documented anywhere whether it implements the option in question. Of course it does not come with sources either, though I suppose they could be dug up somewhere on the internet.

    Like

  2. PS: Without preview nor formatting-help I couldn’t tell what it will do with raw < and > in previous comment.

    Like

  3. when I type ‘ant release’ in the terminal, an error message states: [exec] ERROR: Unknown command ‘–custom-package’

    samething with -custom-package (only 1 hyphen).

    Any help please..

    Like

  4. If you have a newer android SDK
    {SDK_DIR}/platforms/android-2.1/templates/android_rules.xml

    WILL NOT WORK!

    You will want to use:

    ANDROID_HOME/tools/ant

    Unfortunately, -resource-src target is also different from the blog post so it’s not immediately clear how to specify a –custom-package yet.. Looking into this currently.

    Like

Leave a comment