在我们Activity中,我们要加载页面布局文件,通过setContentView()方法就能将我们用XML编写的布局文件载入。今天我们通过源代码来对这个过程进行分析。

看一段简单代码:

public class Main2Activity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main2);    }}

我们进入setContentView()方法,可以看到:

/** * Set the activity content from a layout resource.  The resource will be * inflated, adding all top-level views to the activity. * * @param layoutResID Resource ID to be inflated. * * @see #setContentView(android.view.View) * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) */public void setContentView(@LayoutRes int layoutResID) {    getWindow().setContentView(layoutResID);    initWindowDecorActionBar();}

只有两行,由于涉及到layoutResID,我们继续去看getWindow(),发现很简单的一个函数:

  /**     * Retrieve the current {@link android.view.Window} for the activity.     * This can be used to directly access parts of the Window API that     * are not available through Activity/Screen.     *     * @return Window The current window, or null if the activity is not     *         visual.     */    public Window getWindow() {        return mWindow;    }

其实也就是返回了一个Window对象,那么Window类又是一个什么类呢?

我们去了解下Window类:

/** * Abstract base class for a top-level window look and behavior policy.  An * instance of this class should be used as the top-level view added to the * window manager. It provides standard UI policies such as a background, title * area, default key processing, etc. * * 

The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */public abstract class Window { /** Flag for the "options panel" feature. This is enabled by default. */ public static final int FEATURE_OPTIONS_PANEL = 0; /** Flag for the "no title" feature, turning off the title at the top * of the screen. */ public static final int FEATURE_NO_TITLE = 1; ……}

截取了部分代码,我们可以看到其实Window是一个抽象类,接着我们去看下关于这个类的描述,其中有一句话特别重要,就是这个Window类只有唯一的一个实现类PhoneWindow,好了,对于:

getWindow().setContentView(layoutResID);

我们就直接去PhoneWindow中的setContentView()方法一探究竟。

@Overridepublic 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是什么?

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

看下对这个字段的注释,注释上说这是放置窗口内容的视图,它要么是mDecor本身,要么是是内容所去mDecor的孩子。其实说一千道一万,只是说了mContentParent是我们放置内容的父布局。那么我们的mContentParent是什么时候初始化的呢?带着这个问题,我们去寻找,最后我们可以发现:

private void installDecor() {    mContentParent = generateLayout(mDecor);}

可以看到它的初始化是在installDecor()方法中,而我们的installDecor()又是在当mContentParent == null时才会被调用,所以这里的mContentParent是空的。

既然是空的,那我们接下来就是要去installDecor()这个方法。

方法很长,首先来了解下mDecor。其实就是DecorView,而DecorViewFrameLayout的一个子类。

函数有点长,看重点:

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

省略了部分代码,在mContentParent == null之前都是初始化eDecor,由于刚刚我们说过mContentParent就是空,所以接下来,我们要去看看这个generateLayout(mDecor)方法。

走了好半天,终于走到了关键部分了,真的是不容易。走过去以后,发现这个函数不是一般的长,怎么办?

看重点!!!

我们往下略着看,看到有个地方有点意思:

final Context context = getContext();final int targetSdk = context.getApplicationInfo().targetSdkVersion;final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;final boolean targetHcNeedsOptions = context.getResources().getBoolean(        R.bool.target_honeycomb_needs_options_menu);final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {    setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE);} else {    setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE);}

看到没,这里就是控制版本在11之前和11之后是否显示菜单按钮的控制部分。看,读源码的过程还会有意外的收获。

嗯,我们接着往下看。

发现了一段注释

// Inflate the window decor.

嗯,看到这句我们就知道重点要来了。
看完了一大推的if-else语句之后,终于看到了一句:

// Embedded, so no decoration is needed.layoutResource = R.layout.screen_simple;// System.out.println("Simple!");

对呀,我们没有设置任何关于Feature的属性,所以也很好理解为啥一下子就来到了这里。

既然有布局可以看,那么我们就看看上面的布局是个什么。

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

很简单有木有?

往下看,其实我们可以发现,mDecor是在

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

将上面的xml布局inflate出来,然后添加到我们的mDecor上的。

然后通过mDecorfindViewById(),找到上面xml中的FrameLayout,然后在最后返回。

终于把这个过程屡清楚了。其实就是inflate一个xml,然后将里面的FrameLayout返回出来。

所以我们的mContentParent,最后的引用就是刚刚那个FrameLayout

一个installDecor()找的我们真是肝肠寸断。

最终我们还是回到了这里:

@Overridepublic 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.removeAllViews(),再接着就是mLayoutInflater.inflate(layoutResID, mContentParent),进去看看吧,这个方法不止一点重要。

进去后发现这TM不就是用了XmlPullParser来解析xml的么?

我们看到其中一部分源码:

if (TAG_MERGE.equals(name)) {    if (root == null || !attachToRoot) {        throw new InflateException(" can be used only with a valid "           + "ViewGroup root and attachToRoot=true");   }   rInflate(parser, root, inflaterContext, attrs, false);}

这里就是如果merge标签没有parent,就直接抛异常,然后调用:

rInflate(parser, root, inflaterContext, attrs, false);

看看这个函数的实现:

/** * Recursive method used to descend down the xml hierarchy and instantiate * views, instantiate their children, and then call onFinishInflate(). * 

* Note: Default visibility so the BridgeInflater can * override it. */void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException(" cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException(" must be the root element"); } else { final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); } } if (finishInflate) { parent.onFinishInflate(); }}

不短,但也不长,代码很简单,看看就懂了,我们只需要关注一句:

rInflateChildren(parser, view, attrs, true);

返回到函数调用的地方,我们在看看else里面,直接通过

// Temp is the root view that was found in the xmlfinal View temp = createViewFromTag(root, name, inflaterContext, attrs);

获取到了根节点,最后的最后也走到了:

// Inflate all children under temp against its context.rInflateChildren(parser, temp, attrs, true);

看函数意思,其实我们很容易理解,这个函数就是去inflate子布局的。
进入里面一看,我擦,最后又去了上面的rInflate,不用说咯,这就是递归,而且还是以深度优先的递归,厉害了。

最后我们的mContentParent上就有了布局啦,真的不容易啊,给自己一杯奶茶,庆祝下吧。

更多相关文章

  1. Unity-Android通信:AndroidJava 使用Unity c#编写Android程序调用
  2. Android例子—设置Activity全屏的三种方法
  3. Android stuio在MainActivity中运行java的main方法
  4. Android UI布局
  5. android中的提示信息显示方法(toast应用)
  6. adb devices 找不到设备的解决方法
  7. android 按home键返回到桌面后,再按桌面应用图标又重新打开该应用
  8. android获取各种系统路径的方法

随机推荐

  1. Android(安卓)viewPager实现翻动
  2. Android系统目录结构详解
  3. Android(安卓)Platform 3.0 SDK和Eclipse
  4. android 小游戏 ---- 数独(三)
  5. Android 控制WIFI相关操作
  6. Android之消息处理机制(二)Handler的本质-M
  7. [置顶] Android应用程序的自动更新升级(自
  8. android的Surface Flinger服务启动分析
  9. Android(安卓)之 串口通信
  10. 收藏-------------Android应用程序组件Co