您的位置:首页 > 运维架构 > 网站架构

Android热修复之dex多分包架构设计

2016-09-13 01:41 621 查看
转载请注明出处:http://blog.csdn.net/magic_jss/article/details/52521056

自2015年QQ空间Team放出安卓App热补丁动态修复技术介绍之后,各种热修复技术层出不穷,越来越多的项目也开始尝试这种新技术,也有了一些相对稳定的框架出现。本文主要讲解如何在Eclipse中利用Ant构建工具产生多个dex文件,从而为热修复做准备。若有问题恳请指正,欢迎评论交流!



1、什么是dex多分包?

一般情况下Apk解压后里面都会包含一个classes.dex文件,该文件里面包含了应用的所有.class文件,当创建多个dex文件,并指定某些.class到指定dex文件中就是dex多分包。简言之,一个Apk包含多个.dex文件。

dex多分包的应用场景:

解决方法数越界,因为单个dex文件中方法数最多为65536个。超出将抛出DexIndexOverflowException。(方法包括:自定义方法,FrameWork及第三方Jar的所有方法,可通过IDA Pro逆向工具进行查看)。

实现热修复,该热修复根据QQ空间Team的热补丁动态修复技术原理进行修复。

2、dex多分包的基本步骤

在ADT中对Android项目进行多分包需要借助Ant构建工具,通过修改构建策略和规则产生多分包,如果你对Ant构建不是很了解可以参考http://blog.csdn.net/magic_jss/article/details/52504131;该步骤讲解主要是根据前篇文章的修改。

Ant打包主要流程:

删除gen、bin目录并新建

生成R.java类文件

编译aidl文件

编译源文件生成对应的class文件

将.class文件转化成.dex文件

打包资源文件

生成未签名的apk包

对未签名的apk包进行签名

对签名的apk包进行字节对齐

在第5步,将.class文件转化成.dex文件,在这里可以修改打包策略产生多个dex文件,修改如下:

主包:classes.dex主要存放Activity、Application等入口类。

从包:classesn.dex主要存放业务逻辑类,可能出现bug的类,后期修复的类。

<!-- 将.class文件转化成.dex文件 -->
<target
name="dex"
depends="compile" >

<echo message="dex..." />

<exec
executable="${dx}"
failonerror="true" >

<arg value="--dex" />

<arg value="--multi-dex" /><!-- 多分包命令 -->

<arg value="--set-max-idx-number=10000" /><!-- 指定单个dex中的方法数 -->

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

<arg value="${basedir}/c.txt" /><!-- 放到主包里面的class文件 -->

<arg value="--minimal-main-dex" />

<arg value="--output=${bin}" /> <!-- 输出位置 -->

<arg value="${bin}" /><!-- 把bin下所有class打包 -->

<arg value="${libs}" /><!-- 把libs下所有jar打包 -->
</exec>
</target>


c.txt

将该文件里面的class文件打包到classes.dex(主包),一般情况下要保证该文件里面的类不会出现问题。

com/magic/test_hotfix/MainActivity.class
com/magic/test_hotfix/MainActivity$1.class//代表内部类
com/magic/test_hotfix/HotFixUtils.class


在第7步,需要将主包classes.dex添加到未签名的apk中去。

<!-- 根据classes.dex文件和resources.ap_生成未签名的apk包 -->
<target
name="package"
depends="package-res-and-assets" >

<echo message="package..." />

<exec
executable="${apkbuilder}"
failonerror="true" >

<arg value="${unsigned-package}" /><!-- 输出 -->

<arg value="-u" /><!-- u指创建未签名的包 -->

<arg value="-z" /><!-- 资源压缩包 -->

<arg value="${resources-package}" />

<arg value="-f" /><!-- dex文件 -->

<arg value="${bin}/classes.dex" />

<arg value="-rf" />

<arg value="${src}" />

<arg value="-rj" />

<arg value="${libs}" />

<arg value="-nf" />

