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

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

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

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类的注释中可以看出:

* 

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

3.1可以看出当mContentParent = null时,会调用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了?

3.2下面看PhoneWnidow中的installDecor()方法。(what a fuck? why this method is so long?)

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()方法。
3.2.1接下来看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()方法。
3.2.2接下来看generateLayout()方法。
这个方法有点长我截取几部分

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也不显示。

获取了主题的样式和属性后,接着
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">    "@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 把layoutResource对应的布局添加到mDecor中

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

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

mDecor和我们在Activity中设置的布局id是怎么联系到一起的呢?
在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中了。
用图片来表示:

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

  • 设置的布局加载到哪里了呢?
    从上图可以看出,设置的布局layoutResID会添加到DecorView中的FramLayout中。
  • Activity和布局之间有什么样的关系呢?
    1 Activity通过Window的API来完成界面的绘制
    2 PhoneWindow是Window的子类
    3 PhoneWindow中包含一个内部类DecorView,它是一个FramLayout。
    4 Activity对应的布局会添加到即为DecorView中所包含的一个FramLayout中。

更多相关文章

  1. 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
  2. Android(安卓)View的onClick事件监听
  3. Android创建桌面快捷方式两种方法
  4. android横竖屏和隐藏标题栏、状态栏总结
  5. 实现Android的不同精度的定位(基于网络和GPS)[转]
  6. Android(安卓)Notification 通知详解(兼容Android(安卓)O)
  7. Android与设计模式:用单一职责则为Activity解耦
  8. 大神之路:重学Android——Android多媒体MediaPlayer
  9. 玩转Android---组件篇---数据存储之SQLite

随机推荐

  1. Android(安卓)中动画的基本使用
  2. Form表单组合控件
  3. RelativeLayout 用法以及举例
  4. Android(安卓)N画中画模式
  5. Android4: 请放弃使用Theme.Dialog
  6. 安卓NDK的开发
  7. Android(安卓)RelativeLayout的一些常用
  8. Android(安卓)ListView列表 刷新和加载更
  9. 多点触控测试代码 PointerLocation
  10. Android(安卓)Wear - Creative Vision fo