AAPT - Android Asset Packaging Tool
看全称,就可知道AAPT是Android资源打包工具。�讲这个之前,是有必要简单说下Android是如何构建一个APK的。
上图是Google官方发布的一张非常经典的Apk打包流程图。�
流程概述:
总结:
按照上面aapt的地址配置好环境变量后,在终端中输入 aapt v 会得到aapt版本信息,如下:
再输入 aapt 命令会列出所有的aapt命令,下面我们一条条来使用并且分析其作用:
1.aapt l[ist] [-v] [-a] file.{zip,jar,apk}
作用:列出压缩文件(zip,jar,apk)中的目录内容。
例如:
结果如下:
可以看出来不加任何参数,aapt只是简单的罗列压缩文件中每一项的内容。
结果如下:
从图中可以看出,加上-v后,输出的内容很详细,并且以列表的形式标识出很多参数,其中表目有:
Length:原始文件的长度
Date:日期
Time:时间
Name:名称
Method:压缩方法,Deflate及Stored两种,即该Zip目录采用的算法是压缩模式还是存储模式;可以看出resources.arsc、*.png采用压缩模式,而其它采用压缩模式。
Ratio:压缩率
Size:这个是压缩省掉的大小,即如果压缩率是xx%。那Size是原始长度*(1-xx%)。
CRC-32:循环冗余校验。这个计算是有特定的算法的。
offset:zipfile中偏移量的意思
结果如下:
-a表示会详细输出压缩文件中所有目录的内容,详细到什么程度的,可以看上图,上图截取的只是很小的一部分,这部分是manifest.xml文件的所有数据,可以看出来基本上所有的manifest信息都列了出来。
2.aapt d[ump] [--values] [--include-meta-data] WHAT file.{apk} [asset [asset ...]]
作用:通过参数配置可以dump apk中各种详细信息。
Print the contents of the resource table string pool in the APK
即打印apk中所有string资源表
结果:
不太了解这个结果是什么具体的意思,猜测应该是序列化的string字段。
Print the label and icon for the app declared in APK.
结果:
查看APK中的配置信息,包括包名,versionCode,versionName,platformBuildVersionName(编译的时候系统添加的字段,相当targetSdkVersionCode的描述)等。同事该命令还列出了manifest.xml的部分信息,包括启动界面,manifest里配置的label,icon等信息。还有分辨率,时区,uses-feature等信息。
较简单,输出APK中使用到的权限信息。
Print the resource table from the APK
结果:
输出了apk中所有的资源信息,从这里也可以看出来aapt打包时也包含了android系统很多资源。并且这里也发现,系统通过标准的aapt构建出来的资源绝大部分的资源id都是0x7f开头的,这个也是和我们在R.java文件中看到的资源编号是对应起来的。
configurations 官方解释:Print the configurations in the APK
结果:
可以看出该命令输出了apk所有的资源目录,仅仅是目录,里面有语言,分辨率,夜间模式相关的数据。
Print the compiled xmls in the given assets
结果:
该命令直接反编译除了apk中某一个xml布局文件的组织结构。命令需要两个参数 第一是apk的地址 第二后面是apk中某个编译好的xml的相对路径地址
Print the strings of the given compiled xml assets
结果:
从字面解释,输出xml文件中所有的string信息。看结果,实际上并没看出来什么特殊的,也并不是简单的string信息,猜测可能是索引吧。
3.aapt p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml]
官方解释:
Package the android resources. It will read assets and resources that are supplied with the -M -A -S or raw-files-dir arguments. The -J -P -F and -R options control which files are output.
android 编译资源打包资源文件的命令。
该命令也是aapt最核心、最复杂的命令。这边我只尝试了一下简单的实践,讲工程的资源编译到一个包里。下面是命令
输出了一个apk文件,解压以后文件格式如下:
这个apk文件除了没有代码dex,资源都在的。这个是aapt打包的关键步骤之一,还有一个步骤就是把资源文件编译成R.java,供程序调用。命令如下:
4.aapt r[emove] [-v] file.{zip,jar,apk} file1 [file2 ...]
官方解释:
Delete specified files from Zip-compatible archive
就是从一个zip archive文件中删除一个文件。较简单,不做实例了。
5.aapt a[dd] [-v] file.{zip,jar,apk} file1 [file2 ...]
官方解释:
Add specified files to Zip-compatible archive.
�即在一个zip包中添加一个一个指定的文件。
6.aapt c[runch] [-v] -S resource-sources ... -C output-folder ...
官方解释:
Do PNG preprocessing on one or several resource folders and store the results in the output folder.
对多个或者单个资源文件夹进行处理,并且将结果保存在输出文件夹中
6.aapt s[ingleCrunch] [-v] -i input-file -o outputfile
官方解释:
Do PNG preprocessing on a single file
预处理一个文件
首先下载Android源码
Android Source
�我这边下载的是Android 6.0的源码
AAPT代码地址:***/frameworks/tools/aapt/目录下。
我们这里以一个命令来跟踪源码的流程,即用aapt是如何构建一个R.java的,命令格式如下:
实践:
运行该命令后,在配置的R.java目录下 会生成一个自己app包名的目录,里面会生成R.java文件,如下:
R.java里会生成这样的索引ID类,都是以0x7f开头
好,既然我们知道了输入以及输出,那让我们来分析这块的代码。
PS:由于某些函数较长,不会贴出所有的源码
当通过所有的匹配规则后,该函数实际调用是 handleCommand(&bundle)。 至于执行什么命令说白了也是命令指定的,-p 设置的command参数是kCommandPackage。
注释:
3-1:这个过程主要是对资源配置信息进行校验,Android应用程序资源的组织方式有18个维度,包括mcc(移动国家代码)、mnc(移动网络代码)、local(语言区域)等。改代码的主要实现是在 /framewors/base/tools/aapt/AaptConfig.cpp 里的parse方法。解析完成的数据,会丢给WeakResourceFilter类中的一个向量成员mConfigs。关于这块的详细解释,可以参考官网或者老罗的博客,地址如下:
https://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources
Android资源管理框架(Asset Manager)简要介绍和学习计划
ps:改函数较长,截取部分代码分步解析
首先解析manifest文件,调用的是parsePackage函数,解析之前,manifest被封装成一个AaptGroup对象。
没有具体细看里面的代码,说下具体思路,通过传进来的形参AaptGroup拿到具体的AaptFile对象。在调用公共类的parseXmlResource解析xml文件得到具体数据后,存放在对象ResXmlTree中。parseXMLResource函数在类 frameworks/base/tools/aapt/XMLNode.cpp 中。有兴趣的可以自己去读下,这里就不贴了。解析玩manifest.xml后,我们继续buildResources的分析。
这段代码的目的主要是收集当前编译的资源需要依赖的的资源并且存放在ResourceTable这个数据结构中。这边简单介绍一下ResourceTable这个数据结构,首先我们得知道R.java里面的资源标识id的构成,比方说 0x7f040002 其中0x7f表示是packageID,也就是上面的packageType,它是一个命名空间,限定资源的来源,7f表明是当前应用程序的资源,系统的资源是以0x01开头。04 表示TypeID。资源的类型animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID。最后四位是EntryID,指的是每一个资源在起对应的TypID中出现的顺序。
而ResouceTable里面存储的最核心的元素就是这个id的区分。
收集完成当前应用依赖的资源以后,就要编译当前应用自己的资源。这里由于代码太过于复杂,本人也没有完全看懂,就不贴了,逻辑基本上就是通过命令的输出,一个个的编译资源文件和png。然后存储在一个xml文件中,为后面生成R.java文件中做准备。实际上前面也有提到,所有的资源都会存在ResouceTable这个数据结构中,做完编译工作以后,只需要去遍历这个向量表,然后对里面的packageID,typeID,EvtryID进行拼接,就可以得到我们所熟悉的 0x7f040002这种资源ID。ResouceTable的构造函数也可以看出来里面的过程:
完成上述的编译资源的工作以后,细心的读者就会发现,对于manifest.xml一直都是读取里面的配置信息,并没有编译,所以最后一步就是把manifest.xml编译成二进制文件。这个就不贴出源码了。
最后一步,将上述的编译结果输出到R.java和Apk中。其中还会输出混淆文件,java符号表等。
在第三节我们讲AAPT是如何编译资源并且生成R.java文件的,也提到R.java中资源ID的含义,在组件化框架中,由于组件和宿主分开编译,为了防止组件的资源ID和宿主的资源ID冲突,所以就需要修改AAPT源码。基本思路就是每个组件分配一个不一样的ID,宿主的ID是以0x7f开头,组件的ID是0x**开头,这样就避免冲突。
可以看出如果不修改AAPT源码重新构建,就会导致组件之间或者组件与宿主之间的ID冲突。所以就会有如下模型: