深入Android系统(八)Android的资源管理

Android的优势之一是它几乎能运行在任何尺寸的设备上,为了能让同一个apk在不同设备上正常运行,Android设计了一套资源管理系统来完成目标。

Android并不是简单地将UI布局和图片进行扩大和缩小来匹配不同配置的设备,而是通过复杂的资源定义方式来保证每种设备都可以有对应的资源文件,从而让用户体验最佳。

资源系统简介

Android 应用适应不同设备的方法是尽量为每种类型的设备提供一套资源。理论上虽然可以这样做,但实际上却行不通,我们只能为常见的几种设备类型提供完整的资源,否则应用的占用空间会膨胀到无法接受的程度

常用术语和单位

大部分来自官网,应用资源概览

Android中常用的单位:

  • dpi:屏幕密度,即每英寸的像素点数。160 dpi表示屏幕每英寸包含160个像素点
  • px:像素,1 px表示一个物理的像素点。px不被推荐使用,但是如果需要通过像素点来控制UI,也可以使用。
  • dpdp是一个虚拟像素单位,1 dp约等于中密度屏幕(160dpi;“基准”密度)上的1像素。对于其他每个密度,Android会将此值转换为相应的实际像素数。
  • spsp多用于表示字体大小上。spdp概念相似。区别是Android在系统配置中定义一个scale值,spdp的换算关系是sp=dp*scale,通常scale值为1。(官方说法:默认情况下,sp单位与dp大小相同,但它会根据用户的首选文本大小来调整大小。)

Android把屏幕尺寸归为4类:

  • small:尺寸类似于低密度 VGA 屏幕的屏幕。小屏幕的最小布局尺寸约为 320x426 dp。例如,QVGA 低密度屏幕和 VGA 高密度屏幕。
  • normal:尺寸类似于中等密度 HVGA 屏幕的屏幕。标准屏幕的最小布局尺寸约为 320x470 dp。例如,WQVGA 低密度屏幕、HVGA 中等密度屏幕、WVGA 高密度屏幕。
  • large:尺寸类似于中等密度 VGA 屏幕的屏幕。大屏幕的最小布局尺寸约为 480x640 dp。例如,VGA 和 WVGA 中等密度屏幕。
  • xlarge:明显大于传统中等密度 HVGA 屏幕的屏幕。超大屏幕的最小布局尺寸约为 720x960 dp。在大多数情况下,屏幕超大的设备体积太大,不能放进口袋,最常见的是平板式设备。此项为 API 级别 9 中的新增配置。

Android把屏幕密度(dpi)分为:

  • ldpi:低密度屏幕;约为 120dpi。
  • mdpi:中等密度(传统 HVGA)屏幕;约为 160dpi。
  • hdpi:高密度屏幕;约为 240dpi。
  • xhdpi:超高密度屏幕;约为 320dpi。此项为 API 级别 8 中的新增配置
  • xxhdpi:绝高密度屏幕;约为 480dpi。此项为 API 级别 16 中的新增配置
  • xxxhdpi:极高密度屏幕使用(仅限启动器图标);约为 640dpi。此项为 API 级别 18 中的新增配置
  • nodpi:可用于您不希望为匹配设备密度而进行缩放的位图资源。
  • tvdpi:密度介于 mdpi 和 hdpi 之间的屏幕;约为 213dpi。此限定符并非指“基本”密度的屏幕。它主要用于电视,且大多数应用都不使用该密度 — 大多数应用只会使用 mdpi 和 hdpi 资源,而且系统将根据需要对这些资源进行缩放。
  • anydpi:此限定符适合所有屏幕密度,其优先级高于其他限定符。这非常适用于矢量可绘制对象。此项为 API 级别 21 中的新增配置

系统资源定义

对于下面的Layout资源定义:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
LinearLayout>

上面的定义中有各种各样的符号,例如LinearLayoutlayout_widthorientation等,这些符号在哪里定义的呢?语法规则优势什么呢?

Android的资源系统并不想看上去那么简单,Android利用了xml定义了一套完整的资源语言,我们来具体看下

定义属性

Framework资源目录frameworks/base/core/res/res/values的目录下,有一个attrs.xml文件,在这个文件里,Android定义了资源的属性值,以LinearLayout为例,相关的定义如下:

    <declare-styleable name="LinearLayout">
        <attr name="orientation" />
        <attr name="gravity" />
        <attr name="baselineAligned" format="boolean" />
        <attr name="baselineAlignedChildIndex" format="integer" min="0"/>
        <attr name="weightSum" format="float" />
        <attr name="measureWithLargestChild" format="boolean" />
        <attr name="divider" />
        <attr name="showDividers">
            <flag name="none" value="0" />
            <flag name="beginning" value="1" />
            <flag name="middle" value="2" />
            <flag name="end" value="4" />
        attr>
        <attr name="dividerPadding" format="dimension" />
    declare-styleable>

从文件中不难看出,LinearLayout中所使用的各种属性正是在这里定义的。不但定义了属性的名称,还有属性值的格式也做了定义

对于UI的基本元素widget而言,最重要的是获得预先定义好的各种属性值。于是在attrs.xml文件中,通过declare-styleable的方式定义了一套属性的集合。我们再看下LinearLayout类的构造方法:

    public LinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        ......
        final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.LinearLayout, defStyleAttr, defStyleRes);
        int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1);
        if (index >= 0) {
            setOrientation(index);
        }
        ......
        a.recycle();
    }
  • 在构造函数中,LinearLayout调用方法obtainStyledAttributes()来创建一个属性集合
  • a.getInt()用来获取属性
    • 第一个参数是com.android.internal.R.styleable.LinearLayout,正是attrs.xml文件中定义的属性集合名称
    • 还有一个参数是缺省值-1,如果在编写layout文件时必须给每个widget的所有属性都赋值,这会变得很繁琐,因此在读取每个属性时,代码中都会给出一个缺省值

给属性赋值

LinearLayout的构造方法中有一个defStyleAttrdefStyleRes用来指定使用的stylestyle就是一些预定义的属性值,例如:

    <style name="Widget.Button">
        "background">@drawable/btn_default
        "focusable">true
        "clickable">true
        "textAppearance">?attr/textAppearanceSmallInverse
        "textColor">@color/primary_text_light
        "gravity">center_vertical|center_horizontal
    style>

这段xml定义了一个名为Widget.Buttonstyle。使用style的方式是在layout定义中加入下面的语句:

style="@style/Widget.Button"

对于style的命名,为什么要用.分割呢?Android用这种方式表示一种继承关系。Widget.Button的含义是当前style继承了Widget的所有属性值

除了通过名字来表示继承关系外,还可以通过