Android热修复之dex多分包架构设计
2016-09-13 01:41
621 查看
转载请注明出处:http://blog.csdn.net/magic_jss/article/details/52521056;
自2015年QQ空间Team放出安卓App热补丁动态修复技术介绍之后,各种热修复技术层出不穷,越来越多的项目也开始尝试这种新技术,也有了一些相对稳定的框架出现。本文主要讲解如何在Eclipse中利用Ant构建工具产生多个dex文件,从而为热修复做准备。若有问题恳请指正,欢迎评论交流!
dex多分包的应用场景:
解决方法数越界,因为单个dex文件中方法数最多为65536个。超出将抛出DexIndexOverflowException。(方法包括:自定义方法,FrameWork及第三方Jar的所有方法,可通过IDA Pro逆向工具进行查看)。
实现热修复,该热修复根据QQ空间Team的热补丁动态修复技术原理进行修复。
Ant打包主要流程:
删除gen、bin目录并新建
生成R.java类文件
编译aidl文件
编译源文件生成对应的class文件
将.class文件转化成.dex文件
打包资源文件
生成未签名的apk包
对未签名的apk包进行签名
对签名的apk包进行字节对齐
在第5步,将.class文件转化成.dex文件,在这里可以修改打包策略产生多个dex文件,修改如下:
主包:classes.dex主要存放Activity、Application等入口类。
从包:classesn.dex主要存放业务逻辑类,可能出现bug的类,后期修复的类。
c.txt
将该文件里面的class文件打包到classes.dex(主包),一般情况下要保证该文件里面的类不会出现问题。
在第7步,需要将主包classes.dex添加到未签名的apk中去。
由于aapt命令在添加或者删除的时候不能是绝对路径,而是相对路径,因此需要拷贝文件到项目的根目录下面,因为我们的脚本是在根目录下面,这样在运行aapt的时候,可以直接操作dex文件了。
添加其他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的路径。
其他步骤就是对apk进行签名,对签名的apk包进行字节对齐,和之前文章一样不在赘述。
最后通过cmd进入到该项目目录下执行 ant make (make为自定义)命令即可进行打包。由于项目中我只打包了两个dex,即classes.dex、classes2.dex,查看dex文件内容如下:
classes.dex
classes2.dex
生命不息,奋斗不止!
自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>
生命不息,奋斗不止!
相关文章推荐
- Android热补丁动态修复技术(一):从Dex分包原理到热补丁
- 关于 Android中的插件化开发,dex分包,热修复(Tinker)的思考(二)
- Android热补丁动态修复技术(一)dex分包原理
- Android热修复+dex分包+相关知识总结
- Android热补丁动态修复技术(一):从Dex分包原理到热补丁
- Android dex分包方案以及热补丁修复
- 关于 Android中的插件化开发,dex分包,热修复(Tinker)的思考(一)
- Android热补丁动态修复技术(一):从Dex分包原理到热补丁
- Android热修复三部曲之MultiDex 分包架构
- Android热修复三部曲之MultiDex 分包架构
- Android热补丁动态修复技术(一):从Dex分包原理到热补丁
- 精通android体系架构、mvc、常见的设计模式、控制反转(ioc)
- Android自动化测试从入门到精通(Robotium自动化测试工具、架构设计、云测试应用)
- android app 架构设计02
- android app 架构设计01
- Android架构:认识简法设计与EIT软件造形
- Android dex分包方案
- [经验分享] 精通android体系架构、mvc、常见的设计模式、控制反转(ioc)
- 王家林的81门一站式云计算分布式大数据&移动互联网解决方案课程第14门课程:Android软硬整合设计与框架揭秘: HAL&Framework &Native Service &App&HTML5架构设计与实战开发
- 精通android体系架构、mvc、常见的设计模式、控制反转(ioc)