前言:前天某个公众号突然给我推了一篇文章,里面给出了Android打包编译知识的思维导图,于是觉得作为Android开发者有必要了解一下这个流程。即使现在的知识储备不够,但是人嘛,总是在学习中不断成长的。一步一步,不会的努力学会,总有一天可以融会贯通。关于Android打包和编译这一块,网上有许多优秀的博客,大家可以自行寻找,比较有名的是罗升阳大神写的一篇博客:Android应用程序资源的编译和打包过程分析,不过大神写的博客真的很长很详细,详细到我总是看博客的时候开小差~写这篇博客也没想比别人写的好,只是做一个记录,加深自己的理解。好了,贴上那张图:
好吧,知识点多的不要不要的,准备一一啃一遍,希望能坚持下来。走你~
Android中的资源文件
Android开发中,项目中有一个res文件夹,专门用来存放应用中可能用到的资源文件,诸如布局、图片、音频、菜单、动画等等。Android内所有支持的资源文件夹如下:
目录 | 资源类型 |
---|---|
animator | 用于定义属性动画的 XML 文件。 |
anim | 定义渐变动画的 XML 文件。(属性动画也可以保存在此目录中,但是为了区分这两种类型,属性动画首选 animator/ 目录。) |
color | 用于定义颜色状态列表的 XML 文件。 |
drawable | 用于存放图片或者可绘制对象的xml文件 |
mipmap | 适用于不同启动器图标密度的可绘制对象文件。 |
layout | 用于存放布局文件。 |
menu | 用于定义菜单的xml文件。 |
raw | 以原始形式保存的任意文件。 |
values | 用于存放定义了数组、颜色、尺寸、字符串值、样式的xml文件。 |
xml | 可以在运行时通过调用 Resources.getXML() 读取的任意 XML 文件。各种 XML 配置文件(如可搜索配置)都必须保存在此处。 |
当然还有一个assets文件夹也可以存放特定的资源文件,不过不在res文件夹下。需要注意的是,切勿将资源文件直接保存在 res/ 目录内,这会导致出现编译错误。
提供备用资源
我们在开发的时候,最常见的就是存放图片的文件夹了,比如drawable-hdpi、drawable-ldpi等等,提供多个文件夹并存放相同文件名不同大小的图片是为了Android在运行时,根据设备的大小来加载合适的资源。drawable-hdpi中的drawable是资源文件夹名称,至于后面的hdpi、ldpi等则是限定符,限定这个文件夹下的资源是提供给特定dpi设备的。我们常见的就是屏幕dpi的限定符,那Android中还有哪些限定符呢?这里列出了所有的限定符:
配置 | 限定符值 | 说明 |
---|---|---|
MCC 和 MNC | 示例:mcc310、mcc310-mnc004、mcc208-mnc00等等 | 移动国家代码 (MCC),(可选)后跟设备 SIM 卡中的移动网络代码 (MNC)。例如,mcc310 是指美国的任一运营商,mcc310-mnc004 是指美国的 Verizon 公司,mcc208-mnc00 是指法国的 Orange 公司。 |
语言和区域 | 示例:en、fr、en-rUS、fr-rFR、fr-rCA等等 | 用以区分语言的限定符 |
布局方向 | ldrtl、ldltr | 应用的布局方向。ldrtl 是指“布局方向从右到左”。ldltr 是指“布局方向从左到右”,这是默认的隐式值。它适用于布局、图片或值等任何资源。 |
smallestWidth | 示例:sw320dp、sw600dp、sw720dp等等 | 屏幕的基本尺寸,由可用屏幕区域的最小尺寸指定。 |
可用宽度 | 示例:w720dp、w1024dp等等 | 指定资源应该使用的最小可用屏幕宽度,以 dp 为单位,由 值定义。在横向和纵向之间切换时,为了匹配当前实际宽度,此配置值也会随之发生变化。 |
可用高度 | 示例:w720dp、w1024dp等等 | 指定资源应该使用的最小可用屏幕高度,以“dp”为单位,由 值定义。 在横向和纵向之间切换时,为了匹配当前实际高度,此配置值也会随之发生变化。 |
屏幕尺寸 | small、normal、large、xlarge | 屏幕的尺寸。small:尺寸类似于低密度 QVGA 屏幕的屏幕。小屏幕的最小布局尺寸约为 320x426 dp 单位。例如,QVGA 低密度屏幕和 VGA 高密度屏幕。 normal:尺寸类似于中等密度 HVGA 屏幕的屏幕。标准屏幕的最小布局尺寸约为 320x470 dp 单位。例如,WQVGA 低密度屏幕、HVGA 中等密度屏幕、WVGA 高密度屏幕。 large:尺寸类似于中等密度 VGA 屏幕的屏幕。 大屏幕的最小布局尺寸约为 480x640 dp 单位。 例如,VGA 和 WVGA 中等密度屏幕。 xlarge:明显大于传统中等密度 HVGA 屏幕的屏幕。超大屏幕的最小布局尺寸约为 720x960 dp 单位。在大多数情况下,屏幕超大的设备体积过大,不能放进口袋,最常见的是平板式设备。 API 级别 9 中的新增配置。 |
屏幕纵横比 | long、notlong | long:宽屏,如 WQVGA、WVGA、FWVGA notlong:非宽屏,如 QVGA、HVGA 和 VGA |
圆形屏幕 | round、notround | round:圆形屏幕,例如圆形可穿戴式设备 notround:方形屏幕,例如手机或平板电脑。 此项为 API 级别 23 中新增配置。 |
屏幕方向 | port、land | port:设备处于纵向(垂直) land:设备处于横向(水平) |
UI 模式 | car、desk、television、appliance 、watch | car:设备正在车载手机座上显示。 desk:设备正在桌面手机座上显示。 television:设备正在电视上显示,为用户提供“十英尺”体验,其 UI 位于远离用户的大屏幕上,主要面向方向键或其他非指针式交互。 appliance:设备用作不带显示屏的装置。 watch:设备配有显示屏,戴在手腕上。 此项为 API 级别 8 中新增配置,API 13 中新增电视配置,API 20 中新增手表配置。 |
夜间模式 | night、notnight | night:夜间 notnight:白天 |
屏幕像素密度 (dpi) | ldpi mdpi、hdpi、xhdpi、xxhdpi等等。 | ldpi:低密度屏幕;约为 120dpi。 mdpi:中等密度(传统 HVGA)屏幕;约为 160dpi。 hdpi:高密度屏幕;约为 240dpi。 xhdpi:超高密度屏幕;约为 320dpi。此项为 API 级别 8 中新增配置。 xxhdpi:超超高密度屏幕;约为 480dpi。此项为 API 级别 16 中新增配置。 |
触摸屏类型 | notouch、finger | notouch:设备没有触摸屏。 finger:设备有一个专供用户通过手指直接与其交互的触摸屏。 |
键盘可用性 | keysexposed、keyshidden、keyssoft | keysexposed:设备具有可用的键盘。 keyshidden:设备具有可用的硬键盘,但它处于隐藏状态,且设备没有启用软键盘。 keyssoft:设备已经启用软键盘(无论是否可见)。 |
主要文本输入法 | nokeys、qwerty、12key | nokeys:设备没有用于文本输入的硬按键。 qwerty:设备具有标准硬键盘(无论是否对用户可见)。 12key:设备具有 12 键硬键盘(无论是否对用户可见)。 |
导航键可用性 | navexposed、navhidden | navexposed:导航键可供用户使用。 navhidden:导航键不可用(例如,位于密封盖子后面)。 |
主要非触摸导航方法 | nonav、dpad、trackball、wheel | nonav:除了使用触摸屏以外,设备没有其他导航设施。 dpad:设备具有用于导航的方向键。 trackball:设备具有用于导航的轨迹球。 wheel:设备具有用于导航的方向盘(不常见)。 |
平台版本(API 级别) | 示例:v3、v4、v7等等 | 设备支持的 API 级别。例如,v1 对应于 API 级别 1(带有 Android 1.0 或更高版本系统的设备),v4 对应于 API 级别 4(带有 Android 1.6 或更高版本系统的设备) |
好吧,真的很多,更详细的请参照Android官方文档:Android官方文档
这些限定符的用法也很简单,无非是在资源文件夹名称后面加上限定符。比如drawable-notouch就代表这个文件夹下的资源是提供给没有触摸屏的设备的。资源文件夹后面也可以添加多个限定符,例如drawable-round-notouch表示圆形的没有触摸屏的设备,限定符中间用短横线分割就好。
注意:
- 文件夹名称后面的限定符顺序是要严格遵照上表中限定符出现的顺序的,例如drawable-round-notouch,如果写成drawable-notouch-round,就是错误的。
- 不能嵌套备用资源目录。如 res/drawable/drawable-en/。
- 每种限定符只支持一个值,比如drawable-notouch-finger则是错误的,因为出现了两个触摸类型限定符。
创建别名资源
假设有两个图片资源文件夹引用了同一张图片,常规的做法是在两个文件夹下都放一张相同的图片。然后外部引用。但是假如图片很大呢?放两张图岂不是很浪费空间?Android中有一种解决办法,就是创建别名资源。比如,有两个文件夹用到了相同的图片icon.png。那么就可以把这个icon.png放到drawable文件夹下,两个需要使用的文件夹中不放置这张图片,而是放置一个引用该图片的xml文件,如下:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/icon" />
这样即使icon.png很大,也只需要在drawable中放置一份图片即可,其它的用xml文件的方式引用,要比直接放置图片来的小的多得多。
提供默认资源
要使应用支持多种设备配置,则务必为应用使用的每种资源类型提供默认资源。 例如,如果应用支持多种语言,要始终包含不带语言和区域限定符的 values/ 目录(用于保存字符串)。相反,否则在语言设置不支持您的字符串的设备上运行应用时,应用将会崩溃。 但是,只要提供默认 values/ 资源,应用就会正常运行(即使用户不理解该语言,这也总比崩溃要好)。
同样,如果您根据屏幕方向提供不同的布局资源,则应选择一个方向作为默认方向。 例如,不要在 layout-land/ 和 layout-port/ 中分别提供横向和纵向的布局资源,而是保留其中之一作为默认设置,例如:layout/ 用于横向,layout-port/ 用于纵向。
因此,为了提供最佳设备兼容性,请始终为应用正确运行所必需的资源提供默认资源。 然后,使用配置限定符为特定的设备配置创建备用资源。
Android查找最佳匹配资源
开发时我们会提供多个资源文件夹,那么Android系统是如何寻找到契合设备的资源文件夹并使用的呢?其实,Android系统有一套资源管理机制和算法用于寻找最佳匹配资源:
- 首先淘汰和设备配置冲突的资源文件。
淘汰和设备配置冲突的资源文件,比如当前设备是英文环境,则drawable-en是可用的资源文件,其它非英文环境的资源会被淘汰。例如drawable-fr。 - 遍历限定符列表
从限定符列表依次向下(从MCC开始)开始遍历,查看所有的资源文件中是否含有当前的限定符,如果没有,则继续向下遍历限定符列表;如果有,则淘汰所有不含此限定符的资源文件。
需要注意的是:一定是按照限定符列表依次向下遍历的,因为限定符拥有不同的优先权。上面的优先权大于下面的优先权。 哪怕一个目录有多个限定符,一个目录只有一个限定符,只要拥有一个限定符的优先权大于拥有多个限定符的优先权,那么拥有多个限定符的目录依旧会被淘汰,这就是优先权的力量。
直到最后只剩一个可用目录,这个目录就是我们app运行时用到的资源目录。这就是Android资源管理的机制和算法,具体的流程图引用官网的:
举个例子吧,比如一台手机的配置信息如下:
语言区域 = en-GB
屏幕方向 = port
屏幕像素密度 = hdpi
触摸屏类型 = notouch
主要文本输入法 = 12key
刚开始有如下目录:
drawable/
drawable-en/
drawable-fr-rCA/
drawable-en-port/
drawable-en-notouch-12key/
drawable-port-ldpi/
drawable-port-notouch-12key/
那么首先会淘汰和设备语言环境不符合的目录drawable-fr-rCA/,则剩下如下目录:
drawable/
drawable-en/
drawable-en-port/
drawable-en-notouch-12key/
drawable-port-ldpi/
drawable-port-notouch-12key/
然后会淘汰所有不含语言限定符的目录,则drawable/、drawable-port-ldpi/、drawable-port-notouch-12key/三个目录会被淘汰。剩下:
drawable-en/
drawable-en-port/
drawable-en-notouch-12key/
根据限定符列表,屏幕方向在触摸类型前面,所以会淘汰掉drawable-en/、drawable-en-notouch-12key/,最后只剩下一个:
drawable-en-port/
这就是最终app在该设备上运行时使用的资源目录。好了,关于Android资源管理机制就写到这里,这篇博文也是出自官方文档,只不过加入了我自己的一些理解,如有错误,欢迎批评指正,完~