Activity是我们开发过程中最常见到的界面,然而它是怎么从layout布局文件加载显示到我们眼前的呢?接下来我们就来看看,activity界面的呈现过程,在Activity中我们通常通过setContentView()来设置layout的资源。
frameworks/base/core/java/android/app/Activity.java public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }在Activity中通过getWindow()获取PhoneWindow对象的实例,PhoneWindow是Window的唯一子类
Window类:是一个抽象类,提供了绘制窗口的一组通用API,可以将之理解为一个载体,各种View在这个载体上显示。
PhoneWindow类:该类是Window的子类,是Window类的具体实现,可以通过该类具体去绘制窗口。并且,该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。 简而言之,PhoneWindow类是把一个FrameLayout类即DecorView对象进行一定的包装,将它作为应用窗口的根View,并提供一组通用的窗口操作接口。
DecorView类 :是Fragment的一个子类,不对外提供接口,是在PhoneWindow中加载显示的,是所有应用窗口的父view
private void initWindowDecorActionBar() { private void initWindowDecorActionBar() { Window window = getWindow(); // Initializing the window decor can change window feature flags. // Make sure that we have the correct set before performing the test below. window.getDecorView(); if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) { return; } mActionBar = new WindowDecorActionBar(this); mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); mWindow.setDefaultIcon(mActivityInfo.getIconResource()); mWindow.setDefaultLogo(mActivityInfo.getLogoResource()); }调用Window里的setContentView设置要显示的content资源,初始化Activity的ActionBar,下面主要看看PhoneWindow@setContentView
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java @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; }通过mLayoutInflater.inflate把layoutResID放到mContentParent中,在设置layoutResID之前需要先得到mContentParent对象,mContentParent是通过generateLayout来获取的ViewGroup,上面mContentParent对象是ViewGroup的实例,里面可能是DecorView,或者DecorView的子类
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); ... }generateLayout里面主要是设置当前的Theme,通过mDecor.onResourcesLoaded(mLayoutInflater, layoutResource),来加载默认的layoutResource,然后返回mContentParent对象
在LayoutInflater的inflate中,通过Resources的getLayout方法获取XmlResourceParser对象的实例,上面讲到在setContentView里通过mLayoutInflater.inflate(layoutResID, mContentParent)把指定的布局放入mContentParent中
frameworks/base/core/java/android/view/LayoutInflater.java 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) + ")"); } final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }然后在public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 中解析layout布局文件,通过addView把解析出来的view加入到ViewGroup中
frameworks/base/core/java/android/view/LayoutInflater.java 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 { // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); ... 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); } 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); } } ... // Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true); ... // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; } } } ... return result; } }解析具体过程就不在分析了,主要是利用传入的ayoutResID利用pull解析xml对应标签,并放入mContentParent中,最后附上一张图,简单分析了一个简单布局的视图
