现在的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包