Activity界面加载流程

我们都知道在Activity的OnCreate()方法中调用了setContentView()去添加我们的layout,不调用它就看不到界面了,我们来看一下这个方法究竟做了什么,Activity的界面是如何加载的。

protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
}

进到setContentView()方法。

public void setContentView(@LayoutResintlayoutResID){
	getWindow().setContentView(layoutResID);
	initWindowDecorActionBar();
}

调用getWindow的setContentView,先看一下getWindow()。

private Window mWindow;
...
public Window getWindow(){
	return mWindow;
}

返回一个mWindow实例,这个mWindow是在哪里赋值的。
在Activity的启动流程中,我们知道最终会调用ActivityThread类的performLaunchActivity()方法创建Activity实例,接着调用activity的attach方法,最后调用OnCreate()方法。我们进到Activity类的attach()方法看一下。

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, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
	attachBaseContext(context);

	mFragments.attachHost(null /*parent*/);
	//创建PhoneWindow,这里传入的window是null
	mWindow = new PhoneWindow(this, window, activityConfigCallback);
	mWindow.setWindowControllerCallback(this);
	mWindow.setCallback(this);
	mWindow.setOnWindowDismissedCallback(this);
	mWindow.getLayoutInflater().setPrivateFactory(this);
	if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
		mWindow.setSoftInputMode(info.softInputMode);
	}
	if (info.uiOptions != 0) {
		mWindow.setUiOptions(info.uiOptions);
	}
	...
}

创建了一个PhoneWindow,并赋值给mWindow。所以前面通过getWindow()得到的是一个PhoneWindow实例。创建PhoneWindow时做了什么,看一下PhoneWindow的构造函数。

public PhoneWindow(Context context, Window preservedWindow,
		ActivityConfigCallback activityConfigCallback) {
	this(context);
	// Only main activity windows use decor context, all the other windows depend on whatever
	// context that was given to them.
	mUseDecorContext = true;
	//preservedWindow为null,不会为mDecor赋值
	if (preservedWindow != null) {
		mDecor = (DecorView) preservedWindow.getDecorView();
		mElevation = preservedWindow.getElevation();
		mLoadElevation = false;
		mForceDecorInstall = true;
		// If we're preserving window, carry over the app token from the preserved
		// window, as we'll be skipping the addView in handleResumeActivity(), and
		// the token will not be updated as for a new window.
		getAttributes().token = preservedWindow.getAttributes().token;
	}
	// Even though the device doesn't support picture-in-picture mode,
	// an user can force using it through developer options.
	boolean forceResizable = Settings.Global.getInt(context.getContentResolver(),
			DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
	mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature(
			PackageManager.FEATURE_PICTURE_IN_PICTURE);
	mActivityConfigCallback = activityConfigCallback;
}

因为preservedWindow为空,不会执行if中的代码,到这里mDecor还没有被赋值。
继续看代码,现在我们知道了是调用了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为空,会执行installDecor()方法。

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);
		...
	}
}

前面已经看到了,mDecor为空,所以会调用generateDecor()为mDecor赋值。

protected DecorView generateDecor(int featureId) {
	// System process doesn't have application context and in that case we need to directly use
	// the context we have. Otherwise we want the application context, so we don't cling to the
	// activity.
	Context context;
	if (mUseDecorContext) {
		Context applicationContext = getContext().getApplicationContext();
		if (applicationContext == null) {
			context = getContext();
		} else {
			context = new DecorContext(applicationContext, getContext());
			if (mTheme != -1) {
				context.setTheme(mTheme);
			}
		}
	} else {
		context = getContext();
	}
	return new DecorView(context, featureId, this, getAttributes());
}

返回时创建了一个DecorView()实例。回到installDecor()方法,mContentParent为空,调用了generateLayout()方法为mContentParent赋值。

protected ViewGroup generateLayout(DecorView decor) {
	...

	// Inflate the window decor.

	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;
		setCloseOnSwipeEnabled(true);
	} 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 {
			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!");
	}

	mDecor.startChanging();
	mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

	ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	if (contentParent == null) {
		throw new RuntimeException("Window couldn't find content container view");
	}

	...

	return contentParent;
}

首先为layoutResource选择一套布局资源,以R.layout.screen_title为例

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <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:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

可以看到,布局里面有两个FrameLayout,上面的里面包括一个id引用为@android:id/title的TextView,下面的里面是空的,id引用为@android:id/content。
然后调用了decroView的onResourcesLoaded()方法

扫描二维码关注公众号,回复: 9053523 查看本文章
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
	if (mBackdropFrameRenderer != null) {
		loadBackgroundDrawablesIfNeeded();
		mBackdropFrameRenderer.onResourcesLoaded(
				this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
				mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
				getCurrentColor(mNavigationColorViewState));
	}

	mDecorCaptionView = createDecorCaptionView(inflater);
	final View root = inflater.inflate(layoutResource, null);
	if (mDecorCaptionView != null) {
		if (mDecorCaptionView.getParent() == null) {
			addView(mDecorCaptionView,
					new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
		}
		mDecorCaptionView.addView(root,
				new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
	} else {

		// Put it below the color views.
		addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
	}
	mContentRoot = (ViewGroup) root;
	initializeElevation();
}

调用inflate()方法创建了View实例。

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
	final Resources res = getContext().getResources();
	if (DEBUG) {
		Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
			  + Integer.toHexString(resource) + ")");
	}

	View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
	if (view != null) {
		return view;
	}
	XmlResourceParser parser = res.getLayout(resource);
	try {
		return inflate(parser, root, attachToRoot);
	} finally {
		parser.close();
	}
}

