解析:Activity的DecorView与WindowManager添加View的测量原理

96 阅读3分钟

用「商品房 vs 自建房」的比喻彻底讲透源码实现


核心问题答案

  1. 测量差异:Activity的DecorView和WindowManager直接添加的View,测量原理完全一致,均由ViewRootImpl根据窗口参数生成MeasureSpec。
  2. 父容器参数来源:顶级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 DecorViewPhoneWindow.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  

测量过程

  1. MATCH_PARENT → 生成EXACTLY + 窗口尺寸的MeasureSpec
  2. 调用myView.measure() → 子View根据父容器(ViewRootImpl)要求布局

总结:三句话掌握核心

  1. 顶层权力:所有View的测量均由ViewRootImpl统一调度,无父容器的View由它生成MeasureSpec。
  2. 规则统一:DecorView和WindowManager添加的View采用完全相同的MeasureSpec生成逻辑。
  3. 参数决定命运:MeasureSpec由窗口尺寸 + LayoutParams动态计算,与添加方式无关!

最终比喻
Android窗口系统就像城市规划局(ViewRootImpl),无论是开发商(Activity)还是个人(WindowManager),建房尺寸(MeasureSpec)都要按土地大小(窗口尺寸)和申报材料(LayoutParams)审批,规则绝对公平!