关于android:认识Android中的ViewRootImpl和DecorView

6次阅读

共计 9094 个字符,预计需要花费 23 分钟才能阅读完成。

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。

正文完
 0