在 Eclipse 中构建多个(测试/生产)版本的 Android APK

Posted

技术标签:

【中文标题】在 Eclipse 中构建多个(测试/生产)版本的 Android APK【英文标题】:Build multiple (test/prod) versions of Android APKs in Eclipse 【发布时间】:2011-04-28 08:43:37 【问题描述】:

我正在寻找优化生成同一 android 应用的略有不同的 APK,唯一的区别是它使用的 http API 服务器 (dev/staging/prod)。

理想情况下,我只希望我的 Eclipse 构建 2 个 APK,一个用于生产服务器,一个用于开发服务器。

我什至可以有 2 个运行配置,但我无法弄清楚如何将参数传递给应用程序并从代码中读取它们。

我想以 1.5 为目标,顺便说一句,我想使用 Eclipse 自动构建工具,所以我正在寻找最通用的解决方案。

谢谢。

【问题讨论】:

[Android – 同一应用的多个自定义版本]的可能副本(***.com/questions/1222302/…) 据我所知,没有一个解决方案能很好地与 Eclipse 集成。我实际上很困惑为什么这么简单的任务看起来如此复杂。不是要引发一场激烈的战争,但我已经查看了我们的 iPhone 开发人员的 iPhone dev env,从外部将参数传递给您的应用程序非常简单。这让我很难过。 我知道如何使用 maven-android-plugin 做到这一点,实际上很容易。但是,它需要将您的应用程序与 maven 绑定,此外,maven-android-plugin/m2e-android 中的最新 Android SDK (r14 & r15) 似乎存在一些不兼容问题,可能不是您想要的。 您是否考虑过在初始编码阶段仅使用 Eclipse,然后在初始发布和测试阶段切换到 Ant 构建?设置批处理/脚本文件以将参数传递给 Ant 将指定源和输出目录非常容易,然后您只需稍微修改 build.xml 的目标以考虑这些目录。 如果您控制服务器,您的另一个选择是让您的应用程序首先访问一个众所周知的 URL,以下载包含要应用的设置的策略,“targetUrl”就是其中之一。您也可以通过这种方式对客户端进行版本控制。 【参考方案1】:

我认为使用 ant 构建脚本将是最简单的解决方案。 Eclipse 支持 ant 构建,所以你可以在 eclipse 中运行 ant 命令。

你可以像这样用蚂蚁解决你的问题。

    准备两个xml android资源文件。 使用资源 #1 构建包 用资源 #2 的内容覆盖资源 #1 构建另一个包

xml 会是这样的:

资源 #1:

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

资源 #2:

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

ant 脚本会是这样的:

<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>

【讨论】:

现在看来我们正在取得进展。您能否提供有关如何在 Eclipse 中进行设置的进一步说明? 看到这个:Ant support - 1. 创建新的 ant 构建文件 / 2. 在 eclipse 中打开 ant 视图 / 3. 将你的 ant 构建添加到视图中(拖放) / 4. 运行它。 小心:android 需要 ant 1.8 或更高版本。 eclipse内置了ant,但它的版本是1.7。您应该手动安装 ant,然后在 Eclipse 属性中将您的 ant 二进制文件设置为默认的 ant 二进制文件。 几个月前我在设置 ant 时发现这很困难 - 确实它在吐槽什么东西。请问 - 您过去是否成功设置此方法并使其正常工作? 是的。我正在构建两个库项目,5 个与 ant 脚本略有不同的应用程序。没有蚂蚁,我无法完成这项工作。我只需单击鼠标即可在 Eclipse 上运行 ant 脚本。尽管我的要求与您的不同,但我认为您可以使用 ant 脚本完成这项工作。【参考方案2】:

将所有代码移至库项目,请参阅 http://developer.android.com/guide/developing/projects/projects-eclipse.html#SettingUpLibraryProject

然后在 eclipse 中为测试和生产创建单独的项目,每个项目都有一个唯一的包名称。然后,您可以使用包名称来区分版本。

类似:

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

这对于管理不同的 http 端点似乎有点过分,但它会使代码更易于管理。您还可以做一些有用的事情,例如:

用不同的应用程序图标标记测试版本 在一台设备上同时运行测试和生产版本

这一切都可以在 eclipse 中完成,无需使用第三方工具。

【讨论】:

这对我来说听起来像是版本控制的噩梦,每个文件的顶部都有不同的包名称。我错了吗? 版本控制没有问题。包名在清单中设置一次,所有代码都保留在库项目中。 和我的一样,但不需要改变 只是为了改进该解决方案 - 我不会使用包名称,而是创建一个包含所有代码的库项目,并拥有一个带有 URL 的字符串资源。这样,您可以为不同的环境创建多个项目,在单个库项目中维护您的代码(由所有其他项目引用)并且只更改字符串资源值,以便您可以根据需要为每个环境构建项目 为每个目标应用程序创建一个项目是一个非常不完美的解决方案,因为这意味着必须在所有目标中手动复制对清单等的更改。您确实需要某种脚本来同步所有目标的清单,仅更改包名称或类似的东西。【参考方案3】:

这不是你真正想要的:

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;
    

如果应用使用调试密钥进行签名,并且使用发布证书进行生产,则您可以访问开发/登台服务器。

【讨论】:

这仍然构建 1 个文件 - 我希望 Eclipse 构建 2 个(或 N 个)文件并创建多个配置,以便轻松构建 staging、dev 等。【参考方案4】:

对于传递参数,您总是可以在 android 的目录系统中创建一个文件并让您的代码从中读取它。

【讨论】:

但是如何将文件名参数传递给代码呢?如果我可以传递文件名,我也可以传递服务器 url - 问题是如何传递。【参考方案5】:

就我而言,我只是想在不同版本之间更改strings.xml 中的一些值。

首先我必须加载ant-contrib 库,以定义for 循环任务:

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

我将我的配置列表config.names 放在properties 文件中:

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

并定义一个build-all 目标,该目标在config.names 上循环:

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

为每一个定义一个自定义的resources目录,将目录名称保存在config.resources属性中:

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

删除它,将res的全局资源复制到其中:

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

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

将配置名称中的 - 更改为 /,使其成为 URL 参数中的路径:

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

运行 XSLT 转换以修改 strings.xml 文件:

<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>

这是我使用的 XSLT 样式表:

    <?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>

然后回到custom_rules.xml,然后我从原始(未修改)res/values/strings.xml 中提取app_version

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

并使用antcall 任务调用debug 构建:

<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>

具有两个更改的属性值:

resource.absolute.dir 告诉debug 目标使用我修改过的res 目录,在上面的config.resources 属性中定义; out.final.file 告诉它生成一个具有不同名称的 APK,包括配置名称(例如 student-testing)和从 strings.xml 中提取的版本号。

最后,我可以从命令行运行ant build-all 并构建所有四个目标。多一点脚本,就在build-all目标结束之前,将编译好的APK文件一起列出以供参考:

<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>

【讨论】:

以上是关于在 Eclipse 中构建多个(测试/生产)版本的 Android APK的主要内容,如果未能解决你的问题,请参考以下文章

在eclipse上配置多个jdk

如何使用Xcode的Targets来管理开发和生产版本的构建

在自定义构建中自动化 WSDL.exe

创建生产版本后,在 Play 商店中找不到应用

maven是啥意思

使用 Subversion 构建 PHP 项目的生产版本