layoutinflater
开头先附一段layoutinflater类的注释简介
/** * instantiates a layout xml file into its corresponding {@link android.view.view} * objects. it is never used directly. instead, use * {@link android.app.activity#getlayoutinflater()} or * {@link context#getsystemservice} to retrieve a standard layoutinflater instance * that is already hooked up to the current context and correctly configured * for the device you are running on. * * to create a new layoutinflater with an additional {@link factory} for your * own views, you can use {@link #cloneincontext} to clone an existing * viewfactory, and then call {@link #setfactory} on it to include your * factory. * * for performance reasons, view inflation relies heavily on pre-processing of * xml files that is done at build time. therefore, it is not currently possible * to use layoutinflater with an xmlpullparser over a plain xml file at runtime; * it only works with an xmlpullparser returned from a compiled resource * (r.something file.) */
这是layoutinflater开头的一段介绍,我们能看到几个重要的信息:
- layoutinfalter的作用是把xml转化成对应的view对象,需要用
activity#getlayoutinflater()
或者getsystemservice
获取,会绑定当前的context - 如果需要使用自定义的
factory
查看替换加载信息,需要用cloneincontext去克隆一个viewfactory
,然后调用setfactory
设置自定义的factory - 性能原因,inflate会在build阶段进行,无法用来加载一个外部文本xml文件,只能加载r.xxx.xxx这种处理好的资源文件。
//layoutinflater.java public view inflate(@layoutres int resource, @nullable viewgroup root) { //root是否为null来决定attachtoroot是否为true。 return inflate(resource, root, root != null); } public view inflate(xmlpullparser parser, @nullable viewgroup root) { return inflate(parser, root, root != null); } public view inflate(@layoutres int resource, @nullable viewgroup root, boolean attachtoroot) { final resources res = getcontext().getresources(); ... final xmlresourceparser parser = res.getlayout(resource); try { return inflate(parser, root, attachtoroot); } finally { parser.close(); } } //三个inflate方法最终都会调用到下面这个三个参数的inflate方法。 /** * parser xml节点包含了view的层级描述 * root 需要attached到的根目录,如果attachtoroot为true则root必须不为null。 * attachtoroot 加载的层级是否需要attach到rootview, * return attachtoroot为true,就返回root,反之false就返回加载的xml文件的根节点view。 */ public view inflate(xmlpullparser parser, @nullable viewgroup root, boolean attachtoroot) { synchronized (mconstructorargs) { 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("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) { // 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); } } 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; } } } catch (xmlpullparserexception e) { ... } return result; } }
inflate方法使用xmlpullparser解析xml文件,并根据得到的标签名执行不同的逻辑:
- 首先如果是
merge
标签,会走rinflate方法,方法前面带r
的说明是recurse
递归方法 - 如果不是
merge
标签,执行createviewfromtag
,根据传入的name
和attrs
获取到name对应的rootview并且添加到root里面。
针对merge
标签,如果是merge
标签必须有root并且必须attachtoroot==true
,否则直接抛异常,所以我们得知merge必须作为root标签使用,并且不能用在子标签中①,rinflate
方法中也会针对merge
标签进行检查,保证merge
标签不会出现在子标签中,后面会有介绍。
检查通过则调用rinflate(parser, root, inflatercontext, attrs, false)
方法,递归遍历root的层级,解析加载childrenview挂载到parentview下面,rinflate详细解析可以看。
如果不是merge标签则调用createviewfromtag(root, name, inflatercontext, attrs)
,这个方法的作用是加载名字为name的view
,根据name反射方式创建对应的view,根据传入的attrs
构造params设置给view,返回创建好的view。
当然这只是创建了一个view,需要再调用rinflatechildren(parser, temp, attrs, true)
,这个方法也是一个递归方法,它的作用是根据传入的parser包含的层级,加载此层级的子view并挂载到temp
下面。
createviewfromtag
view createviewfromtag(view parent, string name, context context, attributeset attrs, boolean ignorethemeattr) { if (name.equals("view")) { name = attrs.getattributevalue(null, "class"); } // apply a theme wrapper, if allowed and one is specified. // 如果传入的attr中包含theme属性,则使用此attr中的theme。 if (!ignorethemeattr) { final typedarray ta = context.obtainstyledattributes(attrs, attrs_theme); final int themeresid = ta.getresourceid(0, 0); if (themeresid != 0) { context = new contextthemewrapper(context, themeresid); } ta.recycle(); } if (name.equals(tag_1995)) { // let's party like it's 1995! return new blinklayout(context, attrs); } try { 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); } if (view == null) { final object lastcontext = mconstructorargs[0]; mconstructorargs[0] = context; try { if (-1 == name.indexof('.')) { view = oncreateview(parent, name, attrs); } else { view = createview(name, null, attrs); } } finally { mconstructorargs[0] = lastcontext; } } return view; } catch (exception e) { ... } }
先看当前标签的attr属性里面是否设置了theme,如果设置了就用当前标签的theme属性,绑定到context上面。 这里很有意思的是特殊判断了一个tag_1995
,也就是blink
,一个将包裹的内容每隔500ms
显示隐藏的一个标签,怎么看都像个彩蛋~
然后调用mfactory2
的oncreateview
,如果没有设置mfactory2
就尝试mfactory
,否则调用mprivatefactory
,mfactory2和mfactory后面再说,这里先往后走。
如果还是没有加载到view,先判断name,看名字里是不是有.
,如果没有就表明是android原生的view,最终都会调用到createview
方法,oncreateview
最终会调用到createview(name, "android.view.", attrs);
,会在view名字天面添加"android.view."前缀。
下面是默认的createview的实现:
@nullable public final view createview(@nonnull context viewcontext, @nonnull string name, @nullable string prefix, @nullable attributeset attrs) throws classnotfoundexception, inflateexception { objects.requirenonnull(viewcontext); objects.requirenonnull(name); constructor constructor = sconstructormap.get(name); if (constructor != null && !verifyclassloader(constructor)) { constructor = null; sconstructormap.remove(name); } class clazz = null; try { if (constructor == null) { // class not found in the cache, see if it's real, and try to add it clazz = class.forname(prefix != null ? (prefix name) : name, false, mcontext.getclassloader()).assubclass(view.class); if (mfilter != null && clazz != null) { boolean allowed = mfilter.onloadclass(clazz); if (!allowed) { failnotallowed(name, prefix, viewcontext, attrs); } } constructor = clazz.getconstructor(mconstructorsignature); constructor.setaccessible(true); 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 = class.forname(prefix != null ? (prefix name) : name, false, mcontext.getclassloader()).assubclass(view.class); boolean allowed = clazz != null && mfilter.onloadclass(clazz); mfiltermap.put(name, allowed); if (!allowed) { failnotallowed(name, prefix, viewcontext, attrs); } } else if (allowedstate.equals(boolean.false)) { failnotallowed(name, prefix, viewcontext, attrs); } } } object lastcontext = mconstructorargs[0]; mconstructorargs[0] = viewcontext; object[] args = mconstructorargs; args[1] = attrs; try { final view view = constructor.newinstance(args); if (view instanceof viewstub) { // use the same context when inflating viewstub later. final viewstub viewstub = (viewstub) view; viewstub.setlayoutinflater(cloneincontext((context) args[0])); } return view; } finally { mconstructorargs[0] = lastcontext; } } catch (exception e) { ... } finally { trace.traceend(trace.trace_tag_view); } }
这个方法可以看到view是怎么创建出来的,用类的全限定名拿到class信息,有一个sconstructormap
缓存类的constructor
,如果能拿到有效的构造器就不再重复创建来提升效率,如果没有缓存的构造器,就反射得到构造器并添加到sconstructormap
中以便后面使用。这里有个mfilter
来提供自定义选项,用户可以自定义哪些类不允许构造。
拿到构造器之后,实际上newinstance
是调用了两view
个参数的构造方法。第一个参数是context
,第二个参数是attrs
,这样我们就得到了需要加载的view。
这里可以结合layoutinflater.factory2
一起来看,activity实际上是实现了layoutinflater.factory2
接口的:
//activity.java public view oncreateview(@nonnull string name, @nonnull context context, @nonnull attributeset attrs) { return null; }
所以我们可以直接在activity里面重写oncreateview
方法,这样就可以根据view的名字来实现我们的一些操作,比如换肤的操作,比如定义一个名字来表示某种自定义view。可以看这样一个用法:
然后我们在重写的oncreateview
里面判断name:
@override public view oncreateview(string name, context context, attributeset attrs) { if ("placeholder".equals(name)) { return new textview(this, attrs); } return super.oncreateview(name, context, attrs); }
这样其实就不拘泥于名字可以自己创建对应的view,这样其实可以用在多个module依赖的时候,如果在modulea中得不到moduleb的某个自定义view,可以使用一个这样的方式来在moudlea中暂时的用来做一个占位标记,在moduleb中做一个判断。
同样的,通过上面的代码我们知道layoutinflater是通过反射拿到构造方法来创建view的,那众所周知反射是有性能损耗的,那么我们可以在oncreateview
方法中判断名字直接new出来,当然也可以跟appcompatactivity里面做的一样,做一些兼容的操作来替换成不同版本的view:
public final view createview(view parent, final string name, @nonnull context context, view view = null; switch (name) { case "textview": view = new appcompattextview(context, attrs); break; case "imageview": view = new appcompatimageview(context, attrs); break; case "button": view = new appcompatbutton(context, attrs); break; case "edittext": view = new appcompatedittext(context, attrs); break; case "spinner": view = new appcompatspinner(context, attrs); break; case "imagebutton": view = new appcompatimagebutton(context, attrs); break; ... } ... return view; }
还没有展开说rinflate,篇幅限制,放到另外一篇文章中去分析,。
流程图如下:
总结
- layoutinfalter的作用是把xml转化成对应的view对象,需要用
activity#getlayoutinflater()
或者getsystemservice
获取 - 加载时先判断是否是merge标签,merge标签走递归方法rinflate,否则走createviewfromtag
- createviewfromtag作用是根据xml标签的名字去加载对应的view,使用的是反射的方法
- layoutinflater.factory2是设计出来灵活构造view的接口,可以用来实现换肤或者替换view的功能,同时也是appcompatactivity用来做兼容和版本替换的接口
以上就是源码分析android layoutinflater的使用的详细内容,更多关于android layoutinflater的资料请关注其它相关文章!