Android源码:xml文件是如何加载到屏幕上的(一)

请尊重原创,转载请注明出处【tianyl_Melody】的博客

1、说明

在Android中,我们的布局通常是通过先写在xml文件中,然后通过setContentView这个方法,就能将布局加载到对应的Activity中的,那么这个xml的布局文件到底在加载到Activity中,做了哪些事情呢,今天,就从setContentView这个方法出发,看看xml文件是如何加载到屏幕上的。

2、setContentView

首先,从activity的setContentView出发

public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
}
public Window getWindow() {
        return mWindow;
    }

通过这两个方法,我们可以看到它首先是拿到了一个mWindow对象,然后调用了这个mWindow对象的setContentView方法。而Window是一个抽象类,它仅有一个实现类,那就是PhoneWindow。

3、PhoneWindow

if (mContentParent == null) {
      installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
      mContentParent.removeAllViews();
}

可见,在PhoneWindow中,首先会判断mContentParent是否为null,如果为null,那么会调用installDecor,可以猜测这是一个创建mContentParent的方法,如果不为null,那么就移除掉所有的子view进行复用。

4、installDecor

在installDecor方法中,首先会判断mDecor是否为null,如果mDecor对象也为null,那么就会调用generateDecor方法。

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

在generateDecor方法中,会创建一个PhoneWindow的内部类DecorView对象;

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

在创建完DecorView之后,会对DecorView的属性做一些设置;然后再判断mContentParent是否为null,如果mContentParent为null,那么把刚刚创建的mDecor作为参数传入,调用generateLayout方法;

mContentParent = generateLayout(mDecor)

5、generateLayout

generateLayout这个方法主要就是对window的一些属性进行设定。首先,它会拿到我们在xml中设置的window属性;

TypedArray a = getWindowStyle();
 public final TypedArray getWindowStyle() {
    synchronized (this) {
        if (mWindowStyle == null) {
            mWindowStyle = mContext.obtainStyledAttributes(
            com.android.internal.R.styleable.Window);
        }
    return mWindowStyle;
    }
}

然后再对这些属性进行实现,比如我们经常使用的Window_windowNoTitle:

if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
     requestFeature(FEATURE_NO_TITLE);
}

它会通过requestFeature(int featureId)这个方法来将我们设置的window属性保存起来,将所有属性保存完之后,就会通过这个方法进行取出:

int features = getLocalFeatures();

然后加载对应的xml布局文件,比如:

if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
     layoutResource = R.layout.screen_swipe_dismiss;
}

做完这些事情之后,就开始给DecorView添加布局了:

mDecor.startChanging();
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);

首先,mDecor.startChanging():

public void startChanging() {
    mChanging = true;
}

然后,会将我们之前拿到的xml文件填充出来,并且通过addView添加到decor中:

 decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

其中,LayoutParams我们可以看到都是MATCH_PARENT。然后填充出来是view会当成mContentRoot,而contentParent就是我们之前的xml文件中,id为content的布局。

最后,会将我们构建的contentParent返回出去:

return contentParent;

这些就是我们installDecor做的主要过程了,接下来就是这段代码:

if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
          getContext());
    transitionTo(newScene);
} else {
      mLayoutInflater.inflate(layoutResID, mContentParent);
}

首先会判断FEATURE_CONTENT_TRANSITIONS这个值,FEATURE_CONTENT_TRANSITIONS是Android中的过渡,简单理解就是界面跳转的一些动画,如果没有,那么就调用inflate方法,并且把外界传入的layoutResID和自己构建出来的mContentParent作为对象传入。

6、inflate

在上面的installDecor中,最后调用到了inflate方法,而inflate最终会调用到这个方法:

public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
   final Resources res = getContext().getResources();
   if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
   }
   final XmlResourceParser parser = res.getLayout(resource);
   try {
       return inflate(parser, root, attachToRoot);
   } finally {
       parser.close();
   }
}

这里,会将Resources传入,拿到一个对应的xml的解析器parser,然后再调用inflate方法:

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
    //首先会拿到xml布局中的属性
    final AttributeSet attrs = Xml.asAttributeSet(parser);
    ......
    //然后不断的循环,找到xml布局中的根节点
    while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
    //然后拿到这个跟节点的名字
    final String name = parser.getName();
    ......
    //如果这个跟节点不是merge,那么就会生成对应的根节点
    final View temp = createViewFromTag(root, name, attrs, false);
    ......
    //然后通过root的generateLayoutParams方法,拿到对应的属性,设置到刚生成的根节点布局中
    params = root.generateLayoutParams(attrs);
    if (!attachToRoot) {
        // Set the layout params for temp if we are not
        // attaching. (If we are, we use addView, below)
        temp.setLayoutParams(params);
    }
}

其中root就是在之前的方法installDecor中创建并且返回出来的contentParent对象,而根节点就是我们自己写的布局文件的最外层布局,比如LinearLayout。然后通过contentParent来解析出LinearLayout的对应属性,通过setLayoutParams给LinearLayout设置上。

7、总结

1、在setContentView中,首先,会判断mDecor是否为null,如果为null,那么就会new一个DecorView给mDecor赋值;
2、然后在getWindowStyle方法中拿到xml中设置的window属性通过requestFeature方法保存到对象中。
3、接下来通过getLocalFeatures取出window的属性对象,并且根据对象的值,加载对应的布局文件。
4、通过mLayoutInflater.inflate填充出window对应的布局文件,并且通过addview添加到mDecor中,作为mDecor的child对象。而填充出来的布局文件就是contentParent,返回出来给外面使用。
5、在inflate中,首先会拿到从setContentView中传入的布局id,并且通过这个id,得到对应的xml解析器。
6、在xml解析器中,拿到跟布局,然后创建出跟布局的根节点,通过contentParent的generateLayoutParams方法,将从xml解析器中拿到的属性对象attrs传入,拿到对应的属性参数,最后将这个属性参数设置给布局的根节点temp。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值