一.分析层层调用的关键代码

  • 设置布局文件:每个Activity的onCreate()中 setContentView(R.layout.xx)
  • 实际是调用Window抽象类的方法:this.getWindow().setContentView(R.layout.xx)。
  • Window唯一实现类PhoneWindow实现的setContentView():
    关键调用方法:
    1)installDecor()
        1> 创建布局容器 : 先创建顶层View DecorView, 通过 mDecor 创建 ViewGroup布局容器。
        2> 加载基础布局 : 调用 generateLayout(), 处理 requestFeature 设置, 根据处理结果加载不同的基础布局;
            默认加载布局R.layout.screen_simple,垂直方向线性布局,包含状态栏ViewStub+Activity传入的自定义布局存放在FrameLayout。

    2)mLayoutInflater.inflate() 加载布局文件
        加载开发者自己的布局文件,LayoutInfater是PhoneWindow创建时初始化的布局加载器。
        布局加载器加载过程:进行xml解析,解析出具体的组件和层级。
public class PhoneWindow extends Window implements Callback {
	//窗口最顶层的 View
    private DecorView mDecor;
    //窗口的内容放置器
    ViewGroup mContentParent;
    //PhoneWindow 创建时初始化 mLayoutInflater 布局加载器成员变量
    private LayoutInflater mLayoutInflater;
    
    public PhoneWindow(Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
    }

 	public void setContentView(int layoutResID) {
        //对feature 属性进行初始化生效操作
        if (mContentParent == null) {
        	//1.关键方法:installDecor()
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            ...
        } else {
        	//2.加载自己的布局文件
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
    }
    
    private void installDecor() {
        this.mForceDecorInstall = false;
        if (this.mDecor == null) {
        	//1.创建窗口最顶层的View:DecorView 成员变量对象
            this.mDecor = this.generateDecor(-1);
        } else {
            this.mDecor.setWindow(this);
        }

        if (this.mContentParent == null) {
        	//2.创建内容存放容器对象
            this.mContentParent = this.generateLayout(this.mDecor);
        }
        
    protected DecorView generateDecor(int featureId) {
        return new DecorView((Context)context, featureId, this, this.getAttributes());
    }

	protected ViewGroup generateLayout(DecorView decor) {
        //处理 requestFeature 设置
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            requestFeature(FEATURE_ACTION_BAR);
        }
        
        //这一组代码负责处理布局文件相关
        int layoutResource;
        int features = getLocalFeatures();
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {...
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {...
        } else {
           //默认状态下不做任何设置的布局
           layoutResource = R.layout.screen_simple;
        }
    }
    	
}

public abstract class LayoutInflater {
	public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
		//进行 xml 解析, 解析出具体的组件及层级
        final XmlResourceParser parser = res.getLayout(resource);
    }
}

二.UI绘制流程

  1. 主线程ActivityThread会调用handleResumeActivity(),核心语句是wm.addView(decor, l); 将开发者开发的布局加载到 decor 基础布局中
  2. WIndowManager 的 addView 逻辑是定义在 WindowManagerGlobal 中,将View 对象, DecorView 对象、关联的ViewRootImpl 对象、布局参数对象三个队列全部交给 ViewRootImpl对象,root.setView(view, wparams, panelParentView);
  3. 在 setView 方法中调用了 requestLayout() --> scheduleTraversals() --> 回调调用了 TraversalRunnable 中的 run() --> doTraversal()–>ViewRootImpl 的 performTraversals() UI绘制的核心方法

三.UI绘制核心-View测量、布局、绘制流程

① 测量 Measure
② 布局 Layout
③ 绘制 Draw

1.绘制流程:

  • 确定控件的起点位置:在ViewRootImpl.performTraversals()绘制一个矩形Rect来确定布局整体的位置
  • 布局测量:在performMeasure()中调用view.measure(),在其中计算组件大小,根据组件模式和设定的宽高。
  • 布局摆放:在performLayout()中调用view.layout(),
  • 布局绘制:在performDraw()中调用view.draw()

2.View布局测量:

  • 进行布局测量 ViewRootImpl.performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  • getRootMeasureSpec():生成View测量的MeasureSpec布局参数。
    MeasureSpec 规格 : 将 组件的大小 结合 布局参数中设置的转换规则
    32位int值=2模式位+30位大小值
    1> 与父容器相大小相等 MeasureSpec.EXACTLY : match_parent 。与父容器宽高完全一致, 充满父容器;
    2> 与子控件相关 MeasureSpec.AT_MOST : wrap_content 子容器测量后的占用的大小, 最大不能超过父容器;
    3> 设置固定值 MeasureSpec.EXACTLY : 直接设置一个 dp 或 px 值;
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    //基于窗口的根组件的布局参数
    //计算出对应的 MeasureSpec 参数, windowSize 窗口的宽或高, rootDimension 窗口的某个方向的布局参数
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
	        case ViewGroup.LayoutParams.MATCH_PARENT:
	            //MATCH_PARENT  使用根 view 大小
	            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
	            break;
	        case ViewGroup.LayoutParams.WRAP_CONTENT:
	            //WRAP_CONTENT  设置根 view 的 最大大小;
	            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
	            break;
	        default:
	            //其他:固定的大小, 将根 view 设置成 指定的大小; 
	            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
	            break;
        }
        return measureSpec;
    }
}
  • View 的 onMeasure 方法:调用onMeasure(w,h);自己实现测量逻辑,自定义控件根据自己的业务需求,重写核心方法

3.View布局摆放:

  • 布局摆放Layout与布局测量Measure一样,都是顶层 View 循环调用子组件的 onMeasure/onLayout 方法, 直至调用到 最底层的 组件;
  • 进行布局绘制 ViewRootImpl.performLayout()核心调用view的layout(),setFrame 设置一个布局摆放范围, 然后调用 onLayout()。
    setFrame方法: 对组件的上下左右四个坐标进行初始化。将每次布局摆放的坐标记录下来提升性能,传入新的坐标时与旧坐标进行对比。不一致重新布局计算。
  • View 的 onLayout方法:调用onLayout(w,h);自己实现摆放逻辑,计算出 组件的 左 上 右 下 的值。

4.View布局绘制:

  • 进行布局绘制 ViewRootImpl.performDraw(),核心调用draw(),获取画布和要绘制的rect范围,传入drawSoftware()中。
  • drawSoftware():
    1> 先设置画布Canvas:mSurface.lockCanvas(dirty)
    2> 正式绘制:调用mView.draw(canvas)
    绘制流程:
    ① 绘制背景,
    ② 图层保存, (如果有必要, 保存当前画布层, 以便进行渐变绘制)
    ③ 绘制组件内容,
    ④ 绘制子组件,
    ⑤ 图层恢复, (如果有必要, 恢复画布层, 绘制渐变内容)
    ⑥ 绘制装饰内容;
  • View 的 onDraw(canvas) 绘制组件,自己实现