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

xiaoxiao2021-02-28  58

请尊重原创,转载请注明出处【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。

转载请注明原文地址: https://www.6miu.com/read-85618.html

最新回复(0)