4

认识Android中的ViewRootImpl和DecorView

 2 years ago
source link: https://segmentfault.com/a/1190000040820475
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

认识Android中的ViewRootImpl和DecorView

发布于 今天 05:52

PS:本文系转载文章,阅读原文可读性会更好,文章末尾有原文链接

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
          android:inflatedId="@+id/action_mode_bar"
          android:layout="@layout/action_mode_bar"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:theme="?attr/actionBarTheme" />
<FrameLayout
     android:id="@android:id/content"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:foregroundInsidePadding="false"
     android:foregroundGravity="fill_horizontal|top"
     android:foreground="?android:attr/windowContentOverlay" />

</LinearLayout>

我们来看注释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。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK