Android 布局加载源代码分析

在我们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.
 *
 * <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 {
    /** 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()方法一探究竟。

@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();
    }
    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()找的我们真是肝肠寸断。

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

@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();
    }
    mContentParentExplicitlySet = true;
}

然后就会走mContentParent.removeAllViews(),再接着就是mLayoutInflater.inflate(layoutResID, mContentParent),进去看看吧,这个方法不止一点重要。

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

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

if (TAG_MERGE.equals(name)) {
    if (root == null || !attachToRoot) {
        throw new InflateException("<merge /> 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().
 * <p>
 * <strong>Note:</strong> 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("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> 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 xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值