认识Android中的ViewRootImpl和DecorView

PS:本文系转载文章,阅读原文可读性会更好些,原文链接:
https://mp.weixin.qq.com/s/EGFHW7URuIv0HqwZWt09sg

ps:源码是基于 android api 27 来分析的

ViewRootImpl 是用来测量、布局和绘制 View 用的,View 的测量、布局和绘制是从 Activity 的 makeVisible方法开始的,但是本篇文章重点不是具体讲这个(View 的测量、布局和绘制代码细节),而是讲对 ViewRootImpl和DecorView 认知。

1、ViewRootImpl

我们来看一下 Activity 的 makeVisible方法;

void makeVisible() {
if (!mWindowAdded) {

        //1、
        ViewManager wm = getWindowManager();
        
        //2、
        wm.addView(mDecor, getWindow().getAttributes());
        ......
    }
    ......

}

注释1 中的 ViewManager 实现类是 WindowManagerImpl;这里的 mDecor 变量是 DecorView 类型的,我们来看一下注释2 中的代码具体实现,也就是 WindowManagerImpl 的 addView 方法;

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);

    //3、
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

注释3 处调用了 WindowManagerGlobal的 addView 方法,我们往下看;

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {

synchronized (mLock) {

root = new ViewRootImpl(view.getContext(), display);

try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {

}
}
}

这里的 View 是 DecorView,可以看出 ViewRootImpl 是 WindowManagerImpl 和 DecorView 连接的纽带;在DecorView 的测量、布局和绘制之前,ViewRootImpl 的执行过程是这样的:ViewRootImpl.setView 方法调用 ViewRootImpl.requestLayout 方法,ViewRootImpl.requestLayout 方法调用 ViewRootImpl.scheduleTraversals 方法,ViewRootImpl.scheduleTraversals 方法调用mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null) 语句,mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null) 语句会回调 ViewRootImpl 的内部接口 TraversalRunnable,TraversalRunnable 调用 ViewRootImpl.doTraversal 方法,ViewRootImpl.doTraversal 方法调用 ViewRootImpl.performTraversals 方法。

我们来看看 ViewRootImpl 的 performTraversals 方法;

private void performTraversals() {

if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {

if (!mStopped || mReportNextDraw) {

if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

if (measureAgain) {

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}

}
}
} else {

}

if (didLayout) {
performLayout(lp, mWidth, mHeight);

}

if (!cancelDraw && !newSurface) {

performDraw();
} else {

}

}

从 ViewRootImpl 的 performTraversals 方法可以看出 View 的测量、布局和绘制就在这里开始了。

performMeasure 方法是 ViewGroup 的测量方法入口,performMeasure 会调用 ViewGroup 的 measure 方法,在ViewGroup 的 measure 方法中又会调用 ViewGroup 的 onMeasure 方法,在 ViewGroup 的 onMeasure 方法中则会对所有的子元素进行 measure 过程,这个时候 measure 流程就从父容器传递到子元素中了,这样就完成了一次测量过程;接着子元素会重复父容器的 measure 过程,如此反复就完成了整个 View 树的遍历。

performLayout 方法是 ViewGroup 的布局方法入口,performLayout 会调用 ViewGroup 的 layout 方法,在 ViewGroup 的 layout 方法中又会调用 ViewGroup 的 onLayout 方法,在 ViewGroup 的 onLayout 方法中则会对所有的子元素进行 layout 过程,这个时候 layout 流程就从父容器传递到子元素中了,这样就完成了一次布局过程。

performDraw 方法是 ViewGroup 的绘制方法入口,performDraw 会调用 ViewGroup 的 draw(Canvas canvas) 方法,ViewGroup 的 draw(Canvas canvas) 方法会调用 dispatchDraw 方法和 onDraw 方法(注意:有的 View 是在 draw 方法完成绘制,有的是在 onDraw 方法完成),ViewGroup 的 dispatchDraw 方法则会对所有的子元素进行遍历,然后调用 ViewGroup 的 drawChild 方法,ViewGroup 的 drawChild 方法会调用子元素的 draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法,这个时候 draw 流程就从父容器传递到子元素中了,子元素的 draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法调用子元素的 draw(Canvas canvas) 方法,子元素就会遵循父元素的 draw(Canvas canvas) 方法调用过程;这样不断遍历子 View 及子 View 的不断对自身的绘制,从而使得 View 树完成绘制。

