安卓系统 - 同一个应用的多个自定义版本

47

怎样部署多个定制版的Android应用程序是最佳方式?

目前,我有一个脚本来交换资源文件夹以获取我的应用程序的定制版本。它运行得很好,但所有定制版本在AndroidManifest.xml中仍然具有相同的包名。因此,无法同时安装两个定制版本的应用程序。

这是解决此问题的一种方法,但必须手动完成

你能想到更简单的解决方案吗?或者如何将其构建到脚本中?

(顺便说一句:这不是用于色情/垃圾邮件/任何东西的应用程序,甚至不是付费应用程序)


我在这里回答了类似的问题,也许对你有帮助: https://dev59.com/3GQn5IYBdhLWcg3wa2qV#21332803 - Marek Vavrečan
2
被接受的答案有点过时了。craned发布的答案可能是现在正确的做法。 - Darthg8r
在Android Studio中,你肯定想使用内置的Gradle flavors,这是被鼓励的。请参考链接:http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Product-flavors - craned
8个回答

24

也许在最初的帖子发布时,Android内置的“库”概念并不完善,但从2011年开始它可能是首选的方法。按照以下步骤进行ant构建:

从一个可工作的应用(我们称其为目录“myOrigApp”,包名为com.foo.myapp)开始,只需将以下行添加到“default.properties”中即可将其转换为库:

android.library=true

现在以任何你喜欢的方式在一个兄弟目录中创建一个新应用程序(假设我们称之为目录“sibling”,包名为com.foo.myVariant)。 例如,使用Intellij Idea从头开始创建一个名为“sibling”的项目,它将创建你通常需要的所有文件/目录。

在那个新的兄弟目录中编辑“default.properties”文件来添加依赖:

android.library.reference.1=../myOrigApp

复制原始目录中的清单文件:

cd sibling
cp ../myOrigApp/AndroidManifest.xml  ../myOrigApp/local.properties ../myOrigApp/build.properties  .

将复制的清单文件编辑,将其包名更改为你的新变体 "com.foo.myVarient",这是唯一的更改。

如果只运行构建脚本,则可能已经完成(我只需设置签名密钥)。

如果您想要设置像Idea这样的IDE,使库项目成为变体项目的依赖项,请按照以下步骤添加库项目到变体项目中(假设您已经为两个项目设置了项目):

  • 打开原始项目,打开项目设置,选择您的 Facet 并勾选“是库项目”,然后保存。
  • 打开变体项目,打开项目设置,选择模块
  • 添加一个模块
  • 选择“导入现有模块”
  • 浏览到原始目录(myOrigApp)并选择其 .iml 文件(IntelliJ 项目源文件)
  • 点击“完成”。 (库项目将作为模块添加到变体项目中。)
  • 在模块列表中单击 Variant 项目以选择它。
  • 在右侧选择“依赖项”选项卡。
  • 单击“添加…”
  • 选择“模块依赖项…”(应该会出现一个列表,其中包括您先前添加到项目中的模块/库的名称-也许是列表中唯一的条目)。
  • 选择您添加的库项目并按“确定”。 (它将添加到您项目的依赖项列表中。)
  • 按“确定”完成配置项目。 (您应该会看到 2 个模块,在 Variant 项目中可用和被识别的库资源和类。)

感谢 http://soledadpenades.com/2011/06/17/android-library-projects-with-intellij-idea/ 提供的 Idea 步骤。 - larham1
我相信在过去这种解决方案并不存在,但它绝对比使用正则表达式查看源文件更好。我以前一直使用Prashast的解决方案,效果不错。但对于新项目,我更喜欢使用库项目。 - Ulrich Scheller
我喜欢这个解决方案,比起修改数十个源文件(源代码控制的噩梦!),它好太多了! - Barry Fruitman
2
我认为这种方法存在一个主要的限制,就是如果您在主应用程序中使用了资产或其他类型的资源,当将其转换为jar库时,它们将无法提供给尝试使用库的分支版本。 如果您只想共享代码和/或布局,那没问题。 但是,如果您的应用程序使用了资产,则我认为这会破坏(没有一些自定义aapt构建脚本)。 - Nate
你是正确的,@Nate。从文档中可以看到: 工具不支持在库项目中使用原始资产文件(保存在assets/目录中)。任何应用程序使用的资产资源必须存储在应用程序项目本身的assets/目录中。请参阅http://developer.android.com/guide/developing/projects/index.html。 - olafure
这个不起作用。我得到了UNEXPECTED TOP-LEVEL EXCEPTION: java.lang.IllegalArgumentException: already added: Lcom/facebook/android/R$layout; 当构建兄弟项目时。我猜是因为它试图创建R类,而库项目也有R。如果我清理库(这将删除R),那么我在项目中会得到很多语法错误,无法再将其用作库。 - User

