ant脚本使用multidex解决65536问题

时间:2022-09-11 08:47:58

现在的android项目应该大多都用gradle构建了吧,但是仍然有很多老项目使用的ant工具,这里并不推荐使用ant构建,因为最新的android sdk tools里边已经去掉了ant相关的lib包。不管gradle也好,ant也好,其实编译打包apk的过程基本都是一样的。

我遇到的这个项目,经历了两次方案的调整。

方案一:

最开始并没用dx的multidex参数,而是将所有的第三方jar包(应用启动时用的除外)打到从包中去,从包可以是多个,剩下的源码打到主包中,也就是分别调用dx打出dex包。

<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>
<echo>Converting external libraries into ${assets}/${dex}...</echo>
<delete file="${asset.absolute.dir}\plug.jar"/>
<delete file="${asset.absolute.dir}\plug2.jar"/>
<if condition="${proguard.enabled}">
<then>
<separete
inputjar="${out.dex.input.absolute.dir}"
mapping="${obfuscate.absolute.dir}/mapping.txt"
alljarpath="project.all.jars.path"
plugjarconfig="${plug.jar.config}"
plugjarconfig2="${plug.jar.config2}"
plugjarpath="project.plug.path"
plugjarpath2="project.plug.path2"
projectjarpath="project.core.path"/>
<path id="project.dex.core.path">
<path refid="project.core.path" />
</path>
</then>
<else>
<filterlib
alljarpath="project.all.jars.path"   plugjarconfig="${plug.jar.config}"   plugjarconfig2="${plug.jar.config2}"    plugjarpath="project.plug.path"    plugjarpath2="project.plug.path2"    projectjarpath="project.core.path"/>                     <path id="project.dex.core.path">                         <path path="${out.dex.input.absolute.dir}"/>               <path refid="project.core.path" />                    </path>                </else>   </if>            <dex executable="${dx}"                    output="${asset.absolute.dir}\plug.jar"                  dexedlibs="${out.dexed.absolute.dir}"                    nolocals="@{nolocals}"                    forceJumbo="${dex.force.jumbo}"                    disableDexMerger="${dex.disable.merger}"                    verbose="${verbose}">                <path refid="project.plug.path" />            </dex>            <dex executable="${dx}"                    output="${asset.absolute.dir}\plug2.jar"                  dexedlibs="${out.dexed.absolute.dir}"                    nolocals="@{nolocals}"                    forceJumbo="${dex.force.jumbo}"                    disableDexMerger="${dex.disable.merger}"                    verbose="${verbose}">                <path refid="project.plug.path2" />            </dex>            <delete file="${asset.absolute.dir}\plug.jar.d"/>            <delete file="${asset.absolute.dir}\plug2.jar.d"/>    <checksum file="${asset.absolute.dir}\plug.jar" forceOverwrite="yes"/>    <checksum file="${asset.absolute.dir}\plug2.jar" forceOverwrite="yes"/>    <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 refid="project.dex.core.path" />                <external-libs />            </dex>                    </sequential>    </macrodef>

其中separete和filterlib是两个自定义的task,作用都是从project.all.jars.path中过滤出事先定义好的第三方jar包的path,区别是separete是在代码经过混淆后执行,将混淆后的代码生成plug.jar,可以看到调用了三次dex。

代码中加载从包的方法:

PathClassLoader pathClassLoader = (PathClassLoader) app.getClassLoader();
DexClassLoader dexClassLoader = new DexClassLoader(libPath, app.getDir("dex", 0).getAbsolutePath(), libPath, app.getClassLoader());
InjectResult result = null;
try {
Object dexElements = combineArray(getDexElements(getPathList(pathClassLoader)), getDexElements(getPathList(dexClassLoader)));
Object pathList = getPathList(pathClassLoader);
setField(pathList, pathList.getClass(), "dexElements", dexElements);
} catch (IllegalArgumentException e) {
result = makeInjectResult(false, e);
e.printStackTrace();
}

DexClassLoader加载从包dex,然后将pathClassLoader与dexClassLoader中的DexPathList的dexElements属性值合并,再放到pathClassLoader中的DexPathList中。最开始方案一是满足需求的,但随着主包越来越大,终于还是65536了,这种只分出去第三方jar包的做法还是不太灵活,于是有了方案二。

方案二:

使用dx的multidex参数,指定maindexlist清单文件,在这个清单文件中的类会打到主包中,清单文件的生成可以使用build-tools下的mainDexClasses脚本生成。

 <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}" />
<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>
<echo message="start dx ${maindexlist.dir}"/>
<multidex executable="${dx}"
output="${out.dir}"
dexedlibs="${out.dexed.absolute.dir}"
nolocals="@{nolocals}"
forceJumbo="${dex.force.jumbo}"
disableDexMerger="${dex.disable.merger}"
multidex="true"
mainDexList="${maindexlist.dir}"
verbose="${verbose}">
<path path="${out.dex.input.absolute.dir}"/>
<path refid="out.dex.jar.input.ref" />
<external-libs />
</multidex>
</sequential>
</macrodef>
<target name="-maindexlist" depends="-set-release-mode, -compile, -post-compile, -obfuscate">        <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>        <maindexlist output="build/maindexlist-proguard.txt"                mainDexList="build/maindexlist.txt"                mappingFile="${out.absolute.dir}/proguard/mapping.txt" >            <path path="${out.dex.input.absolute.dir}"/>            <path refid="out.dex.jar.input.ref" />        </maindexlist>    </target>

m ultidex和maindexlist也是两个自定义task,multidex其实就是调用的dx --dex --multi-dex --main-dex-list=maindexlist.txt  --minimal-main-dex --output bin --input-list,maindexlist task的作用是生成混淆后的主包清单文件。应用启动时代码原理和方案一差不多,也可以直接使用官方的android-surpport-multidex.jar包