<arg value="${libs}" />
</exec>
</target>


由于aapt命令在添加或者删除的时候不能是绝对路径,而是相对路径,因此需要拷贝文件到项目的根目录下面,因为我们的脚本是在根目录下面,这样在运行aapt的时候,可以直接操作dex文件了。

<!-- 拷贝文件到项目的根目录下面,因为我们的脚本是在根目录下面,这样在运行aapt的时候,可以直接操作dex文件了 -->
<target
name="copy_dex"
depends="package" >

<echo message="copy dex..." />

<copy todir="." >

<fileset dir="${bin}" >

<include name="classes*.dex" />
</fileset>
</copy>
</target>


添加其他dex文件到未签名Apk中去。这里需要用到ant-contrib-1.0b3.jar,下载地址http://download.csdn.net/detail/magic_jss/9628968;将该Jar放到Ant解压目录的lib目录下面,还需要在build.xml中指定ant-contrib-1.0b3.jar的路径。

<!-- ant-contrib-1.0b3.jar的路径-->
<taskdef
classpath="D:\Ant\apache-ant-1.9.7\lib\ant-contrib-1.0b3.jar"
resource="net/sf/antcontrib/antlib.xml" />


<!-- 使用aapt命令添加dex文件 -->
<target name="aaptadd-dex" >

<echo message="${dir.name}" />
<!-- 使用正则表达式获取classes的文件名 -->
<propertyregex
casesensitive="false"
input="${dir.name}"
property="dexfile"
regexp="classes(.*).dex"
select="\0" />
<!-- 这里不需要添加classes.dex文件 -->
<if>
<equals
arg1="${dexfile}"
arg2="classes.dex" />

<then>
<echo>
${dexfile} is not handle
</echo>
</then>

<else>
<echo>
${dexfile} is handle
</echo>

<exec
executable="${aapt}"
failonerror="true" >

<arg value="add" />

<arg value="${unsigned-package}" />

<arg value="${dexfile}" />
</exec>
</else>
</if>

<delete file="./${dexfile}" /><!-- 删除项目根目录下dex文件 -->
</target>


其他步骤就是对apk进行签名,对签名的apk包进行字节对齐,和之前文章一样不在赘述。

最后通过cmd进入到该项目目录下执行 ant make (make为自定义)命令即可进行打包。由于项目中我只打包了两个dex,即classes.dex、classes2.dex,查看dex文件内容如下:

classes.dex



classes2.dex



3、完整的build.xml脚本

<?xml version="1.0" encoding="UTF-8"?>
<project
name="Test_HotFix"
default="make" >

<property file="local.properties" />

<property file="ant.properties" />

<property environment="env" />

<condition
property="sdk.dir"
value="${env.ANDROID_HOME}" >

<isset property="env.ANDROID_HOME" />
</condition>

<taskdef
classpath="D:\Ant\apache-ant-1.9.7\lib\ant-contrib-1.0b3.jar"
resource="net/sf/antcontrib/antlib.xml" />

<!-- 相关目录 -->
<property
name="basedir"
value="." />

<property
name="sdk-tools"
value="${sdk.dir}/tools" />

<property
name="sdk-build-tools"
value="${sdk.dir}/build-tools/android-4.4.2" />

<property
name="android-jar"
value="${sdk.dir}/platforms/android-19/android.jar" />

<property
name="framework-aidl"
value="${sdk.dir}/platforms/android-19/framework.aidl" />

<!-- 编译工具 -->
<property
name="aapt"
value="${sdk-build-tools}/aapt.exe" />

<property
name="aidl"
value="${sdk-build-tools}/aidl.exe" />

<property
name="dx"
value="${sdk-build-tools}/dx.bat" />

<property
name="apkbuilder"
value="${sdk-tools}/apkbuilder.bat" />

<property
name="jarsigner"
value="${env.JAVA_HOME}/bin/jarsigner.exe" />

