快过年了,项目进度放换了,看android 热修复方案的时候,顺便把多dex拆分方案,看了一下。
1.前提执行备案
方案需要大家了解只是:
1.ant编译android项目
step 1: 下载安装
step 2: 跟android项目添加ant编译方式。参考 android update proejct -p ./
step 3: 然后在项目的根目录中运行:ant debug 或者ant releces
2.android 编译apk过程
大概流程:
step 1. 收集所有的java文件,包括:R.java(通过android aapt生成),.java文件,.aidl文件(通过android AIDL 工具生成)
step 2.将生成的java文件,调用javac编译为.class文件
step 3.执行代码混淆
step 4.将所有的.class文件,通过android dex工具,打包为一个.dex文件()。
step 5. 将res,asset,dex,打包为apk
step 6. 签名
以上是android apk编译的大概流程。
2. 拆分dex原理:
相关原来以及问题,个人感觉: android 多dex方案 整理的不错,就不在赘述。
3.实现方案
整体方案参考 上面连接描述的。这个方案并没有自己重写android apk编译流程的build.xml文件。只是将android 原来的(%ANDROID_HOME%/tools/ant/build.xml,复制到项目中,将原来的文件做了些小的添加,
整体方案如下:
1.在android 编译流程中 step 3代码混淆执行完成后,将需要拆分的class文件移动别的文件夹中
2. 在android 编译流程中 step 4中,当主dex文件执行完成后,在将需要拆分的class文件执行一次dex
3.在android编译流程中 step 5之前,将拆分出来的dex文件复制到asset/dex(路径可以随意指定)目录中
4.编译结束后,将复制到asset/dex文件中的dex文件删除
5. 在apk的Application的oncreate中通过,反射,将asset/dex文件夹中的所有dex文件,加载到ClassLoader中,见:MultiDex.java
下面进入主题:
step 1: 配置项目可以使用ant编译,参考上面ant使用
在生成的build.xml中添加:
<!-- 拆分dex,从build_apk.xml移动到这里 -->
<property name="out.dir" value="bin" />
<property name="out.absolute.dir" location="${out.dir}" />
<property name="asset.dir" value="assets" />
<property name="asset.absolute.dir" location="${asset.dir}" />
step 2 :将(%ANDROID_HOME%/tools/ant/build.xml 复制到项目的跟目录下,名称可以随意命名
A.我将android默认的build文件修改为:build_apk.xml
< import file ="build_apk.xml" />
C.定义代码混淆后执行的target,代码位置在<target name="-obfuscate">前面加入:
<!-- add by shehonghao ,拆分dex文件,开始-->
<!-- 代码混淆后执行 -->
<target name="-post-obfuscate"/>
<!-- add by shehonghao ,拆分dex文件,结束-->
D.在target:<target name="-obfuscate">的最后面加入:
<!-- add by shehonghao,拆分dex文件,开始-->
<!-- 代码混淆结束后,开始拆封class文件 -->
<antcall target="-post-obfuscate"></antcall>
<!-- add by shehonghao,拆分dex文件,结束-->
在代码之前添加:
<!-- add by shehonghao ,拆分dex文件,开始-->
<!-- 打包dex后执行 -->
<target name="-post-dex" />
<!-- 创建资源后执行 -->
<target name="-pre-crunch"></target>
<!-- add by shehonghao ,拆分dex文件,结束-->
F.找到: <macrodef name="dex-helper">,在<dex executalbe="${ds}的之后添加:
<antcall target="-post-dex"></antcall>
step 3: 创建多拆分包build_multi.xml,并在build.xml中引入:
文件内容:
<?xml version="1.0" encoding="UTF-8"?>
<project
name="MultiDemo_"
default="help" >
<!-- 不需要修改的 开始 -->
<!-- dex在asset目录中的位置, -->
<property name="dex.asset.dir" value="dex" />
<!-- aseent/dex目录中 -->
<property name="mulite.abs.dir" value="${asset.absolute.dir}/${dex.asset.dir}" />
<!-- 不需要修改的 结束 -->
<!-- 拆分dex 1 -->
<!-- dex在dex.asset.dir文件夹中的文件名称 -->
<property name="dex.file.name1" value="classes1"></property>
<!-- 保存被拆分出来的class文件的路径-->
<property name="out.multi.classes.absolute.dir1" location="${out.dir}/${dex.file.name1}" />
<!-- 编译后,生成的dex文件的绝对路径,包含文件名 -->
<property name="intermediate.dex.file1" location="${out.absolute.dir}/${dex.file.name1}.dex" />
<!-- 拆分dex 2 -->
<property name="dex.file.name2" value="classes2"></property>
<!-- 保存被拆分出来的class文件的路径-->
<property name="out.multi.classes.absolute.dir2" location="${out.dir}/${dex.file.name2}" />
<!-- 编译后,生成的dex文件的绝对路径,包含文件名 -->
<property name="intermediate.dex.file2" location="${out.absolute.dir}/${dex.file.name2}.dex" />
<!-- 创建保存拆分class文件的路径,每个dex文件需要一个路径 , 需要修改 -->
<target name="-init" >
<!-- 创建第一个需要被拆分的文件路径 -->
<mkdir dir="${out.multi.classes.absolute.dir1}" ></mkdir>
<mkdir dir="${out.multi.classes.absolute.dir2}" ></mkdir>
</target>
<!-- 代码混淆后执行移动class文件策略,不需要修改 -->
<target name="-post-obfuscate" depends="-init" >
<!-- 将需要拆分的class文件,移动到相应的路径中 -->
<antcall target="-move-mulit-class"></antcall>
</target>
<!-- 移动class文件,不需要修改 -->
<macrodef name="move-class">
<attribute name="srcDir" description="源class做在的相对路径,bin/目录开始"></attribute>
<attribute name="disDir" description="目标class做在的相对路径,bin/目录开始"></attribute>
<sequential>
<if>
<condition>
<and>
<length string="@{srcDir}" trim="true" when="greater" length="0" />
</and>
<and>
<length string="@{disDir}" trim="true" when="greater" length="0" />
</and>
</condition>
<then>
<move file="@{srcDir}" todir="@{disDir}" ></move>
</then>
<else>
<echo> 为设置远文件,或目标文件</echo>
</else>
</if>
</sequential>
</macrodef>
<!-- 移动需要拆分的class文件 ,需要修改-->
<target name="-move-mulit-class">
<!-- 将所有需要拆封在一个dex文件中的class移动一个文件夹中 -->
<move-class srcDir="${out.dir}/classes/com/multi/dex1" disDir="${out.multi.classes.absolute.dir1}/com/multi/"></move-class>
<!-- dex 2 中包含的class文件 -->
<move-class srcDir="${out.dir}/classes/com/multi/dex2" disDir="${out.multi.classes.absolute.dir2}/com/multi/"></move-class>
</target>
<!-- 主dex编译结束后,编译拆分的dex,需要修改 -->
<target name="-post-dex">
<!--打包第一个被拆分dex文件 -->
<echo>编译拆分dex 1</echo>
<dex executable="${dx}"
output="${intermediate.dex.file1}"
nolocals="@{nolocals}"
forceJumbo="${dex.force.jumbo}"
disableDexMerger="${dex.disable.merger}"
verbose="${verbose}">
<path path="${out.multi.classes.absolute.dir1}"/>
</dex>
<!-- 打包第二个被拆分的dex文件 -->
<echo>编译拆分dex 2</echo>
<dex executable="${dx}"
output="${intermediate.dex.file2}"
nolocals="@{nolocals}"
forceJumbo="${dex.force.jumbo}"
disableDexMerger="${dex.disable.merger}"
verbose="${verbose}">
<path path="${out.multi.classes.absolute.dir2}"/>
</dex>
</target>
<!-- 将分拆的dex文件,复制到assents/dex目录,需要修改 -->
<target name="-pre-crunch">
<!-- copy dex1 -->
<echo>复制拆分dex 1</echo>
<move file="${intermediate.dex.file1}" todir="${mulite.abs.dir}"></move>
<!-- copy dex2 -->
<echo>复制拆分dex 2</echo>
<move file="${intermediate.dex.file2}" todir="${mulite.abs.dir}"></move>
</target>
<!-- 编译结束,删除压缩的dex文件, 需要修改-->
<target name="-post-build">
<!-- 删除dex1 -->
<delete file="${mulite.abs.dir}/${dex.file.name1}.dex"></delete>
<!-- 删除dex2 -->
<delete file="${mulite.abs.dir}/${dex.file.name2}.dex"></delete>
</target>
</project>
通过项目三步,编译好的apk的asset/dex目录下,就会存在被拆分出来的dex文件。
最后一步,将拆分出来的dex文件添加到主classload中
方案:
1.通过反射将当前已经加载进来的dex文件对接保存到内存中:
getDexElementsByReflect(getPathListByReflect(pathClassLoader));
2.将指定路径中的dex文件加载到内存中:
/**
dex到内存中
*
@param
@param
@param
@return
*/
private
// 加载指定路径的dex文件
DexClassLoader dexClassLoader =
new DexClassLoader(dexPath, context.getDir("dex", 0).getAbsolutePath(), dexPath, pathClassLoader);
// 反射获取DexClasLoader的pathList属性
Object classLoaderPathList = getPathListByReflect(dexClassLoader);
if (classLoaderPathList == null) {
if (is_debug) {
TAG, "路径--》" + dexPath + " 加载到任何的class,请确认文件格式是否正确");
}
return null;
}
// 反射获取dalvik.system.DexPathList的dexElements熟悉
return
}
3.将所有的dex,合并为一个数组,然后通过反射设置给主classloader。这样就完成,代码如下:
// 合并所有的dex文件
Object array = createObjectTypeArray(allDexElements[0].getClass(), dexNum);
Object[] list = (Object[]) array;
dexNum = 0;
for (int i = 0; i < allDexElements.length; i++) {
if (allDexElements[i] != null) {
elementList = (Object[]) allDexElements[i];
length);
length;
}
}
Object pathList = getPathListByReflect(pathClassLoader);
//
"dexElements", list);