最近研究了一下Android打包apk的流程,简要描述一下。
我们可以跟随android的sdk目录下的tools/ant/build.xml文件的描述来一窥打包apk流程究竟。
首先这是用ant打包的过程,eclipse打包流程应该大抵一样。用ant打包前,如果project不是用ant创建的,先需要执行android update project -p /path/to/project。该命令会在project目录下生成相应的配置文件。如build.xml(与sdk下的那个不同),project.properties,local.properties等。生成的几个文件都是项目相关的配置参数等。ant打包需要build.xml配置文件,project下的配置文件包含sdk/tools/ant/build.xml文件。
project下的build.xml相对简单了,设定一些project属性,引用了project.properties,local.properties等文件。build.xml设定了ant的默认行为(target)是help。显示相应帮助。
sdk/tools/ant/build.xml相对复杂很多,一共有1300+行。乍看很多,粗粗分析开来,一块一块也相对较为明朗。即便我对ant配置一知半解,也看个大概明白。<property>就是设定一些key-value的属性,<target>就是ant对应的目标,类似于Makefile的那个目标。
这个build.xml相应地方做了良好的注释。这个文件内容分布大概如下:
1.定义一些覆盖默认设置的属性。还是有一些挺有意思的属性值得一看的,比如android.package.excludes,设定该属性可以排除编译一部分代码。再比如version.code,version.name可以替换AndroidManifest.xml中的相关版本内容。
2.自定义了一些任务。凭借我的揣测,这是ant提供的一些扩展,这些任务是通过${sdk.dir}/tools/lib/anttasks.jar这个jar包来导入定义的。定义了很多,列举一二,如com.android.ant.NewSetupTask,com.android.ant.AaptExecTask,com.android.ant.IfElseTask等等。
3.其它属性。主要是编译流程用到的一些变量属性,包含输入目录,输出目录,工具位置等。这里可以看见一些经常看到用到的目录和工具。比如src自不用多说,还有gen目录,libs目录等。此处加一段故事,很久之前,我是用Eclipse好的project在带我的bear那里用ant编译有问题,问题在于我将外部的jar包放在了lib目录下,ant编译的时候会找不到这个jar包了,但是放在libs里就可以了,到了这就可以解释了,因为build.xml暗暗的定义了这个默认的文件夹的名字。再插一句最新的adt也支持放在libs中而不需要做多余路径设定了。
4.宏定义。定义了一些多次用的流程为宏。比如do-only-if-not-library,如果不是android library project就怎样做。package-helper等等。
5.Build过程的一些Target。第一个是nodeps,很简单是设定了一个属性,来设定targets间是否存在依赖关系。然后有-per-clean,clean清空bin,gen目录做了这么些事。(前面加-的target,从某种程度来说是private的target,因为输入ant -xxx的时候,-xxx会当作ant的参数,而不是build的目标)
然后就涉及打包流程了。-setup,做一些初始化工作,创建bin目录等,设定一些属性等。-build-setup,也是做一些build的初始化工作。然后是-per-build这个是空的,这个是用来给用户做一些自定义设定或者实现预留的,后面类似这样预留的无内容的部分我就不讲了。然后是-code-gen,这个就有意思一些了,最为人熟知的android中的R文件就要生成了,生成R文件在这个过程做了定义,通过aapt程序(这个程序很有意思,这里略去不讲,没准下回会讲)生成R文件。-code-gen过程还有一些重量级的东西。比如生成用于进程通信的aidl文件相应类的生成,还有renderscript的生成。还有BuildConfig文件的生成。然后就是激动人心的compile编译过程了,就是javac做编译,生成class文件。这里有些令人激动的事情。就是Android为了代码复用,提出了library project的概念,某种程度上还不错,但是扩展能力有限,还是有些坑爹的,而且有一些限制。此处再插故事一则,我有3个project,分别标记为a,b,c,a,b分别是library project,然后依赖是c=>b=>a(c依赖于b,b依赖于a),我想对b做代码混淆,但是出现一些依赖问题。因为android的现在的这个build过程并不把在前面-code-gen生成的依赖的子library的R文件编译到这个library自己的这里,所以在混淆b的时候,会出现a中的R无法找到的问题,因为a中的R会被便已到c中,很混乱吧。事情是这样,但是错误在自己,因为对android的library和整个project的打包流程不清楚,所以造成了上面的窘境。其实根本不用混淆b工程,因为b工程的class会最终放到c中,最后在c中混淆就行了。晕了。。。
继续说打包流程,编译完了,然后就是-obfuscate混淆过程了,混淆默认只在release模式下才进行,内部有一些判断。关于混淆的一些介绍,可以参见我的上一篇博文《ProGuard总结和混淆Android代码中遇到的问题的解决方法以及寻找getSomething游戏》。然后是-dex,转.class到.dex过程。开发Android的都知道,Android没有用标准的jvm,而是自己专门为移动设备优化实现的dalvik vm。这个过程会把所有的.class文件打包到一个.dex文件中。
至此,代码build过程就结束了。全都结束了吗?还没有,Android还有编译资源,再说还没打成apk包呢。
跟资源处理有关的过程先是-crunch这个过程就是对png图片做一些压缩处理。具体怎么处理的咱就不细究了,想知道的话可以研究源代码去。然后又到了激动人心的资源打包过程-package-resources,这个过程就是用牛x的aapt,将所有res目录下的资源文件manifest等文件打包编译处理。这其中有一个对于.9.png的说明。我们知道.9.png的图片是带一圈透明画有黑点的图片。这个过程aapt会将.9.png处理,将透明带黑点写入二进制。和原来的图是不一样了的,逆向工程的时候对.9.png有很多限制。apktool的wiki上有相关说明。xda论坛上也有相关讨论。还有一点是将普通.png改为.9.png的后,ant全程打包是不会有问题的。但是aapt确实对这种情况做了抛出异常处理,所以我猜想是在crunch时做了什么处理,还未证实。
资源文件也build好了,然后对于debug或release。就是打包成apk。然后给apk签名,签名方式上两种模式有些不同处理。debug会进行debug签名,release需要你提供你的签名文件等。然后或进行zipalign操作,字节对齐。
至此,整个过程就结束了。
还有一些关于测试和安装卸载的target就不写了。
(重点过程,加粗标识了,看一遍加粗部分,基本就能知道个大概了)