<property
name="zipalign"
value="${sdk-build-tools}/zipalign.exe" />

<!-- 输入目录 -->
<property
name="assets"
value="${basedir}/assets" />

<property
name="res"
value="${basedir}/res" />

<property
name="src"
value="${basedir}/src" />

<property
name="libs"
value="${basedir}/libs" />

<!-- 输出目录 -->
<property
name="bin"
value="${basedir}/bin" />

<property
name="gen"
value="${basedir}/gen" />

<property
name="resources-package"
value="${bin}/resources.ap_" />

<property
name="unsigned-package"
value="${bin}/${ant.project.name}-unsigned.apk" />

<property
name="signed-package"
value="${bin}/${ant.project.name}-singed.apk" />

<property
name="zipalign-package"
value="${bin}/${ant.project.name}-zipalign.apk" />

<!-- 初始化 -->
<!-- 删除gen、bin目录并新建 -->
<target name="init" >

<echo message="init..." />

<delete dir="${bin}" />

<delete dir="${gen}" />

<mkdir dir="${bin}" />

<mkdir dir="${gen}" />
</target>

<!-- 生成R.java类文件 -->
<target
name="gen-R"
depends="init" >

<echo message="gen-R..." />

<exec
executable="${aapt}"
failonerror="true" >

<arg value="package" /><!-- package表示打包 -->

<arg value="-m" /><!-- m,J,gen表示创建包名的目录和R.java到gen目录下 -->

<arg value="-J" />

<arg value="${gen}" />

<arg value="-M" /><!-- M指定AndroidManifest.xml文件 -->

<arg value="AndroidManifest.xml" />

<arg value="-S" /><!-- S指定res目录 -->

<arg value="${res}" />

<arg value="-A" />

<arg value="${assets}" />

<arg value="-I" /><!-- I指定android包的位置 -->

<arg value="${android-jar}" />
</exec>
</target>

<!-- 编译aidl文件 -->
<target
name="aidl"
depends="gen-R" >

<echo message="aidl..." />

<apply
executable="${aidl}"
failonerror="true" >

<arg value="-p${framework-aidl}" />

<arg value="-I${src}" />

<arg value="-o${gen}" />

<fileset dir="${src}" >

<include name="**/*.aidl" />
</fileset>
</apply>
</target>

<!-- 编译源文件生成对应的class文件 -->
<target
name="compile"
depends="aidl" >

<echo message="compile..." />

<javac
bootclasspath="${android-jar}"
destdir="${bin}"
encoding="gbk"
includeantruntime="false"
source="1.6"
target="1.6" >

<compilerarg value="-nowarn" />

<src path="${src}" />

<src path="${gen}" />

<classpath>

<fileset
dir="${libs}"
includes="*.jar" />
</classpath>
</javac>
</target>

<!-- 将.class文件转化成.dex文件 -->
<target
name="dex"
depends="compile" >

<echo message="dex..." />

<exec
executable="${dx}"
failonerror="true" >

<arg value="--dex" />

<arg value="--multi-dex" /><!-- 多分包命令 -->

<arg value="--set-max-idx-number=10000" />

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

<arg value="${basedir}/c.txt" />

<arg value="--minimal-main-dex" />

<arg value="--output=${bin}" /> <!-- 输出位置 -->

<arg value="${bin}" /><!-- 把bin下所有class打包 -->

<arg value="${libs}" /><!-- 把libs下所有jar打包 -->
</exec>
</target>

<!-- 打包资源文件(包括res、assets、AndroidManifest.xml) -->
<target
name="package-res-and-assets"
depends="dex" >

<echo message="package-res-and-assets..." />

<exec
executable="${aapt}"
failonerror="true" >

<arg value="package" />

<arg value="-f" /><!-- 资源覆盖重写 -->

<arg value="-M" />

<arg value="AndroidManifest.xml" />

<arg value="-S" />

<arg value="${res}" />

