想象你开发了一个多语言、多屏幕适配的 Android 应用。你的资源(图片、文字、布局)就像一堆不同语言、不同尺寸的“货物”。为了让 App 在全球各种手机上都能正确显示,Android 设计了一套精密的“资源管理系统”。
第一部分:资源编译打包 - “翻译官” aapt 的工作 (编译时)
当你在 Android Studio 点击“Build”时,幕后英雄 aapt (Android Asset Packaging Tool) 或它的升级版 aapt2 就开始工作了。它的任务是把你的“人类可读资源”翻译成“机器高效格式”并打包进 APK。
-
拆解包裹清单 (解析 AndroidManifest.xml)
-
aapt先看你的AndroidManifest.xml(<manifest package="com.example.myapp">)。 -
目的: 获取你的 App 身份证号 - 包名 (Package Name),比如
com.example.myapp。这个包名对应资源 ID 的高位字节0x7f。 -
代码逻辑 (简化):
java Copy // aapt 内部逻辑 (伪代码) String packageName = parsePackageNameFromManifest(manifestFile); ResourceTable resourceTable = new ResourceTable(packageName); // 创建资源表,包名是身份证
-
-
请教字典 (添加被引用资源包)
- 你的布局文件里用了
android:orientation="vertical",这个vertical是系统定义的资源。 aapt会引用系统资源包 (framework-res.apk里的resources.arsc) 作为字典,理解这些系统定义的名称和 ID。- 目的: 知道
android:orientation对应的资源 ID 是0x010100c4。
- 你的布局文件里用了
-
收集货物清单 (收集资源文件)
-
aapt扫描你的res/目录:res/drawable-hdpi/icon.png,res/drawable-mdpi/icon.png-> 同一资源icon的不同配置。res/layout/main.xml,res/layout-land/main.xml-> 同一布局main的不同配置(横屏)。res/values/strings.xml-> 包含文字资源。res/values/attrs.xml-> 包含自定义属性。
-
目的: 把资源按类型 (Type) 和名称 (Name) 分组,并记录它们的配置限定符 (Qualifiers)(如
hdpi,land,en)。
-
-
简单货物贴标签 (添加非 values 资源)
- 图片 (
drawable)、布局 (layout) 等文件路径直接被记录到资源表 (ResourceTable)。 - 目的: 知道
icon这个资源在hdpi配置下对应文件res/drawable-hdpi/icon.png。
- 图片 (
-
文字和规则编译 (编译 values 类资源)
-
字符串 (strings.xml):
xml Copy <string name="app_name">My App</string>aapt把"app_name"作为名字,"My App"作为值记录到资源表。 -
自定义属性 (attrs.xml):
xml Copy <attr name="custom_orientation" format="enum"> <enum name="vertical" value="0"/> <enum name="horizontal" value="1"/> </attr>aapt把custom_orientation定义为一个枚举类型属性,并记录其可能值 (vertical=0,horizontal=1)。这称为一个 Bag资源。 -
代码逻辑 (伪代码):
java Copy // 处理 strings.xml ResourceEntry stringEntry = resourceTable.addEntry("string", "app_name"); stringEntry.setValue(configuration, "My App"); // configuration 可能是默认配置 // 处理 attrs.xml (自定义属性是复杂资源-Bag) ResourceEntry attrEntry = resourceTable.addEntry("attr", "custom_orientation"); BagResource bag = new BagResource(); bag.addMap(ResourceHelper.attr_enum, 0); // 系统定义的 enum 类型标记 bag.addMap(R.id.vertical, 0); // R.id.vertical 是编译时生成的资源ID bag.addMap(R.id.horizontal, 1); // R.id.horizontal 是编译时生成的资源ID attrEntry.setComplexValue(bag);
-
-
分配唯一身份证 (给资源分配 ID)
-
aapt为每个资源项分配一个唯一的 32 位资源 ID (0xPPTTEEEE)- PP (Package ID):
0x7f(你的 App) 或0x01(系统资源)。 - TT (Type ID): 资源类型编号 (
0x01=anim,0x02=color,0x03=drawable,0x04=layout,0x05=string 等)。 - EEEE (Entry ID): 该类型下资源的顺序号。
- PP (Package ID):
-
例子:
icon(drawable 类型) 可能是第 1 个资源 ->0x7f030001。app_name(string 类型) 可能是第 1 个字符串 ->0x7f050001。custom_orientation(attr 类型) 可能是第 1 个属性 ->0x7f010000。
-
-
布局文件编译 (编译 XML 资源) - 重点!
-
以
res/layout/main.xml为例:xml Copy <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" ...> <Button android:id="@+id/my_button" android:text="@string/start"/> </LinearLayout> -
aapt做以下转换:-
属性名变 ID:
android:orientation->0x010100c4(系统资源ID)。 -
属性值解析/转换:
"vertical"-> 验证是合法值,并记录为整数 (通常是预定义常量)。"@+id/my_button"-> 创建新 ID0x7f020001并赋给这个按钮。"@string/start"-> 引用已存在的字符串 ID0x7f050002。
-
文本压缩成二进制格式 (压平 XML):
- 创建字符串池: 收集所有出现的字符串 (
"LinearLayout","Button","orientation","id","text","http://schemas.android.com/apk/res/android"等)。 - 替换文本为索引: 文件里的
"Button"变成指向字符串池位置5的索引。 - 结构化数据: 使用高效的二进制结构 (
ResXMLTree_node,ResXMLTree_attrExt,Res_value) 描述元素、属性及其值。属性名直接用资源ID (0x010100c4),属性值用Res_value(包含数据类型TYPE_STRING和索引,或者TYPE_INT_DEC和数值)。 - 最终效果: 文本
main.xml变成体积小、解析飞快的二进制文件main.xml.flat(或在 APK 中)。
- 创建字符串池: 收集所有出现的字符串 (
-
-
-
生成资源索引目录 (生成 resources.arsc) - 核心仓库蓝图!
-
这是资源系统的核心数据库文件
resources.arsc。 -
aapt将所有信息按包 (Package)、类型 (Type)、配置 (Configuration) 组织:- 类型字符串池:
"drawable","layout","string","attr"... - 资源名称字符串池:
"icon","main","app_name","custom_orientation","my_button"... - 资源值字符串池 (非必须): 字符串资源的值 (
"My App","Start")。 -
ResTable_package: 描述你的资源包 (包名0x7f)。 -
ResTable_typeSpec: 描述每种资源类型下有哪些资源项 (icon存在),以及它们的配置差异性 (比如icon有hdpi,mdpi版本)。 -
ResTable_type: 描述特定配置下某个类型资源的具体信息。比如drawable类型在hdpi配置下:icon-> 文件路径res/drawable-hdpi/icon.png。string类型在默认配置下:app_name-> 值"My App"。
- 类型字符串池:
-
目的: 让运行时系统能根据 资源ID + 设备配置 快速找到最匹配的资源!
-
-
生成资源符号表 (生成 R.java)
-
aapt生成R.java文件:java Copy public final class R { public static final class drawable { public static final int icon = 0x7f030001; // 对应资源ID } public static final class string { public static final int app_name = 0x7f050001; } public static final class id { public static final int my_button = 0x7f020001; } ... // 其他类型 } -
目的: 你在代码 (
Activity.java) 中能用R.drawable.icon、R.string.app_name、findViewById(R.id.my_button)来引用资源,编译器将其替换为对应的整数 ID (0x7f030001)。
-
-
打包进 APK
aapt将所有编译好的资源文件 (*.flat或原始assets/文件)、resources.arsc、编译好的AndroidManifest.xml一起打包进最终的.apk文件。
第二部分:资源查找 - “智能仓库管理员” AssetManager/Resources 的工作 (运行时)
当你的 App 在手机上运行时,系统会为它创建 Resources 和 AssetManager 对象。它们就像高效的仓库管理员,根据 resources.arsc 蓝图,快速找到当前设备最合适的资源。
-
仓库开张 (初始化)
-
ActivityThread在创建应用时,会初始化Resources和AssetManager。 -
关键代码:
java Copy // 在 ActivityThread.handleBindApplication() 或类似位置 AssetManager assetManager = new AssetManager(); assetManager.addAssetPath(apkPath); // 加载 APK 里的资源 Resources resources = new Resources(assetManager, displayMetrics, configuration); ContextImpl context = new ContextImpl(...); context.setResources(resources); // 关联给应用上下文
-
-
查询请求 (例如 findViewById)
-
当你在代码中调用
getResources().getDrawable(R.drawable.icon)或findViewById(R.id.my_button)时:R.drawable.icon/R.id.my_button是一个编译时常量 (如0x7f030001)。- 请求被传递到
Resources对象。
-
-
智能仓库管理员查库 (资源查找四步曲)
Resources和AssetManager合作,根据 资源ID (0x7f030001) 和 当前设备配置 (语言、屏幕密度、方向等) 查找资源:-
Step 1: 找仓库 (根据 Package ID)
-
拆解资源ID
0x7f030001:0x7f-> 包ID (你的 App)。0x03-> 类型ID (drawable)。0x0001-> 资源项ID (icon)。
-
在
resources.arsc中找到对应包 (ResTable_package) 的数据块。
-
-
Step 2: 找货架区 (根据 Type ID)
- 在包数据块中,根据类型ID
0x03找到drawable类型的 类型规范 (ResTable_typeSpec) 和 当前配置下最匹配的类型资源项数据块 (ResTable_type)。 - 类型规范 (
ResTable_typeSpec): 告诉管理员icon这个资源有多个配置版本 (hdpi,mdpi等)。 - 类型资源项数据块 (
ResTable_type): 存储了在当前设备配置下,drawable类型所有资源项的具体信息。
- 在包数据块中,根据类型ID
-
Step 3: 精准定位 (根据 Entry ID 和配置匹配)
-
在找到的
ResTable_type数据块中,根据资源项ID0x0001(Entry ID) 找到icon对应的条目 (ResTable_entry)。 -
该条目直接指向资源值:
- 对于
drawable: 指向APK内的文件路径res/drawable-<density>/icon.png。 - 对于
string: 直接存储字符串值"My App"。 - 对于
id: 仅表示一个ID存在 (用于findViewById)。
- 对于
-
-
Step 4: 取货/展示
-
Resources根据得到的值:- 如果是文件路径 (
res/drawable-xxhdpi/icon.png),调用AssetManager.openNonAsset()读取文件内容,可能还需要解码 (如 BitmapFactory.decodeStream)。 - 如果是字符串 (
"My App"),直接返回。 - 如果是 ID,
findViewById就用这个 ID 在视图树里查找对应的 View。
- 如果是文件路径 (
-
-
-
智能匹配算法 (选择最合适的配置)
当icon有多个版本 (hdpi,xxhdpi,zh,en) 时,AssetManager/Resources如何选择?-
算法核心: 消除法 + 优先级。
-
过程 (简化):
-
排除冲突: 丢弃与当前设备配置冲突的目录 (如设备语言是英文
en,就排除values-zh目录)。 -
优先级维度 (从高到低):
MCC(移动国家码) ->MNC(移动网络码) ->语言/区域->屏幕方向->屏幕像素密度 (dpi)->触摸屏类型->键盘状态... (完整列表见 Android 文档)。
-
逐级筛选:
- 从最高优先级维度 (
MCC) 开始检查。 - 如果当前设备有该维度的配置 (比如
MCC=310美国),就检查资源目录中是否有匹配该维度的 (drawable-mcc310)。有,则保留这些目录;没有,则跳过该维度。 - 进入下一优先级维度 (
MNC),重复检查。
- 从最高优先级维度 (
-
找到唯一或最接近: 不断筛选,直到找到最匹配当前设备所有配置的那个资源目录下的资源文件。如果筛选后还有多个,选择包含限定符最多的那个 (最具体)。如果还是找不到,用默认 (
drawable/,values/) 目录下的。
-
-
例子: 设备语言=
en-GB(英国英语),屏幕方向=portrait(竖屏),密度=xxhdpi。- 候选资源目录:
drawable-en-port-xxhdpi/icon.png>drawable-en-port/icon.png>drawable-en-xxhdpi/icon.png>drawable-en/icon.png>drawable-port-xxhdpi/icon.png> ... >drawable/icon.png。 - 胜出者:
drawable-en-port-xxhdpi/icon.png(最匹配)。如果没有,则选drawable-en-xxhdpi/icon.png(语言和密度匹配,方向不冲突)。再没有才用默认的drawable/icon.png。
- 候选资源目录:
-
总结:Android 资源管理框架的精髓
-
编译时 (aapt/aapt2):
-
核心目标: 将人类可读资源 (XML, PNG) 编译成高效的二进制格式 (体积小, 解析快)。
-
关键产出:
- 二进制资源文件 (
*.flat, 打包进 APK)。 - 资源索引表
resources.arsc: 核心数据库,按包、类型、配置组织所有资源信息。 -
R.java: 提供 Java 代码访问资源的符号常量。
- 二进制资源文件 (
-
资源ID (
0xPPTTEEEE): 是快速查找资源的钥匙。
-
-
运行时 (AssetManager/Resources):
-
核心目标: 根据 资源ID (钥匙) 和 当前设备配置 (语言、屏幕等),从 APK 中快速定位并加载最匹配的资源。
-
关键机制:
- 解析
resources.arsc索引表。 - 资源查找四步曲 (Package->Type->Entry->Value)。
- 智能资源配置匹配算法: 基于限定符优先级进行筛选。
- 解析
-
开发者接口:
Resources.getXXX(R.id.name)和findViewById(R.id.name)。
-
通俗比喻:
aapt是 资源翻译官兼仓库管理员,负责把杂乱的各种语言、尺寸的货物贴上精确的条码标签 (资源ID),按照严密的分类规则 (resources.arsc蓝图) 打包进集装箱 (APK),并生成一份货物清单 (R.java)。AssetManager/Resources是 运行时的智能分拣机器人。当 App 需要某件货物 (资源) 时,机器人扫描清单 (R.java给的条码资源ID),根据当前客户的国籍语言 (设备配置),按照蓝图 (resources.arsc) 指示的优先级规则,迅速找到仓库里最符合客户要求的那个版本的货物 (最匹配资源),并准确送达 (加载显示)。
这套机制确保了你的 App 能在全球无数不同配置的 Android 设备上,高效、正确地展示合适的界面和内容!这就是 Android 强大适配能力背后的资源管理框架。