Android从xml加载到View对象过程解析
我们从activity的setcontentview()入手,开始源码解析,
//activity.setcontentview public void setcontentview(int layoutresid) { getwindow().setcontentview(layoutresid); initactionbar(); } //phonewindow.setcontentview public void setcontentview(int layoutresid) { if (mcontentparent == null) { installdecor(); } else { mcontentparent.removeallviews(); } mlayoutinflater.inflate(layoutresid, mcontentparent); final callback cb = getcallback(); if (cb != null && !isdestroyed()) { cb.oncontentchanged(); } }
发现是使用mlayoutinflater创建view的,所以我们去layoutinflater.inflate()里面看下,
public view inflate(int resource, viewgroup root, boolean attachtoroot) { if (debug) system.out.println("inflating from resource: " + resource); xmlresourceparser parser = getcontext().getresources().getlayout(resource); try { return inflate(parser, root, attachtoroot); } finally { parser.close(); } }
先根据resource id 获取到xmlresourceparseer,意如其名,就是xml的解析器,继续往下,进入到inflate的核心方法,有些长,我们只分析关键部分:
public view inflate(xmlpullparser parser, viewgroup root, boolean attachtoroot) { ...... 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, false); } else { // temp is the root view that was found in the xml view temp; if (tag_1995.equals(name)) { temp = new blinklayout(mcontext, attrs); } else { temp = createviewfromtag(root, name, attrs); } ...... } 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; } finally { // don't retain static reference on context. mconstructorargs[0] = lastcontext; mconstructorargs[1] = null; } return result; } }
如果tag的名字不是tag_1995(名字是个梗),就调用函数createviewfromtag()创建view,进去看看,
view createviewfromtag(view parent, string name, attributeset attrs) { if (name.equals("view")) { name = attrs.getattributevalue(null, "class"); } ...... view view; if (mfactory2 != null) view = mfactory2.oncreateview(parent, name, mcontext, attrs); else if (mfactory != null) view = mfactory.oncreateview(name, mcontext, attrs); else view = null; if (view == null && mprivatefactory != null) { view = mprivatefactory.oncreateview(parent, name, mcontext, attrs); } if (view == null) { if (-1 == name.indexof('.')) { view = oncreateview(parent, name, attrs); } else { view = createview(name, null, attrs); } } if (debug) system.out.println("created view is: " + view); return view; ...... }
首先尝试用3个fractory创建view,如果成功就直接返回了。注意,我们可以利用这个机制,创建自己的factory来控制view的创建过程。
如果没有factory或创建失败,那么走默认逻辑。
先判断name中是否有'.'字符,如果没有,则认为使用android自己的view,此时会在name的前面加上包名"android.view.";如果有这个'.',则认为使用的自定义view,这时无需添加任何前缀,认为name已经包含全包名了。
最终,使用这个全包名的name来创建实例,
private static final hashmap<string, constructor<? extends view>> sconstructormap = new hashmap<string, constructor<? extends view>>(); protected view oncreateview(string name, attributeset attrs) throws classnotfoundexception { return createview(name, "android.view.", attrs); } public final view createview(string name, string prefix, attributeset attrs) throws classnotfoundexception, inflateexception { constructor<? extends view> constructor = sconstructormap.get(name); class<? extends view> clazz = null; ...... if (constructor == null) { // class not found in the cache, see if it's real, and try to add it clazz = mcontext.getclassloader().loadclass( prefix != null ? (prefix + name) : name).assubclass(view.class); if (mfilter != null && clazz != null) { boolean allowed = mfilter.onloadclass(clazz); if (!allowed) { failnotallowed(name, prefix, attrs); } } constructor = clazz.getconstructor(mconstructorsignature); sconstructormap.put(name, constructor); } else { // if we have a filter, apply it to cached constructor if (mfilter != null) { // have we seen this name before? boolean allowedstate = mfiltermap.get(name); if (allowedstate == null) { // new class -- remember whether it is allowed clazz = mcontext.getclassloader().loadclass( prefix != null ? (prefix + name) : name).assubclass(view.class); boolean allowed = clazz != null && mfilter.onloadclass(clazz); mfiltermap.put(name, allowed); if (!allowed) { failnotallowed(name, prefix, attrs); } } else if (allowedstate.equals(boolean.false)) { failnotallowed(name, prefix, attrs); } } } object[] args = mconstructorargs; args[1] = attrs; return constructor.newinstance(args); ...... }
从源码中看到,在创建实例前,会先从一个静态map中获取缓存,
constructor<? extends view> constructor = sconstructormap.get(name);
缓存的是constructor对象,目的是用于创建实例,这里要注意sconstructormap是静态的,并且通过constructor创建的实例,是使用和constructor对象同一个classloader来创建的,换句话说,在同一个进程中,同一个自定义view对象,是无法用不同classloader加载的,如果想解决这个问题,就不要让系统使用createview()接口创建view,做法就是自定义factory或factory2来自行创建view。
继续往下看,如果缓存里没有,则创建view的class对象clazz,并缓存到sconstructormap中,
if (constructor == null) { // class not found in the cache, see if it's real, and try to add it clazz = mcontext.getclassloader().loadclass( prefix != null ? (prefix + name) : name).assubclass(view.class); if (mfilter != null && clazz != null) { boolean allowed = mfilter.onloadclass(clazz); if (!allowed) { failnotallowed(name, prefix, attrs); } } constructor = clazz.getconstructor(mconstructorsignature); sconstructormap.put(name, constructor); }
然后就是newinstance了,至此这个view便从xml中变成了java对象,我们继续返回到inflate函数中,看看这个view返回之后做了什么,
...... // temp is the root view that was found in the xml view temp; if (tag_1995.equals(name)) { temp = new blinklayout(mcontext, attrs); } else { temp = createviewfromtag(root, name, attrs); } viewgroup.layoutparams params = null; if (root != null) { // 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 rinflate(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;
从createviewfromtag返回后,会调用个rinflate(),其中parent参数就是刚才创建出的view,应该能猜到里面做了什么,
void rinflate(xmlpullparser parser, view parent, final attributeset attrs, boolean finishinflate) 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 if (tag_1995.equals(name)) { final view view = new blinklayout(mcontext, attrs); final viewgroup viewgroup = (viewgroup) parent; final viewgroup.layoutparams params = viewgroup.generatelayoutparams(attrs); rinflate(parser, view, attrs, true); viewgroup.addview(view, params); } else { final view view = createviewfromtag(parent, name, attrs); final viewgroup viewgroup = (viewgroup) parent; final viewgroup.layoutparams params = viewgroup.generatelayoutparams(attrs); rinflate(parser, view, attrs, true); viewgroup.addview(view, params); } } if (finishinflate) parent.onfinishinflate(); }
没错,就是递归的使用createviewfromtag()创建子view,并通过viewgroup.addview添加到parent view中。
之后,这个view树上的所有view都创建完毕。然后会根据inflate()的参数(root和attachtoroot)判断是否将新创建的view添加到root view中,
if (root != null && attachtoroot) { root.addview(temp, params); }
然后,inflate()就将view返回了。
以上内容是小编给大家介绍的android从xml加载到view对象过程解析,希望对大家有所帮助!