想象一下,你是一位乐高大师(Activity ),要搭建一个酷炫的模型(UI界面 )。你手里有一本厚厚的说明书(APK文件),里面包含了所有乐高积木的清单和拼装步骤。
- 宝藏地图(Resources): 这本说明书不是随便翻的,它需要一个专业的"地图管理员"来帮你查找。这个管理员就是 Resources 类。它不是一个简单的目录,而是一个超级智能的系统,知道去哪里找到你需要的任何积木(字符串、图片、布局等)。
- 积木编号(R.layout.xxx): 你不能直接对管理员说"我要那个红色的长方形积木",你得使用一个唯一的编号,比如
R.layout.activity_main
。这个编号就像是说明书里的精确页码。 - 仓库总管(AssetManager): 地图管理员(Resources)自己并不直接拿着说明书,他有一个更底层的"仓库总管"叫 AssetManager。这个总管才真正拥有打开APK文件(说明书)并读取里面原始数据的能力。
- 变形金刚(LayoutInflater): 当你通过地图管理员和仓库总管拿到了"拼装步骤图"(layout的XML文件)时,它只是一张纸(文本),还不是真正的乐高模型。这时就需要一个"变形金刚"------LayoutInflater 。它的魔法就是把纸上的描述(
<TextView>
)变成实实在在的、可以活动的乐高积木(View对象)。
详细探险:代码级别的寻宝之旅
现在,让我们穿上代码的探险服,跟着 setContentView(R.layout.activity_main)
这句咒语,开始冒险!
第一站:Activity的指令
java
// 在Activity中
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
}
getWindow()
返回的是PhoneWindow
对象,它是Window
抽象类的具体实现。你可以把PhoneWindow
想象成你工作用的"桌面"。
第二站:PhoneWindow的准备
PhoneWindow.setContentView(layoutResID)
做了几件关键事:
- 检查桌面是否干净: 它首先检查是否已经有一个叫
DecorView
的"基础底板"了。如果没有,就创建一个。DecorView
是整个窗口的根视图,它包含了系统UI(如标题栏)和我们自己的内容。 - 寻找内容区域: 它在
DecorView
里找到一个特定的区域叫mContentParent
(一个ViewGroup
,通常是FrameLayout
),我们的布局最终就要放在这里。 - 呼叫变形金刚!: 最关键的一步来了!
java
// 简化版的PhoneWindow代码
public void setContentView(int layoutResID) {
// 1. 创建或获取DecorView
if (mContentParent == null) {
installDecor();
}
// 2. 清空内容区域(如果需要)
mContentParent.removeAllViews();
// 3. 核心魔法:将布局资源ID"膨胀"成真正的View树,并添加到mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
第三站:LayoutInflater的魔法核心
LayoutInflater.inflate(resId, root)
是整个流程的灵魂。我们深入看看 inflate
方法:
java
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
// 重点!通过Resources获取XML的解析器(XmlResourceParser)
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
看!getContext().getResources().getLayout(resource)
!这里,我们终于见到了故事里的"地图管理员"------Resources。
第四站:Resources与AssetManager的联手
Resources.getLayout(int id)
方法:
java
public XmlResourceParser getLayout(int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
它调用了 loadXmlResourceParser
,这里发生了更神奇的事情:
java
XmlResourceParser loadXmlResourceParser(int id, String type) throws NotFoundException {
// 1. 创建一个"价值容器" TypedValue,用于存放查询结果
TypedValue value = new TypedValue();
// 2. 核心查询!通过id获取资源的具体信息
getValue(id, value, true);
// 3. 检查找到的是不是我们想要的XML文件
if (value.type == TypedValue.TYPE_STRING) {
// 4. 让AssetManager去打开这个具体的文件!
return loadXmlResourceParser(value.string.toString(), id, value.assetCookie, type);
}
throw new NotFoundException("Resource ID does not point to an XML file");
}
getValue(id, value, true)
:这是资源查找的核心。Resources对象内部维护着一个复杂的资源表结构(在APK打包时由AAPT2工具生成)。它通过id
这个索引,快速查找到资源在APK中的具体位置、文件名、配置等信息,并填充到TypedValue
对象中。这里的assetCookie
可以理解为对应APK文件的句柄。
最终,Resources
会调用到它的得力助手------AssetManager 的Native方法(如 nativeOpenXmlBlockAsset
),去真正地打开APK文件(ZIP格式),解析并读取对应的XML文件(如 res/layout/activity_main.xml
),返回一个 XmlResourceParser
对象。
简单比喻:
- Resources 像是一个聪明的图书管理员,他知道每本书(资源)在哪个书架(APK)、第几层(资源类型)、第几本(资源ID)。
- AssetManager 则是图书馆的仓库机器人,拥有打开仓库门(APK文件)并取出具体书籍(资源文件)的能力。
第五站:LayoutInflater的解析与创建
现在,LayoutInflater
拿到了 XmlResourceParser
(一个XML解析器)。它开始像读流水线一样解析XML:
java
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
// ... 循环解析XML的每一个节点 ...
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
// 1. 如果是 <merge> 标签,特殊处理
if (TAG_MERGE.equals(name)) {
// ... 处理merge ...
} else {
// 2. 核心:根据标签名创建View对象!
final View view = createViewFromTag(root, name, inflaterContext, attrs);
final ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
// 3. 递归调用!如果当前View是ViewGroup,继续inflate它的子View
if (view instanceof ViewGroup) {
inflateChildren(parser, (ViewGroup) view, attrs, true);
}
// 4. 将创建好的View添加到父容器中
root.addView(view, params);
}
}
// ...
}
createViewFromTag
是另一个魔法点:
java
View createViewFromTag(/* ... */) {
// ... 尝试通过Factory创建,这是插件化、换肤的关键hook点 ...
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(name, context, attrs);
}
// ... 其他Factory尝试 ...
if (view == null) {
// 没有Factory干预,使用最原始的方式创建
if (-1 == name.indexOf('.')) {
// 如果是系统控件,如 <TextView>
view = onCreateView(context, name, attrs);
} else {
// 如果是自定义控件,如 <com.example.MyView>
view = createView(context, name, null, attrs);
}
}
return view;
}
最终,createView
会通过反射调用类的构造函数(如 new TextView(context, attrs)
)来创建View对象!这样,一个冰冷的XML标签就变成了一个有血有肉、可以在屏幕上绘制的Java对象。
寻宝全过程时序图
下面这张时序图清晰地展示了整个调用链条:

总结
整个 Resources
获取 layout
的过程,就像一场精心策划的寻宝行动:
- 发起请求:
Activity
通过setContentView
发出指令。 - 准备舞台:
PhoneWindow
准备好承载内容的根容器DecorView
和mContentParent
。 - 资源定位:
LayoutInflater
求助Resources
,Resources
利用内部的资源映射表,通过ID快速定位到XML文件的具体信息。 - 文件读取:
Resources
派遣AssetManager
,通过Native方法深入到APK包中,将XML文件读取出来,变成一个可解析的流。 - 视图创造:
LayoutInflater
开始解析XML流,遇到一个标签,就通过反射 实例化对应的View类,并设置属性。如果这个View是容器(ViewGroup
),就递归地重复这个过程,最终构建出一棵完整的View树。 - 最终呈现: 这棵View树被添加到
PhoneWindow
的mContentParent
中,经过测量(Measure)、布局(Layout)、绘制(Draw)后,就呈现在了用户面前。
理解这个过程,对于处理界面卡顿(优化布局层级)、实现插件化(hook LayoutInflater
的 Factory
)、换肤等高级技术至关重要。希望这个有趣的故事和详细的代码分析能帮助你彻底掌握它!