在Ant中启用Android的`--multi-dex`选项

12

在 Gradle 构建系统中启用 multi-dex 选项非常容易,但我还没有找到如何在 ant 构建中启用此选项的示例。我该如何实现这一点?

3个回答

8
我们有两个选项:
  1. 更改DexExecTask [引入新的参数以支持多dex],编译ant.jar,并将其用于构建。我不喜欢这个选项,因为我们必须为所有团队成员提供更新后的ant.jar。
  2. 修改项目build.xml文件。我找到了一个很棒的ant build文件,其中包含所有支持多dex的修改:https://github.com/ruboto/ruboto-irb/blob/master/build.xml
希望这能帮到你。

我已经将Ruboto的解决方案添加到我的应用程序的build.xml中(我有一个项目用于我的应用程序,另一个作为库添加到两个项目中),但在尝试从Eclipse启动应用程序时仍然出现“65536”错误消息。 我还确保引用了multidex支持库jar,并且我的Application类继承自MultiDexApplication。 这里有什么问题吗?谢谢。 - AsafK
我已经将rubuto的代码添加到build.xml中。 但是-dex:任务一直卡住了... 无法运行... - Bulma

4

我使用基于Ruboto的build.xml为应用程序引入了多dex支持,(向他们致敬!)。因此,这是我推荐采取的方法。在这种情况下,您不必创建自定义ant jars,只需编辑您的build.xml并应用来自https://developer.android.com/tools/building/multidex.html的所有非gradle步骤即可。

至于build.xml,您需要查看所有路径是否相互配合,您可能已经更改了其中一些路径并需要调整ant步骤。

在我的情况下,我还需要生成必须放置在第一个生成的dex(默认值)中的类列表,以便应用程序可以在Application的onCreate中加载其他dex时启动。为了做到这一点,您需要运行名为mainDexClasses的脚本,在build-tools中可以找到它:

mainDexClasses [--output <output file>] <application path>

Alex Lipov写了一篇很好的文章,与此相关。

当您生成类列表后,您必须通过在dx参数中添加来指向此列表。

<arg value="--main-dex-list="class_list_path" />

dx命令的执行位置。

值得记住的是,Ruboto脚本假设只会创建一个额外的dex文件,但并非总是如此。如果存在多个额外的dex文件,则需要实现添加操作:

            <if>
                <condition>
                    <available file="${third_dex_path}"/>
                </condition>
                <then>
                    <exec executable="${aapt}" dir="${out.absolute.dir}" failonerror="true">
                        <arg line='add -v "${out.absolute.dir}/${resource.package.file.name}" ${third_dex}'/>
                    </exec> 
                </then>
            </if>

Ant中的multidex支持已启用!它不像使用gradle那么容易,但是仍然可以实现。

更新:我的build.xml(除了与答案无关的部分):

<!-- Packages the application. Overriden in order to add  "-post-package-resources"" dependency-->
    <target name="-package" depends="-dex, -package-resources, -post-package-resources">
        <!-- only package apk if *not* a library project -->
        <do-only-if-not-library elseText="Library project: do not package apk..." >
            <if condition="${build.is.instrumented}">
                <then>
                    <package-helper>
                        <extra-jars>
                            <!-- Injected from external file -->
                            <jarfile path="${emma.dir}/emma_device.jar" />
                        </extra-jars>
                    </package-helper>
                </then>
                <else>
                    <package-helper />
                </else>
            </if>
        </do-only-if-not-library>
    </target>

    <target name="-post-package-resources">
        <property name="second_dex" value="classes2.dex" />
        <property name="third_dex" value="classes3.dex" />
        <property name="second_dex_path" value="${out.absolute.dir}/${second_dex}" />
        <if>
            <condition>
              <and>
                <available file="${second_dex_path}" />
                <or>
                  <not>
                    <uptodate srcfile="${second_dex_path}" targetfile="${out.absolute.dir}/${resource.package.file.name}" />
                  </not>
                  <uptodate srcfile="${out.absolute.dir}/${resource.package.file.name}" targetfile="${out.absolute.dir}/${resource.package.file.name}.d" />
                </or>
              </and>
            </condition>
            <then>
                <echo>Adding classes2.dex to ${resource.package.file.name}</echo>
                <exec executable="${aapt}" dir="${out.absolute.dir}" failonerror="true">
                    <arg line='add -v "${out.absolute.dir}/${resource.package.file.name}" ${second_dex}'/>
                </exec>
                <if>
                    <condition>
                        <available file="${out.absolute.dir}/classes3.dex"/>
                    </condition>
                    <then>
                        <echo>Adding classes3.dex to ${resource.package.file.name}</echo>
                        <exec executable="${aapt}" dir="${out.absolute.dir}" failonerror="true">
                            <arg line='add -v "${out.absolute.dir}/${resource.package.file.name}" ${third_dex}'/>
                        </exec> 
                    </then>
                </if>
            </then>
        </if>
    </target> 