6
我之前处理类似问题的方法是使用 antlib 任务,然后遍历所有 Java 和 XML 文件,将旧的包名字符串替换为新的包名字符串。即使文件不在与包名对应的正确 src 路径下也没关系。只需对所有文件进行正则表达式替换就足以解决问题...
例如,要在 src 目录下的所有 Java 文件中替换它:
 <replaceregexp flags="g" byline="false">
    <regexp pattern="old.package.string" /> 
    <substitution expression="new.package.string" />
    <fileset dir="src" includes="**/*.java" /> 
 </replaceregexp>

1
这解决了我的问题,谢谢。 我现在使用两个replaceregexp。一个与你的完全相同,另一个用于替换AndroidManifest.xml中的包名。 - Ulrich Scheller
对我来说不起作用,因为所有的源文件仍然在与旧软件包名称匹配的目录结构中。你是怎么解决的? - mxk
对我来说,源文件的位置并不重要。只要它们具有正确的包信息即可。但在Eclipse中打开可能行不通。我只使用ant构建并使用生成的apk文件。 - Prashast
@Prashast 我尝试了,(1)创建build.xml(2)放置<replaceregexp>但没有成功。你能分享一个完整的构建Android应用程序的build.xml示例吗?谢谢... - eros

6
你绝对想使用在Android Studio上本地提供的Gradle flavors,甚至是受到鼓励的。
这个网站看起来很好地解释了所有基础知识。我今天刚刚完成了转换成Gradle,效果非常好。自定义应用程序图标、名称和字符串等。
正如该网站所解释的那样,这个设计的目的之一是使其更具动态性,更容易允许创建多个 APK,这些 APK 基本上具有相同的代码,听起来与你正在做的事情类似。
我可能没有最好地解释它,但是那个网站做得很好。

4
链接的解决方案不需要手动完成。请记住,在元素中的package属性不必是代码所在的位置,只要您在清单文件的其他地方拼写出完全限定的类名即可(例如,activity android:name="com.commonsware.android.MyActivity"而不是activity android:name=".MyActivity")。脚本化您的清单更改并使用Ant构建新的APK。据我所知,这应该可以工作。

7
这会破坏自动生成的 R 文件。因此,您需要逐个检查所有源文件并更改 R 文件的导入(第 8 点)。 - Ulrich Scheller
我们解决了R的问题。 导入旧包字符串.; 导入新包字符串.; 这将为两个构建都导入R。 - Fedor

2

支持多个合作伙伴

准备config.xml文件

为不同的合作伙伴构建项目

<!--partner.dir, pkg.name, ver.code, ver.name are input from command line when execute 'ant' -->

<!-- set global properties for this build -->
<property name="build.bin" location="bin"/>
<property name="build.gen" location="gen"/>
<property name="src" location="src"/>
<property name="res" location="res"/>

<target name="preparefiles" description="Prepare files for different partner" >
    <delete dir="${build.bin}" />
    <delete dir="${build.gen}" />

    <copy todir="${res}" overwrite="true" />
        <fileset dir="${partner.dir}/res" /> 
    </copy>

    <!-- change the import in all Java source files -->
    <replaceregexp file="AndroidManifest.xml"
        match='android.versionCode="(.*)"'
        replace='android.versionCode="${ver.code}"'
        byline="false">

    <replaceregexp file="AndroidManifest.xml"
        match='android.versionName="(.*)"'
        replace='android.versionName="${ver.name}"'
        byline="false">

    <replaceregexp file="AndroidManifest.xml"
        match='package="(.*)"'
        replace='package="${pkg.name}"'
        byline="false">

    <!-- change the package name in AndroidManifest -->
    <replaceregexp flags="g" byline="false">
        <regexp pattern="import(.*)com.myproject.com.R;" /> 
        <substitution expression="import com.${pkg.name}.R;" />
        <fileset dir="${src}" includes="**/*.java" /> 
    </replaceregexp>

    <replaceregexp flags="g" byline="false">
        <regexp pattern="(package com.myproject.com;)" /> 
        <substitution expression="\1&#10;import com.${pkg.name}.R;" />
        <fileset dir="${src}" includes="**/*.java" /> 
    </replaceregexp>