继续调用inflate方法

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
	synchronized (mConstructorArgs) {
		Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

		final Context inflaterContext = mContext;
		final AttributeSet attrs = Xml.asAttributeSet(parser);
		Context lastContext = (Context) mConstructorArgs[0];
		mConstructorArgs[0] = inflaterContext;
		View result = root;

		try {
			...
			if (TAG_MERGE.equals(name)) {
				...
			} else {
				// Temp is the root view that was found in the xml
				final View temp = createViewFromTag(root, name, inflaterContext, attrs);

				ViewGroup.LayoutParams params = null;

				if (root != null) {
					if (DEBUG) {
						System.out.println("Creating params from root: " +
								root);
					}
					// Create layout params that match root, if supplied
					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);
					}
				}

				if (DEBUG) {
					System.out.println("-----> start inflating children");
				}

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

		} catch (XmlPullParserException e) {
			...
		}

		return result;
	}
}

调用createViewFromTag()方法。

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
		boolean ignoreThemeAttr) {
	...
	try {
		//尝试创建View
		View view = tryCreateView(parent, name, context, attrs);

		if (view == null) {
			final Object lastContext = mConstructorArgs[0];
			mConstructorArgs[0] = context;
			try {
				if (-1 == name.indexOf('.')) {
					view = onCreateView(context, parent, name, attrs);
				} else {
					view = createView(context, name, null, attrs);
				}
			} finally {
				mConstructorArgs[0] = lastContext;
			}
		}

		return view;
	} catch (InflateException e) {
		...
	}
}

首先调用tryCreateView尝试创建view

public final View tryCreateView(@Nullable View parent, @NonNull String name,
	…
	View view;
	if (mFactory2 != null) {
		view = mFactory2.onCreateView(parent, name, context, attrs);
	} else if (mFactory != null) {
		view = mFactory.onCreateView(name, context, attrs);
	} else {
		view = null;
	}

	if (view == null && mPrivateFactory != null) {
		view = mPrivateFactory.onCreateView(parent, name, context, attrs);
	}

	return view;
}

分别判断了两个factory是否为空,如果设置了factory,则调用对应factory的oncreateView方法创建view,如果未设置factory,则返回null,说明尝试创建view失败。如果我们的Activity是继承了android.app.Activity而且没有自己设置的话,这两个factory是为空的。这个不是现在关注的重点。
回到createViewFromTag方法,因为tryCreateView返回的null,会执行下面try中的if判断。

if (view == null) {
	final Object lastContext = mConstructorArgs[0];
	mConstructorArgs[0] = context;
	try {
		if (-1 == name.indexOf('.')) {
			view = onCreateView(context, parent, name, attrs);
		} else {
			view = createView(context, name, null, attrs);
		}
	} finally {
		mConstructorArgs[0] = lastContext;
	}
}

通过name.indexOf(’.’)判断控件中是否含有“.”。如果含有,说明这是一个自定义控件。没有"."的话说明是系统原生的控件,比如Button、TexxtView等。然后调用onCreateView或createView方法根据控件类型创建View。
回到inflate方法,接着会调用rInflateChildren()方法

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
		boolean finishInflate) throws XmlPullParserException, IOException {
	rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

调用rInflate

void rInflate(XmlPullParser parser, View parent, Context context,
		AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

	final int depth = parser.getDepth();
	int type;
	boolean pendingRequestFocus = false;

	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)) {
			...
		} 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);
		}
	}

	...
}

在这个方法中,通过不断地调用createViewFromTag创建view,然后将这个view作为parent继续调用rInflateChildren()方法创建它的子view,从外向内一层一层创建。现在解析的布局就是前面layoutResource对应的xml资源,这时我们已经将该资源加载到mDecor中。
回到generateLayout()方法,通过findViewById()找到id为com.android.internal.R.id.content的view。

/**
 * The ID that the main layout in the XML layout file should have.
 */
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

看一下findviewbyid这个方法。

public <T extends View> T findViewById(@IdRes int id) {
    return getDecorView().findViewById(id);
}

调用了getDecorView()的findViewById()。

@Override
public final View getDecorView() {
    if (mDecor == null || mForceDecorInstall) {
        installDecor();
    }
    return mDecor;
}

这里返回了mDecor,前面已经创建过了。这说明ID_ANDROID_CONTENT这个id对应的布局是在已经创建了的DecorView中查找的,其实就是我们加载的那个layout中id引用为@android:id/content的FrameLayout布局。
回到generateLayout()方法,最后返回了这个FrameLayout赋值给了mContentParent。
再回到PhoneWindow的setContentView()方法。

public void setContentView(int layoutResID) {
	...
	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);
	}
	...
}

调用mLayoutInflater.inflate(layoutResID, mContentParent),其中mContentParent已经创建过了,layoutResID就是我们在onCreate()方法中调用setContentView()传入的自己写的xml。inflate()方法前面已经用到过一次了,是将我们的xml一层一层创建出来,并加载到mContentParent中。
到这里整个setContentView()方法做的事情就基本完成了。从installDecor()方法创建DecroView并加载系统的根布局,到加载我们自己写的布局到根布局上。但是这一步仅仅是加载了各个xml资源,告诉系统有哪些东西需要显示,将这些东西准备好,并没有完成界面的绘制和显示。

发布了10 篇原创文章 · 获赞 19 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/songkai0825/article/details/103674643