<!-- builds dex in regular way and if it fails, switches to multidex-->
    <macrodef name="dex-helper">
        <element name="external-libs" optional="yes" />
        <attribute name="nolocals" default="false" />
        <sequential>
            <condition property="verbose.option" value="--verbose" else="">
                <istrue value="${verbose}" />
            </condition>
            <condition property="jumbo.option" value="--force-jumbo" else="">
                <istrue value="${dex.force.jumbo}" />
            </condition>

            <!-- Regular DEX process.  We would prefer to use the Android SDK
                 ANT target, but we need to detect the "use multidex" error.
                 https://android.googlesource.com/platform/sdk/+/tools_r21.1/anttasks/src/com/android/ant/DexExecTask.java
            -->
            <mapper id="pre-dex-mapper" type="glob" from="libs/*.jar" to="bin/dexedLibs/*-dexed.jar"/>

            <apply executable="${dx}" failonerror="true" parallel="false" dest="${out.dexed.absolute.dir}" relative="true">
                        <arg value="--dex" />
                        <arg value="--output" />
                        <targetfile/>
                        <arg line="${jumbo.option}" />
                        <arg line="${verbose.option}" />
                        <fileset dir="." includes="libs/*" />
                        <external-libs />
                        <mapper refid="pre-dex-mapper"/>
            </apply>

            <apply executable="${dx}" resultproperty="dex.merge.result" outputproperty="dex.merge.output" parallel="true">
                <arg value="--dex" />
                <arg value="--output=${intermediate.dex.file}" />
                <arg line="${jumbo.option}" />
                <arg line="${verbose.option}" />
                <path path="${out.dex.input.absolute.dir}"/>
                <path refid="out.dex.jar.input.ref" />
                <external-libs />
            </apply>

            <if>
                <condition>
                    <or>
                        <contains string="${dex.merge.output}" substring="Too many field references"/>
                        <contains string="${dex.merge.output}" substring="Too many method references"/>
                    </or>
                </condition>
                <then>
                    <echo message="Number of field or method references is too big.  Switching to multi-dex build." />
                    <multi-dex-helper>
                        <external-libs>
                        <external-libs/>
                        </external-libs>
                    </multi-dex-helper>
                </then>
                <else>
                    <echo message="${dex.merge.output}"/>
                    <fail status="${dex.merge.result}">
                        <condition>
                            <not>
                                <equals arg1="${dex.merge.result}" arg2="0"/>
                            </not>
                        </condition>
                    </fail>
                </else>
            </if>

      </sequential>
    </macrodef>

    <macrodef name="multi-dex-helper">
        <element name="external-libs" optional="yes" />
        <sequential>
            <property name="mainDexClasses" location="${android.build.tools.dir}/mainDexClasses" />

            <exec executable="${mainDexClasses}" failonerror="true" >
                <arg line="--output ${out.absolute.dir}/classes_to_kepp_in_main_dex"/>
                <arg file="${out.absolute.dir}/proguard/obfuscated.jar"/>
            </exec>
            <echo>Converting compiled files and external libraries into ${out.absolute.dir} (multi-dex)</echo>
            <echo>Dexing ${out.classes.absolute.dir} and ${toString:out.dex.jar.input.ref}</echo>
            <apply executable="${dx}" failonerror="true" parallel="true">
                <arg value="--dex" />
                <arg value="--multi-dex" />
                <arg value="--main-dex-list=${out.absolute.dir}/classes_to_kepp_in_main_dex" />
                <arg value="--output=${out.absolute.dir}" />

                <arg line="${jumbo.option}" />
                <arg line="${verbose.option}" />
                <arg path="${out.absolute.dir}/proguard/obfuscated.jar" />
                <path refid="out.dex.jar.input.ref" />
                <external-libs />
            </apply>
        </sequential>
    </macrodef>