<arg value="-A" /> <!-- 与R.java不同,需要asset目录也打包 -->

<arg value="${assets}" />

<arg value="-I" />

<arg value="${android-jar}" />

<arg value="-F" /><!-- 输出资源压缩包 -->

<arg value="${resources-package}" />
</exec>
</target>

<!-- 根据classes.dex文件和resources.ap_生成未签名的apk包 -->
<target
name="package"
depends="package-res-and-assets" >

<echo message="package..." />

<exec
executable="${apkbuilder}"
failonerror="true" >

<arg value="${unsigned-package}" /><!-- 输出 -->

<arg value="-u" /><!-- u指创建未签名的包 -->

<arg value="-z" /><!-- 资源压缩包 -->

<arg value="${resources-package}" />

<arg value="-f" /><!-- dex文件 -->

<arg value="${bin}/classes.dex" />

<arg value="-rf" />

<arg value="${src}" />

<arg value="-rj" />

<arg value="${libs}" />

<arg value="-nf" />

<arg value="${libs}" />
</exec>
</target>

<!-- 拷贝文件到项目的根目录下面,因为我们的脚本是在根目录下面,这样在运行aapt的时候,可以直接操作dex文件了 --> <target name="copy_dex" depends="package" > <echo message="copy dex..." /> <copy todir="." > <fileset dir="${bin}" > <include name="classes*.dex" /> </fileset> </copy> </target>

<!-- 循环遍历bin目录下的所有dex文件 -->
<target
name="add-subdex-toapk"
depends="copy_dex" >

<echo message="add-subdex-toapk..." />

<foreach
param="dir.name"
target="aaptadd-dex" >

<path>

<fileset
dir="${bin}"
includes="classes*.dex" />
</path>
</foreach>
</target>

<!-- 使用aapt命令添加dex文件 -->
<target name="aaptadd-dex" >

<echo message="${dir.name}" />
<!-- 使用正则表达式获取classes的文件名 -->
<propertyregex
casesensitive="false"
input="${dir.name}"
property="dexfile"
regexp="classes(.*).dex"
select="\0" />
<!-- 这里不需要添加classes.dex文件 -->
<if>
<equals
arg1="${dexfile}"
arg2="classes.dex" />

<then>

<echo>
${dexfile} is not handle
</echo>
</then>

<else>

<echo>
${dexfile} is handle
</echo>

<exec
executable="${aapt}"
failonerror="true" >

<arg value="add" />

<arg value="${unsigned-package}" />

<arg value="${dexfile}" />
</exec>
</else>
</if>

<delete file="./${dexfile}" />
</target>

<!-- 对生成的apk包进行签名 -->
<target
name="jarsigner"
depends="add-subdex-toapk" >

<echo message="jarsigner..." />

<exec
executable="${jarsigner}"
failonerror="true" >

<arg value="-verbose" />

<arg value="-digestalg" />

<arg value="SHA1" />

<arg value="-sigalg" />

<arg value="SHA1withRSA" />

<arg value="-keystore" /><!-- keystore -->

<arg value="${keystore}" />

<arg value="-storepass" /><!-- 秘钥 -->

<arg value="${storepass}" />

<arg value="-keypass" /> <!-- 秘钥口令 -->

<arg value="${keypass}" />

<arg value="-signedjar" />

<arg value="${signed-package}" /><!-- 输出 -->

<arg value="${unsigned-package}" /><!-- 未签名的apk -->

<arg value="${alias}" /><!-- 别名 -->
</exec>
</target>

<!-- 对签名的apk包进行字节对齐 -->
<target
name="make"
depends="jarsigner" >

<echo message="zipalign..." />

<exec
executable="${zipalign}"
failonerror="true" >

<arg value="-f" />

<arg value="-v" />

<arg value="4" />

<arg value="${signed-package}" />

<arg value="${zipalign-package}" />
</exec>

<echo message="make apk success!" />
</target>

</project>


生命不息,奋斗不止!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息