Android视图加载到窗口的过程

Android的窗口结构

窗口结构示意图:

Android视图加载到窗口的过程_第1张图片

对上图做一个简单的说明:

1、Window表示一个窗口的概念,Android中所有的视图都是通过Window来呈现的,它们的视图实际上都是附加在Window上的,因此Window实际上是View的直接管理者。Activty的设置视图的方法setContentView在底层也是通过Window来完成的。

2、Window是一个抽象类,它的具体实现是PhoneWindow,每个Activity都会创建以恶搞PhoneWindow对象,是Activity和整个View系统交互的接口。

3、DecorView继承FrameLayout,所以是一个ViewGroup,在整个Window最顶层,将要显示的具体内容。最终由PhoneWindowDecorView的内容呈现在屏幕上,也就是说当前Activity在屏幕上显示的东西都在DecorView里。

DecorView

DecorView是一个ViewGroup,是一个View的容器,本身并不会向用户呈现任何东西,DecorView是ViewTree中最顶层的元素,从屏幕的角度来看DecorView是最底层的View,其他的View都呈现在DecorView上面。所以DecorView是当前Activity中所有View的根View

系统为我们提供了几种默认的布局文件填充到DecorView中,作为其子元素,这些布局文件位于 frameworks/base/core/res/layout/ ,最常用的窗口布局文件有:

Screen-title.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:fitsSystemWindows="true">  
      
    <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" />  
    <FrameLayout  
        android:layout_width="match_parent"   
        android:layout_height="?android:attr/windowTitleSize"  
        style="?android:attr/windowTitleBackgroundStyle">  
        <TextView android:id="@android:id/title"   
            style="?android:attr/windowTitleStyle"  
            android:background="@null"  
            android:fadingEdge="horizontal"  
            android:gravity="center_vertical"  
            android:layout_width="match_parent"  
            android:layout_height="match_parent" />  
    FrameLayout>  
    <FrameLayout android:id="@android:id/content"  
        android:layout_width="match_parent"   
        android:layout_height="0dip"  
        android:layout_weight="1"  
        android:foregroundGravity="fill_horizontal|top"  
        android:foreground="?android:attr/windowContentOverlay" />  
LinearLayout>  

将Screen-title.xml填充到DecorView后我们会得到如下的ViewTree:

Android视图加载到窗口的过程_第2张图片

可以看到在ViewTree中,DecorView是最顶层的元素,它有两个子元素:
(1)LinearLayout:就是将Screen-title.xml填充到DecorView,作为其子元素,LinearLayout又包含了三个子元素一个ViewStub和两个帧布局(FrameLayout),可以看出和Screen-title.xml中的元素一致。其中一个FrameLayout中含有一个id为title的TextView,其实就是标题;另外一个FrameLayout的id为content,我们设置的Activity的布局文件就是被填充到这里的

Android视图加载到窗口的过程_第3张图片

(2)View:屏幕最上端状态栏的背景

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" />  
    <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>

在开发中,有时候需要将标题栏去掉,可以在AndroidManifest中进行设置:

android:theme="@android:style/Theme.NoTitleBar"`

这时填充到DecorView中的是Screen-simple.xml,得到如下的ViewTree:

Android视图加载到窗口的过程_第4张图片

DecorView依旧包含两个子元素,我们可以看到LinearLayout中只有一个帧布局id为content,用来填充Activity的布局文件,包含TextView(标题)的帧布局没有了。

那么问题来了,Acitvity的布局文件是如何加载到id为content的帧布局中的呢?

视图加载过程
在开发中通常是定义好XML布局文件后,在Activity的onCreate方法中使用setContentView方法加载布局

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

setContentView方法如下:

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

其中getWindow() 方法获取了当前Activity中的Window的对象

public Window getWindow() {
        return mWindow;
    }

回到setContentView方法,获取Window对象后,调用了Window的setContentView方法,Window是抽象类,它的唯一实现类是PhoneWindow,所以在PhoneWindow中找到setContentView方法

public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

mContentParent是PhoneWindow的一个成员变量:

// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;

其实它就是ViewTree中,DecorView的LineLayout子元素中id为content的FrameLayout,回到PhoneWindow的setContentView,如果mContentParent为空则执行installDecor()方法初始化DecorView的布局,加一个基本布局上去,如前面说的Screen-title.xml。

installDecor()方法源码如下:
源码太长,只看需要的部分

private void installDecor() {
       ...省略
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...省略
        }
        ...省略
    }

如果mContentParent为空调用generateLayout方法为mContentParent赋值。
the fucking source code is too long
generateLayout源码:

    protected ViewGroup generateLayout(DecorView decor) {
        ...省略...
        int layoutResource;
        int features = getLocalFeatures();

        //这些布局都是系统提供的,在SDK的目录:/platforms/android-25/data/res/layout


        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } 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.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);//将布局加载到DecorView中

        //ID_ANDROID_CONTENT对应的是@android:id/content的FrameLayout  即DecorView子元素LinearLayout中的id是content的帧布局
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...省略...

        return contentParent;
    }

概括一下就是,这个方法就是将系统提供的某一种布局加载到DecorView中,然后找到一个id为content的FrameLayout,并将这个FrameLayout返回。

回到installDecor方法,将generateLayout的返回值赋给mContentParent至此我们得到了Activity的布局文件所加载的位置。

再会带PhoneWindow的setContentView方法,将Activity的布局加载到id为content的FrameLayout中。

流程图如下:

Android视图加载到窗口的过程_第5张图片

到目前为止,通过setContentView方法,创建了DecorView和加载了Activity的布局,但是这是,View还是不可见的,因为仅仅是加载了布局,并没有对View进行任何的测量、布局、绘制。



绘制流程:View的绘制流程

你可能感兴趣的:(Android框架学习记录)