你可以把完整的build.xml(或至少是完整的multi-dex部分)发布出来吗? - Nickolai Astashonok
好的,那可能会有帮助。我在我的答案中添加了build.xml文件。 - michalbrz

3
我还将multidex添加到ANT构建中,但以一种稍微不同的方式实现,这种方式在支持classes*.dex方面更加灵活。
无论如何,我分两个阶段完成了这个过程:1)使DX输出multidex,然后2)使用aapt包含multidex类。本文末尾有一个可选步骤,用于生成主要类列表,但基本实现并不需要此步骤。
以下是build.xml中的实现,后面会有注释:
... your build script ...
<!-- version-tag: custom -->
<!-- (1) Override -package target to add additional JAR to the apk -->
<property name="multidex-secondary-classes.jar" value="classes-secondary.jar" />
<target name="-package" depends="-dex, -package-resources, -create-multidex-secondary-classes-jar">
    <!-- Copied from SDK/tools/ant/build.xml -->
    <!-- only package apk if *not* a library project -->
    <do-only-if-not-library elseText="Library project: do not package apk..." >
        <if condition="${build.is.instrumented}">
            <then>
                <package-helper>
                    <extra-jars>
                        <!-- Injected from external file -->
                        <jarfile path="${emma.dir}/emma_device.jar" />
                    </extra-jars>
                </package-helper>
            </then>
            <else>
                <!-- We can finesse apkbuilder by putting secondary classes file(s) in a jar file -->
                <if condition="${build.is.multidex}">
                    <then>
                        <package-helper>
                            <extra-jars>
                                <jarfile path="${out.dir}/${multidex-secondary-classes.jar}" />
                            </extra-jars>
                        </package-helper>
                    </then>
                    <else>
                        <package-helper>
                            <extra-jars />
                        </package-helper>
                    </else>
                 </if>
            </else>
        </if>
    </do-only-if-not-library>
</target>

<!-- (2) Create a JAR file of the secondary classes*.dex files -->
<target name="-create-multidex-secondary-classes-jar" if="${build.is.multidex}">
    <jar destfile="${out.dir}/${multidex-secondary-classes.jar}"
         basedir="${out.dir}"
         includes="classes*.dex"
         excludes="classes.dex"
         filesonly="true"
         />
</target>

<!-- Standard import of Android build.xml -->
<import file="${sdk.dir}/tools/ant/build.xml" />

