本文将介绍Android UI的绘制流程。简单来说就是Android的界面是经过怎样的步骤来显示出来的。


文章目录

  • 1 Activity的setContentView
  • 2 Window的setContentView()方法
  • 3 PhoneWindow的setContentView()方法
  • 3.1 installDecor()
  • 3.2 generateLayout()
  • 3.2.1 获取主题的样式
  • 3.2.2 获取布局Id。
  • 3.2.3 把layoutResource对应的布局添加到mDecor中
  • 4 mDecor和layoutId如何关联到一起的
  • 5 下面在看文章开头的两个问题:
  • 5.1 设置的布局加载到哪里了呢?
  • 5.2 Activity和布局之间有什么样的关系呢?


我们一般都是创建Activity。然后在Activity的onCreate()方法中通过setContentView()来设置自己的布局。那么:

  • 设置的布局加载到哪里了呢?
  • Acvity和布局之间有什么样的关系呢?

1 首先从Activity的setContentView()方法开始分析。

1 Activity的setContentView

public void setContentView(View view) {
        getWindow().setContentView(view);
        initWindowDecorActionBar();
    }

可以看出在Activity的setContent()方法中,调用了

getWindow().setContentView(view);

而getWindow()方法返回的是Window对象

public Window getWindow() {
        return mWindow;
    }

也就是说在Activity的setContentView()方法调用的是Window的setContentView()方法。

2 Window的setContentView()方法

public abstract void setContentView(@LayoutRes int layoutResID);

可以看出Window的setContentView()方法是个抽象的方法。那么Window的实现类是谁呢?从Window类的注释中可以看出:

* <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {

Window类的唯一的实现类是PhoneWindow,接下来打开PhoneWindow来看在PhoneWindow中的setContentView()方法。我们可以发现根本就没有Link,全局也搜索不出这个类,说明这个类时隐藏的。那只好打开 SDK中的source文件夹中去搜索这个类。打开后:

3 PhoneWindow的setContentView()方法

@Override
    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();
        }
    }

可以看出当mContentParent = null时,会调用installDecor()方法

3.1 installDecor()

首先看下mContentParent 是个什么鬼

// 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.
private ViewGroup mContentParent;

这个注释说的很清晰。用我自己的话来说就是这个mContentParent就是Window用来显示内容的view.这个view有可能是mDecor也可能是mDecor的子类。
擦,那mDecor又是个什么东东?

// This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

从注释中可以看出,mDecor是Window中最高水平的View。(在后文中可以看出这个mDecor就是一个FrameLayout)
在setContentView(View view, ViewGroup.LayoutParams params) 方法中:
有这一句:

if (mContentParent == null) {
            installDecor();
        }

哦,也就是说当mContentParent 为null时就去创建mDecor了?
下面看PhoneWnidow中的installDecor()方法。

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);            
    }

这段代码有点长 我只复制了一部分。首先看这一段

if (mDecor == null) {
			//3.2.1
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }

当mDecor==null时,会调用generateDecor()方法。
接下来看generateDecor()方法

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

可以看出在generateDecor()方法中创建了DecorView对象。那么DecorView到底是个什么呢?

private final class DecorView extends FrameLayout

原来这个DecorView就是一个Fragment。是PhoneWindow中的一个内部类。
接着看3.2中的这一段代码

if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

这段代码调用了generateLayout()方法。接下来看generateLayout()方法。
这个方法有点长我截取几部分

3.2 generateLayout()

3.2.1 获取主题的样式

TypedArray a = getWindowStyle();

通过获得的样式来设置属性,下面的属性仅仅是一部分。

if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }

那么都获取了哪些属性呢?比如上面的这段。就是获取了是否显示标题栏。也就是屏幕最顶端的标题栏。并且如果标题栏不显示,那么ActionBar也不显示。

获取了主题的样式和属性后,接着

3.2.2 获取布局Id。

int layoutResource;
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!");
        }

上面这段代码就是根据之前的样式所设置的属性来给布局id layoutResource来赋值。
可以看到有两种layoutResource被赋值的情况,如:

layoutResource = R.layout.screen_title;
layoutResource = R.layout.screen_simple;

那么这两种布局对应的是什么样的呢?打开源码可以看到:

  • R.layout.screen_simple
<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>
  • R.layout.screen_title
<com.android.internal.widget.ActionBarOverlayLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/decor_content_parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:splitMotionEvents="false"
    android:theme="?attr/actionBarTheme">
    <FrameLayout android:id="@android:id/content"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent" />
    <com.android.internal.widget.ActionBarContainer
        android:id="@+id/action_bar_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        style="?attr/actionBarStyle"
        android:transitionName="android:action_bar"
        android:touchscreenBlocksFocus="true"
        android:gravity="top">
        <com.android.internal.widget.ActionBarView
            android:id="@+id/action_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            style="?attr/actionBarStyle" />
        <com.android.internal.widget.ActionBarContextView
            android:id="@+id/action_context_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="gone"
            style="?attr/actionModeStyle" />
    </com.android.internal.widget.ActionBarContainer>
    <com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  style="?attr/actionBarSplitStyle"
                  android:visibility="gone"
                  android:touchscreenBlocksFocus="true"
                  android:gravity="center"/>
</com.android.internal.widget.ActionBarOverlayLayout>

也就是说会根据不同主题样式来加载不同的布局文件。而这两个布局文件都有一个id为content的FramLayout。

3.2.3 把layoutResource对应的布局添加到mDecor中

View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

至此 mDecor做为DecorView的对象,已经初始化完成了。但是

mDecor和我们在Activity中设置的布局id是怎么联系到一起的呢?

4 mDecor和layoutId如何关联到一起的

在setContentView(int layoutResID)方法中会调用installDecor()方法,在installDecor()方法中,用mDecor来构造mContentParent。

if (mContentParent == null) {
      mContentParent = generateLayout(mDecor);

在generateLayout(DecorView decor)方法中会有如下这一段:

View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

这段代码中contentParent就是最后返回的ViewGroup.也就是说这段代码执行后,将会有:
mContentParent = conentParent。而contentParent是mDecor中的id为content的FrameLayout。

/**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

在setContentView(int layoutResID)方法中

mLayoutInflater.inflate(layoutResID, mContentParent);

至此,自己设置的资源文件id已经添加到id为content的FramLayout中了。

用图片来表示:

android aricgis 绘制点 android ui绘制_UI绘制流程

5 下面在看文章开头的两个问题:

5.1 设置的布局加载到哪里了呢?

从上图可以看出,设置的布局layoutResID会添加到DecorView中的FramLayout中。

5.2 Activity和布局之间有什么样的关系呢?

1 Activity通过Window的API来完成界面的绘制
2 PhoneWindow是Window的子类
3 PhoneWindow中包含一个内部类DecorView,它是一个FramLayout。
4 Activity对应的布局会添加到即为DecorView中所包含的一个FramLayout中。