这是我自己参考了网上的一些文章做得小总结,也许有错误,也有解释不对的地方,请指正,一起进步吧!我尽量做到不打错别字,用词正确,不造成阅读障碍。
源码是有变化的,但大体的结构差不多,依旧有很大的参考价值。
一.从ContentView开始
我觉得应该从一个Activity的onCreate方法中的setContentView的方法讲解可能会让人更好理解。
点击setContentView的源码:
public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
其实它有三个方法,还有两个在这:
publicvoidsetContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
publicvoidsetContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
可以看见他们都先调运了getWindow()的setContentView方法,然后调运Activity的initWindowDecorActionBar方法,
关于initWindowDecorActionBar方法并不是重点,所以暂时跳过不解释。
看getWindow().setContentView(view, params);其中getWindow()返回一个Window:
public Window getWindow() { return mWindow; }
而Window是一个抽象类,来张图;
Activity中有一个成员为Window,其实例化对象为PhoneWindow,PhoneWindow为抽象Window类的实现类。Window类上有一段注释:
The only existing implementation of this abstract class is android.policy.PhoneWindow, which you should instantiate when needing a Window. Eventually that class will be refactored and a factory method added for creating Window instances without knowing about a particular implementation.
注释已经很好的说明了PhoneWindow与Window的关系
这里先简要说明下这些类的职责:
Window是一个抽象类,提供了绘制窗口的一组通用API。
PhoneWindow是Window的具体继承实现类。而且该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。
DecorView是PhoneWindow的内部类,是FrameLayout的子类,是对FrameLayout进行功能的修饰(所以叫DecorXXX),是所有应用窗口的根View 。
依据面向对象从抽象到具体我们可以类比上面关系就像如下:
Window是一块电子屏,PhoneWindow是一块手机电子屏,DecorView就是电子屏要显示的内容,Activity就是手机电子屏安装位置。
注意PhoneWindow是internal的,也就是不太容易找到,我是在SDK中sources/android-21/com/android/internal/policy/impl下找到的。
所以我们就要去看PhoneWindow的setContentView方法了。
巧了!它也有三个setContentView方法,先看第一个:
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); } final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } }可以看见,第五行首先判断mContentParent是否为null,也就是第一次调运);如果是第一次调用,则调用installDecor()方法,否则判断是否设置FEATURE_CONTENT_TRANSITIONS Window属性(默认false),如果没有就移除该mContentParent内所有的所有子View;接着16行mLayoutInflater.inflate(layoutResID, mContentParent);
将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中(其中mLayoutInflater是在PhoneWindow的构造函数中得到实例对象的LayoutInflater.from(context);
)。
其他的方法先过滤掉,其实里面有买很多知
识点,比如installDecor就有很多代码,包括初始化一堆属性,按照主题style设置界面等等。我感觉重点是mLayoutInflater.inflate方法。而第二个方法:
public void setContentView(View view) { setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); }第三个方法:
public void setContentView(View view, ViewGroup.LayoutParams params) { // 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)) { view.setLayoutParams(params); final Scene newScene = new Scene(mContentParent, view); transitionTo(newScene); } else { mContentParent.addView(view, params); } final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } }第二个方法就是调用第三个方法啊!也就是重点是第三个方法,而且第一个方法和第三个方法很像,还是先看第一个方法吧,毕竟有我们熟悉的内容。
重点是mLayoutInflater.inflate(...)看代码:
public View inflate(int resource, ViewGroup root) { return inflate(resource, root, root != null); }呵呵,调了另一个方法,再看看这个方法:
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(); } }我传递的int类型的资源Id在这里res.getLayout(resourse);成了parser,而XmlResourseParser是个接口啊,这是要解析我的xml文件的节奏啊!最后又进入了一个方法inflate(parser,root,attachToRoot);去看看:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final AttributeSet attrs = Xml.asAttributeSet(parser); mConstructorArgs[0] = mContext; View result = root; try { int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { } 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, attrs); } else { View temp = createViewFromTag(name, attrs); ViewGroup.LayoutParams params = null; if (root != null) { params = root.generateLayoutParams(attrs); if (!attachToRoot) { temp.setLayoutParams(params); } } rInflate(parser, temp, attrs); if (root != null && attachToRoot) { root.addView(temp, params); } if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (IOException e) { InflateException ex = new InflateException( parser.getPositionDescription() + ": " + e.getMessage()); ex.initCause(e); throw ex; } return result; } }从这里我们就可以清楚地看出,LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的。这里我们注意看下第23行,调用了createViewFromTag()这个方法,并把节点名和参数传了进去。看到这个方法名,我们就应该能猜到,它是用于根据节点名来创建View对象的。确实如此,在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。
当然,这里只是创建出了一个根布局的实例而已,接下来会在第31行调用rInflate()方法来循环遍历这个根布局下的子元素,代码如下所示:
private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; 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)) { parseRequestFocus(parser, parent); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else { final View view = createViewFromTag(name, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs); viewGroup.addView(view, params); } } parent.onFinishInflate(); }可以看到,在第21行同样是createViewFromTag()方法来创建View的实例,然后还会在第24行递归调用rInflate()方法来查找这个View下的子元素,每次递归完成后则将这个View,addView到ViewGroup当中。
这样的话,把整个布局文件都解析完成后就形成了一个完整的DOM结构,最终会把最顶层的根布局返回,至此inflate()过程全部结束。
参考资料:http://blog.csdn.net/guolin_blog/article/details/12921889
http://blog.csdn.net/yanbober/article/details/45970721新闻热点
疑难解答