<!-- (3) Replacement of "dex-helper" to support multidex -->
<macrodef name="dex-helper">
    <element name="external-libs" optional="yes" />
    <attribute name="nolocals" default="false" />
    <sequential>
        <!-- sets the primary input for dex. If a pre-dex task sets it to
             something else this has no effect -->
        <property name="out.dex.input.absolute.dir" value="${out.classes.absolute.dir}" />

        <!-- set the secondary dx input: the project (and library) jar files
             If a pre-dex task sets it to something else this has no effect -->
        <if>
            <condition>
                <isreference refid="out.dex.jar.input.ref" />
            </condition>
            <else>
                <path id="out.dex.jar.input.ref">
                    <path refid="project.all.jars.path" />
                </path>
            </else>
        </if>

        <if condition="${build.is.multidex}" >
            <then>
                <if condition="${dex.force.jumbo}" >
                    <else>
                        <fail message="The following assumes dex.force.jumbo is true" />
                    </else>
                </if>
                <apply executable="${dx}" failonerror="true" parallel="true">
                    <arg value="--dex" />
                    <arg value="--force-jumbo" />

                    <!-- Specify a multi-dex APK -->
                    <arg value="--multi-dex" />

                    <!-- For multidex output to a folder -->
                    <arg value="--output" />
                    <arg value="${out.dir}" />

                    <path path="${out.dex.input.absolute.dir}" />
                </apply>
            </then>
            <else>
                <!-- The value from SDK/tools/ant/build.xml -->
                <dex executable="${dx}"
                        output="${intermediate.dex.file}"
                        dexedlibs="${out.dexed.absolute.dir}"
                        nolocals="@{nolocals}"
                        forceJumbo="${dex.force.jumbo}"
                        disableDexMerger="${dex.disable.merger}"
                        verbose="${verbose}">
                    <path path="${out.dex.input.absolute.dir}"/>
                    <path refid="out.dex.jar.input.ref" />
                    <external-libs />
                </dex>
            </else>
        </if>
    </sequential>
</macrodef>

修改(1)替换了包目标以允许将额外的jar文件传递给ApkBuilder。事实证明,ApkBuilder只是将JAR文件的内容复制到APK中,因此,如果我们将classes [1-N] .dex放入JAR中,就可以让ApkBuilder将这些额外的jar文件打包到APK中。
(2)使用除classes.dex之外的所有classes * .dex构建了额外的JAR文件,因此支持任意数量的额外classes.dex文件。
(3)是一种变通方法,因为“dex”AntTask不支持以任何方式传递--multi-dex到“dx.bat”,而“dx.bat”知道如何使用它。我查看了dx.bat正在调用的内容,并在此处直接添加了它(经过改进:michaelbrz在他的脚本中有更好的实现,因此我借用了它。谢谢)
顺便说一下,我们始终运行Proguard(无论是混淆还是未混淆)以获得类和方法收缩。这对于生成“主类”列表非常方便。在我们的情况下,我们添加了Google Play服务,这使我们的DEX文件方法限制失效,因此决定将这些类和方法放在第二个dex中。从Proguard输出生成该类列表只需过滤dump.txt即可。
<property name="multidex-main-dex-list.file"
          value="bin/multidex-main-dex-list.txt" />

<target name="-create-multidex-main-list" if="${build.is.multidex}">
    <delete file="${multidex-main-dex-list.file}" />
    <copy file="${out.dir}/proguard/dump.txt"
          tofile="${multidex-main-dex-list.file}" >
        <filterchain>

            <!-- Convert classes to the right format -->
            <tokenfilter>
                <containsregex
                    pattern="^..Program class..(.*)"
                    replace="\1.class"/>
            </tokenfilter>

            <!-- Exclude Google Play Services -->
            <linecontains negate="true">
                <contains value="com/google/android/gms/"/>
            </linecontains>

        </filterchain>
    </copy>
</target>

1
我们发现在“main dex列表”中必须为类命名,尤其是MultiDex支持的类本身。因此,最小的main dex列表如下所示:android/support/multidex/MultiDex.class android/support/multidex/MultiDex$V14.class android/support/multidex/MultiDex$V19.class android/support/multidex/MultiDex$V4.class android/support/multidex/MultiDexApplication.class android/support/multidex/MultiDexExtractor.class android/support/multidex/MultiDexExtractor$1.class android/support/multidex/ZipUtil.class android/support/multidex/ZipUtil$CentralDirectory.class - dkneller
我在运行这个解决方案时遇到了问题。好奇一下,你用的是哪个版本的Ant?out.dex.input.absolute.dir属性没有被正确传递到应用目标中,dex命令让我失望了。 :-/ 我使用的是Ant 1.9.6版本。 - Norman H
简单的,加到我的脚本之后。 - Ben Jima

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