【问题标题】:Enable `--multi-dex` option in ant for Android在 Android 的 ant 中启用 `--multi-dex` 选项
【发布时间】:2015-03-10 06:46:49
【问题描述】:

为 gradle 构建系统启用 multi-dex 选项很容易,但我还没有找到如何为 ant 构建启用此选项的示例。这个怎么存档?

【问题讨论】:

    标签: android ant build-system android-multidex


    【解决方案1】:

    我们有两个选择:

    1. 更改DexExecTask【为multi dex引入新参数】,编译ant.jar,使用此jar进行构建。我不喜欢这个选项,因为我们必须为所有团队成员提供更新的 ant.jar。
    2. 修改项目 build.xml 文件。我找到了很棒的 ant 构建文件,其中包含支持多 dex 的所有修改:https://github.com/ruboto/ruboto-irb/blob/master/build.xml

    希望对您有所帮助。

    【讨论】:

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

    我在使用基于Ruboto's build.xml 的 ant 的应用程序中引入了多 dex 支持(向他们致敬!)。所以这就是我推荐的路径。在这种情况下,您不必创建自定义 ant jar,只需编辑您的 build.xml 并应用来自https://developer.android.com/tools/building/multidex.html的所有非 gradle 步骤

    至于 build.xml,你必须看看所有路径是否相互兼容,你可能已经更改了其中一些,需要调整 ant 步骤。

    在我的情况下,我还必须生成必须放置在第一个生成的 dex 中的类列表(默认一个),因此应用程序甚至可以在加载应用程序的 onCreate 中的其他 dex 时启动。为此,您需要运行脚本 mainDexClasses,您可以在构建工具中找到它:

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

    Alex Lipov 写了一篇关于它的好帖子。

    当你生成了类列表时,你必须在 dx 的参数中通过添加来指向这个列表

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

    dx 的执行位置。

    值得记住的是,Ruboto 的脚本假定只会创建一个额外的 dex,但情况并非总是如此。我生成了两个额外的 dex,所以我实现了添加 if 存在:

                <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 部分)
    • 对,这可能会有所帮助。我在答案中添加了 build.xml。
    【解决方案3】:

    我还在 ANT 构建中添加了 multidex,但方式略有不同,在 classes*.dex 支持方面更加灵活。

    无论如何,我分两个阶段完成此操作:1) 让 DX 输出 multidex,然后 2) 使用 aapt 包含 multidex 类。在这篇文章的末尾有一个可选的步骤来生成主类列表,但对于基本实现来说这不是必需的。

    这是 build.xml 中的实现,后面是 cmets:

    ... 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) 替换 -package 目标以允许将额外的 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 调用的内容并直接在此处添加(修改: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>
    

    【讨论】:

    • 我们发现在“主 dex 列表”中命名类是绝对必要的,尤其是 MultiDex 支持类本身。所以一个最小的主 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
    • 我在运行此解决方案时遇到问题。很好奇,但是您使用的是什么版本的 Ant? out.dex.input.absolute.dir 属性没有正确传递到应用目标中,并且 dex 命令正在救我。 :-/ Ant 1.9.6 是我的版本。
    • 简单,在我的脚本之后添加。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多