android中xml布局文件加载流程
简述
当我们在XML写好布局,然后通过Activity中onCreate方法中setContentView(R.layout.activity_path_measure);就可以把布局加载进入,然后把布局界面展现在我们眼前.十分简单,但是你是否了解它加载的过程........先让我们看一下图吧
image.png
加载过程
- Activity中onCreate方法中,通过setContentView方法把布局文件加载进去
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//加载布局文件
setContentView(R.layout.activity_path_measure);
}
-
setContentView方法是调用了setContentView(@LayoutRes int layoutResID)方法
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
在这里我们主要看一下 getWindow().setContentView(layoutResID);这个方法
-
getWindow()是干嘛呢,请看看下面的代码
public Window getWindow() { return mWindow; }
这mWindow又是什么玩意呢,我们慢慢找,我们可以在attach(,,,) 方法中找到mWindow生成方法;
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
//这里就是mWindow的创建
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
//在这里省略了好多代码..........
}
从上面代码我们可以看出,mWindow其实是一个PhoeWindow,它是一个窗体,继承Window;
-
好了,我们理解了 getWindow就是得到一个PhoeWindow,这里我们就可以回到以前那个方法
getWindow().setContentView(layoutResID);
在这里我们得找到PhoeWindow这个类,然后找到setContentView这个方法,看到底是怎么一个加载过程,看一下PhoeWindow中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.
//这个是第一步(1),mContentParent是什么玩意,其实他就是一个 ViewGroup容器,待把布局加载进去
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 {
//这是第二步(2)
//把布局加载进去,这个方法这里我就不深入了,以后有时间我单独讲一下LayoutInflater.inflate
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
从上面的源码我们分析得到,加载布局主要有二个步骤,刚开始mContentParent肯定是null,为null时走 installDecor();这个方法,这个后面会深入的讲解,不为null走mContentParent.removeAllViews()这个方法,这个方法很好理解,就是清空所有已加载的View,第二步就是把布局加载mContentParent中,mLayoutInflater.inflate(layoutResID, mContentParent);这个方法这里我不深入去讲解,有时间后面再单独拿出来讲解;
-
mContentParent这个是什么玩意呢...........先不说,看一下它为null时,走 installDecor()方法是否会赋值;
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) { //我擦,就是这里赋值的,,,,,,哈哈好好看看这个方法,看这个方法前,要先了解mDecor 是什么玩意 mContentParent = generateLayout(mDecor); // Set up decor part of UI to ignore fitsSystemWindows if appropriate. mDecor.makeOptionalFitsSystemWindows(); //这里也省略了N多代码 } }
从上面源码,我们可以看到mContentParent是在这里赋值的,我们好好研究generateLayout(mDecor)这个方法,在研究这个方法前,我们把搞清楚mDecor又是什么玩意;
-
mDecor又是什么玩意呢,直接看源码
protected DecorView generateDecor() { return new DecorView(getContext(), -1); }
从上面源码可以看出,mDecor就是 DecorView, DecorView是一个FrameLayout,看它的源码就很直观;
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
/* package */int mDefaultOpacity = PixelFormat.OPAQUE;
/** The feature ID of the panel, or -1 if this is the application's DecorView */
private final int mFeatureId;
private final Rect mDrawingBounds = new Rect();
private final Rect mBackgroundPadding = new Rect();
private final Rect mFramePadding = new Rect();
private final Rect mFrameOffsets = new Rect();
//又省略N多代码.......................................
}
-
好了,我们着重看一下 mContentParent 是如何赋值的,看一下 generateLayout(mDecor)的源码,我只是把重要的写出来,其他都省略了,为了不会看着头疼...................
protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. //这里得到主题样式,这个很好理解 TypedArray a = getWindowStyle(); //.............................省略了些................ mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false); int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) & (~getForcedWindowFlags()); if (mIsFloating) { setLayout(WRAP_CONTENT, WRAP_CONTENT); setFlags(0, flagsToUpdate); } else { setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); } 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); } //又要省略一大部分 //以下代码就是根据样式加载系统的xml布局,, int layoutResource; int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { // xml布局.可以从系统源码找到 layoutResource = R.layout.screen_title_icons; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); // System.out.println("Title Icons!"); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { // Special case for a window with only a progress bar (and title). // XXX Need to have a no-title version of embedded windows. layoutResource = R.layout.screen_progress; // System.out.println("Progress!"); } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { // Special case for a window with a custom title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_custom_title; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); } 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!"); } //以下几行代码是重点 View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in; //contentParent就是这里赋值的,findViewById找到com.android.internal.R.id.content;这个ID的FrameLayout ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); return contentParent;
}
从上面源码我们可以contentParent就是com.android.internal.R.id.content这个ID的FrameLayout; 而com.android.internal.R.id.content这个ID就是上面layoutResource布局中FrameLayout;在这里还是让大家看一下layoutResource布局文件吧,
<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" />
//contentParent就是这个玩意啊...............................
<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>
好了,我们终于了解到contentParent是什么了,我们回到前面讲的installDecor()方法,这样就可以进行第二个步骤了 mLayoutInflater.inflate(layoutResID, mContentParent);就我们的布局加载到contentParent这个FrameLayout去了.差不多就这些了!总的看一下以下图;
image.png