头顶的风 2021-07-194,100阅读15分钟
在工作和学习中,我们除了要写一些业务代码,还要对项目的编译和打包流程有一定的认识,才能在遇到相关问题的时候能有所头绪。在这个过程中,我们往往会忽略掉资源文件是如何被添加进去的,Android的资源管理框架是一个很庞大和复杂的框架,资源编译打包的过程也很复杂和繁琐,本文就来浅谈一下Android的资源文件是如何编译和打包的吧,除了当做一个自我总结,也希望能对看到本文的你有所帮助和启发。当然了文章比较长,希望你能耐心的看完。
Android一个包中,除了代码以外,还有很多的资源文件,这些资源文件在apk打包的过程中,通过AAPT工具,打包到apk中。我们首先看一下apk的打包流程图,
概述一下这张图,打包主要有一下几个步骤:
存放原始资源文件,系统在编译时不会编译该目录下的资源文件,所以不能通过id的方式访问,如果要访问这些文件,需要指定文件名来访问。可以通过AssetManager访问原始文件,它允许你以简单的字节流的形式打开和读取和应用程序绑定在一起的原始资源文件。以下是一个从assets中读取本地的json文件的实例:
StringBuilder sb = new StringBuilder(); AssetManager assets = getAssets(); try { InputStream open = assets.open(“xxx.json”); //使用一个转换流转换为字符流进行读取 InputStreamReader inputStreamReader = new InputStreamReader(open); //缓冲字符流 BufferedReader reader = new BufferedReader(inputStreamReader); String readLine; while((readLine = reader.readLine())!=null){ sb.append(readLine); } String s = sb.toString(); } catch (IOException e) { e.printStackTrace(); }来看看一般项目中asset目录下会放些什么东东吧
存放可编译的资源文件(raw除外),编译时,系统会自动在R.java文件中生成资源文件的id,访问这种资源可以通过R.xxx.id即可。
| 目录 | 资源类型 |
|---|---|
| animator/ | 用于定义属性动画的xml |
| anim/ | 用于定义补间动画的xml(属性动画也可以在这里定义) |
| color/ | 用于颜色状态列表的xml |
| drawable/ | 位图文件(.9.png、.png、.jpg、.gif) |
| mipmap/ | 适用于不同启动器图标密度的可绘制对象文件 |
| layout/ | 用于定义用户界面布局的 XML 文件 |
| menu/ | 用于定义应用菜单(如选项菜单、上下文菜单或子菜单)的 XML 文件 |
| values/ | 包含字符串、整型数和颜色等简单值的 XML 文件 |
| XML/ | 可在运行时通过调用 Resources.getXML() 读取的任意 XML 文件。各种 XML 配置文件(如可搜索配置)都必须保存在此处。 |
| font/ | 带有扩展名的字体文件(如 .ttf、.otf 或 .ttc),或包含 元素的 XML 文件 |
| raw/ | 需以原始形式保存的任意文件 |
对资源进行编译有以下两点好处
首先来看一张图,这是resources.arsc的结构图
整个resources.arsc是由一系列的chunk组成的,每一个chunk都有一个头,用来描述chunk的元数据。
header:每个chunk的头部用来描述该chunk的元信息,包括当前chunk的类型,头大小,块大小等
Global String Pool:全局字符串池,将所有字符串放到这个池子中,大家都复用这个池子中的数据,什么样的字符串会放到这个池子中呢?所有资源的文件的路径名,以及资源文件中所定义的资源的值,所以这个池子也可以叫做资源项的值字符串资源池,包含了所有在资源包里定义的资源项的值字符串,比如下面代码中ABC就存放在这里
package数据块:
app_name就存放在这里。xxxxxxxxxxxml复制代码 <resources> <string name="app_name">ABC</string> </resources>首先看一下R文件的结构图,每一种资源文件都对应一个静态内部类,对照前面所说的res文件目录结构,其中每个静态内部类中的一个静态常量分别定义一条资源标识符
或者这样:
xxxxxxxxxxarduino复制代码 public static final class layout { public static final int main=0x7f030000; }public static final int main=0x7f030000;就表示layout目录下的main.xml文件。id中最高字节代表package的id,次高字节代表type的id,最后的字节代表当前类型中出现的序号。
0x01,另外一个应用程序资源命令空间为0x7f,所有位于 0x01到0x7f 之间的packageid都是合法的。资源文件只能以小写字母和下划线作为首字母,随后的名字中只能出现a-z或者0-9或者_.这些字符,否则会报错。
当我们在相应的res的资源目录中添加资源文件时,便会在相应的R文件中的静态内部类中自动生成一条静态的常量,对添加的文件进行索引。
在布局文件中当我们需要为组件添加id属性时,可以使用@+id/idname,+表示在R文件的名为id的内部类中添加一条记录。如果这个id不存在,则会首先生成它。
说完了资源文件的一些基本信息以后,相信你对apk包内的资源文件有了一个更加明确的认识了吧,接下来我们就来讲一讲资源文件是如何打包到apk中的,这个过程非常复杂,需要好好的理解和记忆。
Android资源打包工具在编译应用程序资源之前,会创建资源表ResourceTable,当应用程序资源编译完之后,这个资源表就包含了资源的所有信息,然后就可以根据这个资源表来生成资源索引文件resources.arsc了。
获取要编译资源的应用程序的包名、minSdkVersion等,有了包名就可以创建资源表了,也就是ResourceTable。
通常在编译一个apk包的时候,至少会涉及到两个资源包,一个是被引用的系统资源包,里面有很多系统级的资源,比如我们熟知的四大布局 LinearLayout、FrameLayout等以及一些属性layout_width、layout_height、layout_oritation等,另一个就是当前正在编译的应用程序的资源包。
在编译应用程序资源之前,aapt会创建AaptAssets对象,用来收集当前需要编译的资源文件,这些资源文件被保存在AaptAssets类的成员变量mRes中。
之前将资源添加到了AaptAssets中,这一步将资源添加到ResourceTable中,我们最后要根据这个资源表来生成resources.arsc资源索引表,回头看看arsc文件的结构图,它也有一个resourceTable。
这一步收集到资源表的资源是不包括values的,因为values资源需要经过编译后,才能添加到资源表中
values资源描述的是一些比较简单的轻量级资源,如strings/colors/dimen等,这些资源是在编译的过程中进行收集的
values资源下,除了string之外,还有其他的一些特殊资源,这些资源给自己定义一些专用的值,比如LinearLayout的orientation属性,它的取值范围为 vertical 和 horizontal,这就相当于定义了vertical和horizontal两个bag。
在编译其他非values资源之前,我们需要给之前收集到的bag资源分配资源id,因为它可能会被其它非values类资源所引用。
之前的六步为编译xml文件做好了准备,收集到了xml所需要用到的所有资源,现在可以开始编译xml文件了,比如layout、anims、animators等。编译xml文件又可以分为四个步骤
这一步会将xml文件转化为一系列树形结构的XMLNode,每一个XMLNode都表示一个xml元素,解析完成之后,就可以得到一个根节点XMLNode,然后就可以根据这个根节点来完成下边的操作
这一步为每个xml元素的属性名称都赋予资源id,比如一个控件TextView,它有layout_width和layout_height两个属性,这里就要给这些属性名称赋予一个资源id。对系统资源包来说,这些属性名称都是它定义好的一些列bag资源,在编译的时候,就已经分配好了资源id了。
对于每一个xml文件都是从根节点开始给属性名称赋予资源id,然后再递归的给每一个子节点属性名称赋予资源id,一直到每一个节点的属性名称都有了资源id为止。
这一步是上一步的进一步深化,上一步为每个属性赋值id,这一步对属性对应的值进行解析,比如对于刚才的TextView,就会对其width和height的值进行解析,可能是match_parent也可能是warp_content.
将xml文件进行扁平化处理,将其变为二进制格式,有如下几个步骤
这里生成资源符号为之后生成R文件做准备,之前的操作将所有收集到的资源文件都按照类型保存在资源表中,也就是ResourceTable对象。aapt在这里只需要遍历每一个package里面的type,然后取出每一个entry的名称,在根据其在相应的type中出现的次序,就可以计算出相应的资源id了,然后就能得到其资源符号。资源符号=名称+资源id
在这里我们将生成resources.arsc,对其生成的步骤再次进行拆解
<resources> <string name="app_name">ABC</string> </resources>就可以收集其中的属性app_name经过以上几步,资源项索引表resources.arsc就生成好了。
经过以上的几个步骤,应用程序的所有资源就编译完成了,这里就将应用程序的配置文件AndroidManifest.xml也编译为二进制文件。
到这里,我们已经知道了所有的资源以及其对应的id,然后就可以愉快的写入到R文件了,根据不同的type写到不同的静态内部类中,就像之前所描述的R文件的格式那样。
所有的资源文件都编译以及生成完之后,就可以将其打包到apk中了
终于捋完了,整个资源文件的编译打包过程真的是很复杂又很繁琐的一个过程,在阅读的过程中要时刻对照着那几张机构图才能更好地对这些文件有更清晰的认识。资源文件在Android的学习和工作中是非常重要的,很多时候这些知识会被忽略掉,但是如果有时间好好捋一捋这些知识对于自身是一个很大的提升。
最后再用一张流程图来回顾一个整个流程