用「商品房 vs 自建房」的比喻彻底讲透源码实现
核心问题答案
- 测量差异:Activity的DecorView和WindowManager直接添加的View,测量原理完全一致,均由ViewRootImpl根据窗口参数生成MeasureSpec。
- 父容器参数来源:顶级View(无父容器)的MeasureSpec由ViewRootImpl根据窗口尺寸和LayoutParams动态生成。
通俗比喻:商品房 vs 自建房
想象Android屏幕是一块土地,View是房子:
- Activity的DecorView → 商品房
开发商(系统)统一规划,直接对接土地管理局(ViewRootImpl)获取建房标准。 - WindowManager添加的View → 自建房
你自行买地建房,同样需要土地管理局(ViewRootImpl)审批建房标准。
关键点:无论哪种方式,最终审批权都在土地管理局(ViewRootImpl),规则相同!
源码深度解析
1. 测量起点:ViewRootImpl.performTraversals()
无论DecorView还是自定义View,添加到Window后都由ViewRootImpl统一调度:
// 源码:ViewRootImpl.java
void performTraversals() {
// 关键步骤1:计算窗口可用尺寸
Rect frame = mWinFrame;
int width = frame.width(); // 窗口宽度
int height = frame.height(); // 窗口高度
// 关键步骤2:生成MeasureSpec
int rootWidthMeasureSpec = getRootMeasureSpec(width, mLayoutParams.width);
int rootHeightMeasureSpec = getRootMeasureSpec(height, mLayoutParams.height);
// 关键步骤3:触发测量
mView.measure(rootWidthMeasureSpec, rootHeightMeasureSpec);
}
2. MeasureSpec生成规则(getRootMeasureSpec)
private int getRootMeasureSpec(int windowSize, int rootDimension) {
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT: // 占满窗口
return MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
case ViewGroup.LayoutParams.WRAP_CONTENT: // 最大不超过窗口
return MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
default: // 固定尺寸
return MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
}
}
参数来源:
windowSize→ 窗口实际可用尺寸(屏幕宽高 - 状态栏/导航栏)rootDimension→ 来自WindowManager.LayoutParams(width/height)
3. 两种添加方式参数传递对比
| 场景 | LayoutParams来源 | MeasureSpec生成方式 |
|---|---|---|
| Activity DecorView | PhoneWindow.generateLayout()中创建 | ViewRootImpl调用getRootMeasureSpec() |
| WindowManager添加View | 开发者自定义WindowManager.LayoutParams | 同上,完全一致! |
关键问题解答
Q1: 没有父容器,MeasureSpec从哪来?
- 答案:ViewRootImpl是隐藏的“顶级父容器”,它通过
getRootMeasureSpec()将窗口尺寸和LayoutParams转化为MeasureSpec。 - 类比:土地管理局(ViewRootImpl)根据土地大小(窗口尺寸)和你的申请材料(LayoutParams)给你发建房许可证(MeasureSpec)。
Q2: 两种方式测量有差异吗?
- 答案:无差异!两者最终调用链相同:
ViewRootImpl.performTraversals()→getRootMeasureSpec()→view.measure()
实战验证
案例:给WindowManager添加一个MATCH_PARENT的View
// 创建View
View myView = new MyView(context);
// 设置LayoutParams(关键!)
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT, // width
WindowManager.LayoutParams.MATCH_PARENT, // height
TYPE_APPLICATION,
FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
);
// 添加到Window
WindowManager wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
wm.addView(myView, params); // 触发ViewRootImpl生成MeasureSpec
测量过程:
MATCH_PARENT→ 生成EXACTLY + 窗口尺寸的MeasureSpec- 调用
myView.measure()→ 子View根据父容器(ViewRootImpl)要求布局
总结:三句话掌握核心
- 顶层权力:所有View的测量均由ViewRootImpl统一调度,无父容器的View由它生成MeasureSpec。
- 规则统一:DecorView和WindowManager添加的View采用完全相同的MeasureSpec生成逻辑。
- 参数决定命运:MeasureSpec由窗口尺寸 + LayoutParams动态计算,与添加方式无关!
最终比喻:
Android窗口系统就像城市规划局(ViewRootImpl),无论是开发商(Activity)还是个人(WindowManager),建房尺寸(MeasureSpec)都要按土地大小(窗口尺寸)和申报材料(LayoutParams)审批,规则绝对公平!