Включить опцию `–multi-dex` в ant для Android

Легко включить параметр multi-dex для системы построения градиентов, но я не нашел примера, как я могу включить эту опцию для создания муравьев. Как это сделать?

У нас есть 2 варианта:

  1. Изменить DexExecTask [ввести новый параметр для multi dex], компилировать ant.jar, использовать эту банку для построения. Мне не нравится этот вариант, потому что мы должны предоставить обновленный ant.jar для всех членов команды.
  2. Измените файл проекта build.xml. Я нашел потрясающий файл сборки Ant, который имеет все модификации для поддержки multi-dex: https://github.com/ruboto/ruboto-irb/blob/master/build.xml

Надеюсь, что это помогает.

Я представил поддержку multi-dex в приложении, используя муравей, основанный на файле build.xml Ruboto (для них это замечательно !). Так вот путь, который я бы рекомендовал принять. В этом случае вам не нужно создавать пользовательские баннеры ant, просто отредактируйте файл build.xml и примените все шаги без градиента от https://developer.android.com/tools/building/multidex.html.

Что касается build.xml, вам нужно будет взглянуть, хорошо ли все пути хорошо взаимодействуют друг с другом, возможно, вы изменили некоторые из них и вам нужно настроить шаги муравьев.

В моем случае мне пришлось также генерировать список классов, которые должны быть помещены в первый сгенерированный dex (по умолчанию один), поэтому приложение может даже запускаться, когда оно загружает дополнительные dexes в onCreate приложения. Для этого вам нужно запустить скрипт mainDexClasses, который вы найдете в своих инструментах построения:

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

Алекс Липов написал хороший пост об этом.

Когда у вас есть список созданных классов, вы должны указать этот список в параметре dx, добавив

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

Где dx выполняется.

Стоит вспомнить, что скрипт Рубото предполагает, что будет создан только один дополнительный 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> 

И поддержка multidex в Ant включена! Это не так просто, как с градиентом, но это возможно.

UPDATE: my 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> 

Я также добавил multidex в сборку ANT, но несколько иначе, что более гибко в поддержке классов * .dex.

В любом случае, я сделал это в два этапа: 1) получить DX для вывода multidex, затем 2) использовать aapt для включения классов multiidex. Существует необязательный шаг для создания списка основных классов в конце этой публикации, но это не обязательно для основной реализации.

Вот реализация в 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> в ... 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) заменяет цель -пакет, чтобы позволить передавать дополнительные банки в ApkBuilder. Оказывается, ApkBuilder просто копирует содержимое файлов JAR в APK, поэтому, если мы помещаем наши классы [1-N] .dex в JAR, мы можем заставить ApkBuilder упаковать эти дополнительные банки в APK.

(2) Создает этот дополнительный JAR-файл со всеми классами * .dex, за исключением classes.dex, поэтому поддерживает любое количество дополнительных файлов classes.dex

(3) Работает для этого факта, что Antxask dex не поддерживает какой-либо способ передать –multi-dex на «dx.bat», который знает, как его использовать. Я посмотрел, что dx.bat вызывал, и добавил, что прямо здесь (исправлено: michaelbrz имеет лучшую реализацию в своем скрипте, поэтому я заимствовал его. Спасибо)

BTW, мы всегда запускаем 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>