23

I'm looking to optimize generating of slightly different APKs of the same Android app, the only difference being the http API server it's using (dev/staging/prod).

Ideally, I'd just want my Eclipse to build 2 APKs, one with the prod server and one with the dev one.

I'm even OK with having 2 Run configurations, but I haven't been able to figure out how to pass parameters to the app and read them from the code.

I want to target 1.5, BTW, and I'd like to use Eclipse auto-build tools, so I'm looking for the most generic solution.

Thank you.

Artem Russakovskii
  • 21,516
  • 18
  • 92
  • 115
  • possible duplicate of [ Android – multiple custom versions of the same app](http://stackoverflow.com/questions/1222302/android--multiple-custom-versions-of-the-same-app) – EboMike Oct 21 '10 at 00:57
  • None of the solutions integrate nicely with Eclipse as far as I can tell. I'm actually quite stumped as to why such a simple task seems so complicated. Not to start a flamewar, but I've taken a look at the iPhone dev env of our iPhone developer, and it's pretty trivial to pass params to your application from the outside. This makes me sad. – Artem Russakovskii Oct 21 '10 at 01:07
  • I know how to do this with maven-android-plugin, actually pretty easy. however, it requires tie your application with maven, besides, there seems to be some incompatibilities with latest Android SDK (r14 & r15) in maven-android-plugin/m2e-android, probably not what your want. – yorkw Nov 02 '11 at 09:01
  • Have you considered just using Eclipse for the initial coding phase, then switching to Ant builds for the initial release and testing phase? It's pretty easy to set up a batch/script file to pass in parameters to Ant which will specify source and output directories, then you would just have to modify the build.xml's targets slightly to take account of these directories. – NickT Nov 04 '11 at 12:13
  • If you control the server, your other option is to make your app hit a well-known URL first to download a policy that contains settings to apply, "targetUrl" being one of them. You can also do versioning of clients this way. – scorpiodawg Nov 07 '11 at 20:07

5 Answers5

9

I think using ant build script would be the easiest solution. Eclipse supports ant build, so you can run ant command in eclipse.

You can solve your problem with ant like this.

  1. prepare two xml android resource file.
  2. build a package with resource #1
  3. overwrite resource #1 with content of resource #2
  4. build another package

xml would be like this:

resource #1:

<resources>
    <string name="target">dev</string>
</resources>

resource #2:

<resources>
    <string name="target">staging</string>
</resources>

and ant script would be like this:

<project>
  <target name="build_all">
     <copy file="res1.xml" to="res/values/target.xml"/>
     <ant antfile="build.xml" target="debug"/>
     <copy file="res2.xml" to="res/values/target.xml"/>
     <ant antfile="build.xml" target="debug"/>
  </target>
</project>
qris
  • 7,900
  • 3
  • 44
  • 47
kingori
  • 2,406
  • 27
  • 30
  • Now that looks like we're getting somewhere. Can you please give further instructions on how to set this up in Eclipse? – Artem Russakovskii Nov 04 '11 at 16:45
  • see this : [Ant support](http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.platform.doc.user%2Fconcepts%2Fconcepts-antsupport.htm) - 1. create new ant build file / 2. open ant view in eclipse / 3. add your ant build to the view (drag&drop ) / 4. run it. – kingori Nov 05 '11 at 03:24
  • be careful : android needs ant 1.8 or later. eclipse has built in ant, but it's version is 1.7. you should install ant manually, and then set your ant binary as default ant binary in eclipse properties. – kingori Nov 05 '11 at 03:26
  • Found that the hard way when I was setting up ant a few months back - indeed it was barfing on something. May I ask - have you successfully set this method up in the past and had it working? – Artem Russakovskii Nov 05 '11 at 17:07
  • yes. I am building two library project, 5 slightly different app with ant script. without ant, i can not do that job. and I run ant script on eclipse with just one mouse click. Although my requirement is different from yours, I think you can do that work with ant script. – kingori Nov 07 '11 at 10:09
4

Move all you code to a library project see http://developer.android.com/guide/developing/projects/projects-eclipse.html#SettingUpLibraryProject

Then create separate projects in eclipse for test and production each with a unique package name. You can then use the package name to distinguish between versions.

Something like:

public static boolean isProductionVersion(){
  return context.getPackageName().toLowerCase().contains("production");
}

This may seem like overkill for managing different http end points but it will make the code more manageable. You can also do useful things like:

  • flag the test version with a different application icon
  • run test and production versions side by side on one device

This can all be done in eclipse without using and third party tools.

railwayparade
  • 5,154
  • 1
  • 39
  • 49
  • That sounds like a nightmare for version control to me, with a different package name at the top of each file. Am I wrong? – Artem Russakovskii Nov 03 '11 at 05:56
  • There is no issue with version control. The package name is set once in the manifest and all the code remains in the library project. – railwayparade Nov 03 '11 at 07:42
  • And mine, but it doesn't need to change – railwayparade Nov 03 '11 at 21:36
  • Just to improve that solution - instead of using the package name, I'd create a library project with all my code in it and have a string resource with the URL. That way, you can create multiple projects for different environments, maintain your code in a single library project (which is referenced by all other projects) and only change the string resource value so you would build the projects for each environment as required – Muzikant Apr 28 '12 at 10:52
  • Creating a project for each target app is very much an imperfect solution, since it means that changes to the manifest etc have to be manually duplicated across all targets. You really need some sort of script to sync the manifest across all targets with only the package name changes, or something like that. – Adam Jun 27 '13 at 07:25
1

Its not really what you want:

private static Boolean isSignedWithDebugKey = null;
    protected boolean signedWithDebug() {
        if(isSignedWithDebugKey == null) {
            PackageManager pm = getPackageManager();
            try {
                PackageInfo pi = pm.getPackageInfo(getPackageName(), 0);
                isSignedWithDebugKey = (pi.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
            }
            catch(NameNotFoundException nnfe) {
                nnfe.printStackTrace();
                isSignedWithDebugKey = false;
            }
        }

        return isSignedWithDebugKey;
    }

You could then hit a dev/staging server if the app is signed with a debug key, and production with a release certificate.

FunkTheMonk
  • 10,908
  • 1
  • 31
  • 37
  • This still builds 1 file - I want Eclipse to build 2 (or N) files and create multiple configurations for easily building staging, dev, etc. – Artem Russakovskii Nov 03 '11 at 05:54
0

In my case I just wanted to change a few values in strings.xml between different versions.

First I have to load the ant-contrib library, to define the for loop task:

<taskdef resource="net/sf/antcontrib/antcontrib.properties">
    <classpath>
        <pathelement location="lib/ant-contrib-1.0b5-SNAPSHOT.jar" />
    </classpath>
</taskdef>

I put my list of configurations, config.names, in a properties file:

config.url.root=http://projectserver.aptivate.org/
config.names=student-production, teacher-production, student-testing, teacher-testing

And define a build-all target, that loops over the config.names:

<target name="build-all">
    <for param="config.name" trim="true" list="${config.names}">
        <sequential>

Defining a custom resources directory for each one, saving the directory name in the config.resources property:

<var name="config.resources" unset="true" />
<property name="config.resources" value="bin/res-generated/@{config.name}" />

Delete it, and copy the global resources from res into it:

<delete dir="${config.resources}" />

<copy todir="${config.resources}">
    <fileset dir="res"/>
</copy>

Change - to / in the config name, to make it a path in the URL parameter:

<var name="config.path" unset="true" />
<propertyregex property="config.path"
    input="@{config.name}" regexp="-"
    replace="/" casesensitive="true" />

Run an XSLT transform to modify the strings.xml file:

<xslt in="res/values/strings.xml"
    out="${config.resources}/values/strings.xml"
    style="ant/create_xml_configs.xslt"
    force="true">
    <param name="config.url.root" expression="${config.url.root}" />
    <param name="config.name" expression="@{config.name}" />
    <param name="config.path" expression="${config.path}" />
</xslt>

This is the XSLT stylesheet that I use:

    <?xml version="1.0" encoding="ISO-8859-1"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
            <xsl:param name="config.url.root" />
            <xsl:param name="config.name" />
            <xsl:param name="config.path" />

            <!-- http://my.safaribooksonline.com/book/xml/9780596527211/creating-output/xslt-id-4.6 -->
            <xsl:template match="/">
                    <!--
                    This file is automatically generated from res/values/strings.xml
                    by ant/custom_rules.xml using ant/create_xml_configs.xslt.
                    Do not modify it by hand; your changes will be overwritten.
                    -->
                    <xsl:apply-templates select="*"/>
            </xsl:template>

            <xsl:template match="*">
                    <xsl:copy>
                            <xsl:for-each select="@*">
                                    <xsl:copy/>
                            </xsl:for-each>
                            <xsl:apply-templates/>
                    </xsl:copy>
            </xsl:template>

            <!-- the value of update_server_url must end with a slash! -->
            <xsl:template match="string[@name='update_server_url']/text()">
                    <xsl:value-of select="$config.url.root" /><xsl:value-of select="$config.path" />/
            </xsl:template>

            <xsl:template match="string[@name='app_version']/text()">
                    <xsl:value-of select="." />-<xsl:value-of select="$config.name" />
            </xsl:template>
    </xsl:stylesheet>

And back to custom_rules.xml where I then extract the app_version from the original (unmodified) res/values/strings.xml:

<xpath input="res/values/strings.xml" 
    expression="/resources/string[@name='app_version']" 
    output="resources.strings.app_version" />

And use the antcall task to call the debug build:

<antcall target="debug">
    <param name="resource.absolute.dir" value="${config.resources}" />
    <param name="out.final.file" value="${out.absolute.dir}/${ant.project.name}-${resources.strings.app_version}-@{config.name}.apk" />
</antcall>

with two changed property values:

  • resource.absolute.dir tells the debug target to use my modified res directory, defined in the config.resources property above;
  • out.final.file tells it to produce an APK with a different name, including the configuration name (e.g. student-testing) and the version number extracted from strings.xml.

And then, finally, I can run ant build-all from the command line and build all four targets. A little bit more script, just before the end of the build-all target, lists the compiled APK files together for reference:

<echo message="Output packages:" />
<for param="config.name" trim="true" list="${config.names}">
    <sequential>
        <echo message="${out.absolute.dir}/${ant.project.name}-${resources.strings.app_version}-@{config.name}.apk" />
    </sequential>
</for>
qris
  • 7,900
  • 3
  • 44
  • 47
0

For passing parameters, you could always create a file in android's directory system and have your code read it from it.

Jack BeNimble
  • 35,733
  • 41
  • 130
  • 213