</target>

准备文件 $ ant -f config.xml -Dpartner.dir="xxx" -Dpkg.name="xxx" -Dver.code="xxx" -Dver.name="xxx" preparefiles

创建build.xml 构建 $ ant debug 或者 $ ant release


如果你只使用ant工具,那么这很好。 如果你在eclipse中工作,在运行正则表达式后可以继续工作,但是如果你更改了资源,你又会卡住。 - codeScriber
这个文件有一些小问题,但总的来说是一个好主意!我将其复制并进行了一些修改以在客户项目中使用。谢谢! - BrianPlummer

1

我正在使用maven-android-plugin来实现这个目标。为生成源代码的目标指定一个AndroidManifest.xml文件,为最终apk目标指定另一个AndroidManifest.xml文件。这样,在生成R类和构建阶段期间,源代码项目将保留实际的源代码包名称,而适用于市场的清单包名称位于第二个AndroidManifest.xml文件中,该文件包含在最终的apk文件中。


0

我最终得到了一个修补源代码的脚本;虽然修补源代码听起来很冒险,但在版本控制的情况下,这种风险是可以接受的。

所以我先制作了一个版本,提交了源代码,再制作了另一个版本,提交了源代码,并查看差异,用Python编写了一个修补脚本。

我不确定这是否是最佳解决方案。(而且代码缺少一些os.path.joins)

脚本的核心是以下函数:

# In the file 'fname',
# find the text matching "before oldtext after" (all occurrences) and
# replace 'oldtext' with 'newtext' (all occurrences).
# If 'mandatory' is true, raise an exception if no replacements were made.
def fileReplace(fname,before,newtext,after,mandatory=True):
    with open(fname, 'r+') as f:
    read_data = f.read()
    pattern = r"("+re.escape(before)+r")\w+("+re.escape(after)+r")"
    replacement = r"\1"+newtext+r"\2"
    new_data,replacements_made = re.subn(pattern,replacement,read_data,flags=re.MULTILINE)
    if replacements_made and really:
        f.seek(0)
        f.truncate()
        f.write(new_data)
        if verbose:
            print "patching ",fname," (",replacements_made," occurrence", "s" if 1!=replacements_made else "",")"
    elif replacements_made:
        print fname,":"
        print new_data
    elif mandatory:
        raise Exception("cannot patch the file: "+fname)

你可能会发现以下内容有用:

# Change the application resource package name everywhere in the src/ tree.
# Yes, this changes the java files. We hope that if something goes wrong,
# the version control will save us.
def patchResourcePackageNameInSrc(pname):
    for root, dirs, files in os.walk('src'):
    if '.svn' in dirs:
        dirs.remove('.svn')
    for fname in files:
        fileReplace(os.path.join(root,fname),"import com.xyz.",pname,".R;",mandatory=False)

还有一个函数,可以将资源从x-assets-cfgname复制到assets(之前我发现在assets中有一个子目录对我更方便)。

def copyAssets(vname,force=False):
    assets_source = "x-assets-"+vname+"/xxx"
    assets_target = "assets/xxx"
    if not os.path.exists(assets_source):
        raise Exception("Invalid variant name: "+vname+" (the assets directory "+assets_source+" does not exist)")
    if os.path.exists(assets_target+"/.svn"):
        raise Exception("The assets directory must not be under version control! "+assets_target+"/.svn exists!")
    if os.path.exists(assets_target):
        shutil.rmtree(assets_target)
    shutil.copytree(assets_source, assets_target, ignore=shutil.ignore_patterns('.svn'))

好了,你明白了。现在你可以编写自己的脚本了。


0

我认为最好的方法是创建一个新项目并复制内容。步骤如下: - 创建一个没有类的新Android项目 - 创建包(包名称应与清单文件中对应),或者只需复制“gen”文件夹中的包名称 - 复制Java文件 - 复制可绘制文件夹 - 复制布局文件 - 复制您项目中使用的任何其他文件 - 复制清单文件的数据

对我来说,这个任务变得更简单了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接