View 的绘制流程及原理

View

View 是如何被添加到屏幕窗口上

当我们在调用 Activity#setContentView(int layoutResID) 时,实际上调用的是 PhoneWindow#setContentView(int layoutResID)
PhoneWindow 流程代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void setContentView(int layoutResID) {
// ...
installDecor(); // 初始化DecorView和content父布局
// ...
mLayoutInflater.inflate(layoutResID, mContentParent); // 将我们的布局文件加载到content父布局
// ...
}

private void installDecor() {
// ...
if (mDecor == null) {
mDecor = generateDecor(-1);
// ...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// ...
}
}

protected DecorView generateDecor(int featureId) {
// ...
return new DecorView(context, featureId, this, getAttributes());
}

protected ViewGroup generateLayout(DecorView decor) {
// ... 根据主题获取对应的布局
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); // 【注】这里既然使用mDecor为啥还要传入参数decor ...

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); // 通过全局ID获取content父布局
// ...
return contentParent;
}

AppCompatActivity#setContentView(int resId) 兼容实际上调用的是 AppCompatDelegateImpl#setContentView(int resId)
AppCompatDelegateImpl 流程代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}

private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();
// ...
}
}

private ViewGroup createSubDecor() {
// ... 根据主题获取subDecor
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
// ... 将原生的content父布局替换成subDecor的父布局
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
// ...
}
mWindow.setContentView(subDecor); // 将兼容布局设置到PhoneWindow
// ...
return subDecor;
}

View 的绘制流程

  • 绘制入口

    1
    2
    3
    ActivityThread.handleResumeActivity
    --> WindowManagerImpl.addView()
    --> WindowManagerGlobal.addView() --> new ViewRootImpl() // 在创建ViewRootImpl时创建Surface
  • 绘制的类及方法

    1
    2
    3
    4
    5
    6
    ViewRootImpl.setView()
    --> ViewRootImpl.requestLayout()
    --> ViewRootImpl.scheduleTraversals() // 这里会插入同步屏障postSyncBarrier
    // 这里等待Choreographer$FrameDisplayEventReceiver接收到Vsync信号进行回调
    --> ViewRootImpl.doTraversal()
    --> ViewRootImpl.performTraversals()
  • 绘制三大步骤

    1
    2
    3
    测量:ViewRootImpl.performMeasure -> view.onMeasure -> view.setMeasuredDimension -> view.setMeasuredDimensionRaw
    布局:ViewRootImpl.performLayout -> view.layout -> view.onLayout
    绘制:ViewRootImpl.performDraw -> ViewRootImpl.draw -> ViewRootImpl.drawSoftware -> view.draw // trackFPS、lockCanvas、unlockCanvasAndPost
  • Measure类型

    • EXACTLY:固定大小,父容器检测出子 View 的大小【match_parent、dp/px
    • AT_MOST:最大大小,父容器指定一个可用大小,View的大小不能超过该值【wrap_content】
    • UNSPECIFIED:不对 View 做任何限制,系统内部使用
    childLayoutParams\parentSpecMode EXACTLY AT_MOST UNSPECIFIED
    dp/px EXACTLY/childSize EXACTLY/childSize EXACTLY/childSize
    match_parent EXACTLY/parentSize AT_MOST/parentSize UNSPECIFIED/0
    wrap_content AT_MOST/parentSize AT_MOST/parentSize UNSPECIFIED/0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
    result = size;
    break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
    result = specSize; // 这也是为什么View大小wrap_content和match_parent的原因
    break;
    }
    return result;
    }
事件分发机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
participant Activity
participant PhoneWindow
participant DecorView
participant ViewGroup
participant View

Activity -> PhoneWindow:superDispatchTouchEvent()
PhoneWindow -> DecorView:superDispatchTouchEvent()
DecorView -> ViewGroup:dispatchTouchEvent()
ViewGroup -> ViewGroup:onInterceptTouchEvent()
ViewGroup -> View:onTouchEvent()
View -> ViewGroup:return false
ViewGroup -> DecorView:return false
DecorView -> PhoneWindow:return false
PhoneWindow -> Activity:return false
Activity -> Activity:onTouchEvent()

dispatchTouchEvent:是否分发

onInterceptTouchEvent:是否拦截

onTouchEvent:是否消费

事件分发流程

当屏幕被触摸,首先会通过硬件产生触摸事件传入内核,然后走到 FrameWork 层(具体流程感兴趣的可以看看参考链接),最后经过一系列事件处理到达ViewRootImplprocessPointerEvent 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//ViewRootImpl.java
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
...
//mView分发Touch事件,mView就是DecorView
boolean handled = mView.dispatchPointerEvent(event);
...
}

//DecorView.java
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
//分发Touch事件
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//cb其实就是对应的Activity
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}


//Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

//PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}

//DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}

事件的分发流程就比较清楚了:

ViewRootImpl --> DecorView --> Activity --> PhoneWindow --> DecorView --> ViewGroup

这里绕来绕去主要原因在于解耦:

  • ViewRootImpl并不知道有Activity这种东西存在,它只是持有了 DecorView。所以先传给了 DecorView,而 DecorView 知道有AC,所以传给了AC。
  • Activity也不知道有 DecorView,它只是持有 PhoneWindow,所以这么一段调用链就形成了。