测量过程决定了 View 的宽 和 高,测量完成以后,可以通过View 的 getMeasuredWidth 方法和 getMeasuredHeight方法来获取到 View 测量后的宽 和 高,一般情况下测量后的宽高是等于 View 最终的宽高的;布局过程就明确了 View 的四个顶点的位置和要显示的 View 的宽高,View的 onLayout 方法完成以后,可以通过 View 的 getTop 方法、getBottom 方法、getLeft 方法和 getRight 方法来拿到 View 的四个顶点的位置,通过 View 的 getWidth 方法和 getHeight 方法来拿到 View 要显示的宽高;绘制过程就是为了让 View 显示出来,有的 View 是在 draw 方法完成,有的则是在 onDraw 方法。

2、DecorView

我们先来查看 DecorView 这个类;

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {

}

从这个类的继承关系中,我们可以知道 DecorView 其实是一个 FrameLayout;我们知道 Activity 中会有一个 Window,而具体的 Window 实现是 PhoneWindow,我们在 Android 手机上看到的 View,其实是我们的 DecorView 所呈现出来的 View,而 DecorView 是通过 PhoneWindow 呈现出来的,我们来看 DecorView 的布局结构,我们先从 Activity 的 setContentView(int layoutResID) 方法看起;

public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

前面我们说过,Window 的实现类是 PhoneWindow,所以 getWindow() 拿到的实际上是 PhoneWindow 对象,我们来看 PhoneWindow 的 setContentView(int layoutResID) 方法;

@Override
public void setContentView(int layoutResID) {
    ......
    if (mContentParent == null) {
        
        //1、
        installDecor();
    }
    ......
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ......
    } else {
        
        //2、
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ......
}

注释2 的代码我们晚点再说,先看注释1 的代码,PhoneWindow 的 installDecor方法;

private void installDecor() {

if (mDecor == null) {

        //3、
        mDecor = generateDecor(-1);
        ......
    } else {
        ......
    }
    if (mContentParent == null) {
        
        //4、
        mContentParent = generateLayout(mDecor);
        ......
    }

}

我们来看注释3 中的 generateDecor 方法,它属于 PhoneWindow 类中;

protected DecorView generateDecor(int featureId) {

return new DecorView(context, featureId, this, getAttributes());
}

直接返回一个 DecorView 对象,由此可见 generateDecor 方法是创建 DecorView 对象用的;我们再来回看到注释4 中的代码,也就是 generateLayout 方法,同样它也是属于 PhoneWindow 类中;

protected ViewGroup generateLayout(DecorView decor) {

int layoutResource;

if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println(“Title Icons!”);
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println(“Progress!”);
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println(“Title!”);
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
//5、
layoutResource = R.layout.screen_simple;
// System.out.println(“Simple!”);
}

    mDecor.startChanging();
    
    //6、
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    //7、
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ......
    return contentParent;

}

layoutResource 是 DecorView 的结构布局文件,怎么知道是不是呢,我们来看注释 6 的代码,也就是 DecorView 的 onResourcesLoaded 方法;

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {

mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {

        // Put it below the color views.
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    ......

}

DecorView 的 onResourcesLoaded 方法是将 layoutResource 布局文件生成一个 View,然后将 View 添加到 DecorView 中;我们来看一下注释5 的代码,我们来分析当 layoutResource = R.layout.screen_simple 的布局情况;

screen_simple.xml




我们来看注释7 的 ID_ANDROID_CONTENT,它其实等同于 screen_simple.xml 的 FrameLayout 中的 id/content,所以注释7 中的 contentParent 就是 FrameLayout,从上面注释4 可以看出同时也将 contentParent 返回赋值给 mContentParent;回到上面注释2 中的代码,假设我们 Activity 中的语句 setContentView() 中的布局文件为 activity_main.xml,那么 activity_main.xml 就会被添加到 mContentParent 中,所以 DecorView 的结构如下所示:

图片

图自己画的有点丑,ViewStub 是标题栏,FrameLayout 是内容栏,我们的布局文件 activity_main.xml 被添加到了 id 为 content 的 FrameLayout 内容栏之中,所以可以理解为 Activity 指定布局的方法不叫 setview 而叫 setContentView,通过 findViewByld(R.android.id.content) 语句可以拿到 内容栏的 FrameLayout。

上一篇:Android RecycleView切换条目布局visibility导致列表滑动


下一篇:Android setContentView源码阅读