LayoutInflater源码最简解读 带你轻松领略源代码之美
来源:互联网 发布:java 对url encode 编辑:程序博客网 时间:2024/06/04 19:30
首先,我们获取LayoutInflater的方式:LayoutInflater.fron(context);是被封装的过的
public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater;}其实是通过获取Context.LAYOUT_INFLATER_SERVICE这个系统服务来得到的。
解析xml
LayoutInflater的作用就是加载布局,所以肯定需要解析xml布局。其实如果年头比较老的安卓工作者,都知道后台返回给你的数据不是json格式的,而是xml格式的。所以他们,非常熟悉怎么去解析xml。在这里,解析xml布局也是一样的方法。XmlResourceParser。这一块的知识,在郭霖老师的第一行代码第二版里写的非常详细。当然这个东西不掌握也行,因为现在传数据一般都采取json的格式,而且后面的源码解析xml也是十分通俗易懂。
synchronized:锁住xml解析过程
synchronized (mConstructorArgs) {这样是为了保证同一时间只有一个线程执行解析操作,把里面共享的资源保证同时只有一个线程使用,保证了线程的安全性,使之不发生错乱。(简单来说,就是封锁住静态资源因为这个方法以外的因素而改变,只要进入这个锁方法,静态资源不会被外界改变)
根布局的解析:
略过中间部分:因为是找到根布局,所以找里面的节点(在解析中,比如LinerLayout,TextView都算一个节点)根本没有必要,只需要找首位节点即可。
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!");}直接用一个while循环略过中间部分的搜寻,下面的if判断,如果不是开始的节点(即根布局),就报错。
如果根布局是merge:(经常采用merge性能优化的人可能很清楚,这个是用来优化UI速度的标签)
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);}熟悉的人很清楚,merge作为根布局,必须添加一个父布局而且true(代表愿意设置父布局),否则就会有红红的波浪线提示你错误。rInfalte用来加载子节点(后面会讲)
如果根布局是正常的东西:(merge以外)
调用createViewFromTag方法创建根布局,一会儿讲
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
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);}如果你不打算把这个布局添加到另外的布局里,你就可以把创建好的参数给这个view,所以我断定这个参数是父的属性
也是一样加载子布局
rInflateChildren(parser, temp, attrs, true);
如果你打算把这个布局添加到另外的布局里,就要把view和参数一起给他,参数算是嫁妆
if (root != null && attachToRoot) { root.addView(temp, params);}有人可能会疑惑,addView应该就一个view的参数就可以,为什么还有参数,因为他也被封装了。
(加载父布局基本就讲完了,最后面会贴出所有代码。此外如果读者对照着as里的源码看,效果可能会更好)
createViewFromTag
郭霖说的没错,我去源码里找了一下,这个方法最终还是调用了createView方法,通过反射来创建根布局的实例
什么是反射?为什么要用反射?
因为上面那个加载根布局的方法,他哪里知道你的根布局是哪个类?(熟悉view的人知道,布局都是自定义或者安卓帮你定义的类,也就是对象)安卓总不能一个用一个switch case帮你识别各种view应该创建哪个类吧。那我们又应该怎么创建这个类的实例?所以就用到了反射技术。反射技术是这样的一种技术:通俗来讲,再编译时,java会获取这个未知类的真实类型,继而创建这个类的实例。(关于这个类一点都不知道也不行,类名还是要知道的,parser(xml解析器)就是一个盛放这个类名的容器(他在找解析xml的时候偷偷地把根布局的类名放到了口袋里,装作若无其事的样子),然后传递。)
(前方高能,可以跳过)
反射:接下来讲一下通过反射创建这个实例的过程(反射技术强大,但是真的不难,可以看看我的有关反射的博客,写的十分通俗)
类加载器classloader:
if (constructor != null && !verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name);}
先通过verifyClassLoader鉴别一下,这个方法判断classloader是不是LayoutInflater那一派(双亲委托模型)的,如果是,直接用,就不用另外创建了,大大加快了效率,如果不是,只能忍痛设为空了。
获取类名:
prefix != null ? (prefix + name) : name如果你前面的包名不为空,类名就是 包名+类名,如果没有包名,那就是直接是类名。(追溯下来发现这里传入的包名是空,因为这是安卓内部的,然而你要自定义控件, 这个包名就肯定不能为空了)
生成实例:
Class<? extends View> clazz = null;
clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class);后面的asSubclass就是等于告诉编译器这个类是View的子类。可能有人认为这是多此一举,但是你不这么做,你就会被定义成一个Object类,而你上面说明的是View的子类,那么这里从Object到View就会发生强制类型转换,发生的结果就是可能会有bug或者这个类来自View的属性会在转型的过程中丢失,当然最大的可能性是编译的时候都通过不了,或者红红的波浪线从一开始就提示你不能这么做。
LayoutInflater内部接口Filter
if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, attrs); }}这里mFilter肯定在另外地方有实现,用来对这个类进行一定的规范,比如安卓自定义控件,有些地方不符合规范,在这个地方就很有可能被拦截下来。
存储构造器:(如果没有这个系列的构造器的话)
constructor = clazz.getConstructor(mConstructorSignature);constructor.setAccessible(true);sConstructorMap.put(name, constructor);
Filter存储,方便下次使用(请注意:性能优化的实例,可以借鉴):
如果Filter不为空,直接用(为什么上面不用,因为上面是没有构造器的情况,而Filter是依赖于构造器的)
Boolean allowedState = mFilterMap.get(name);
else if (allowedState.equals(Boolean.FALSE)) { failNotAllowed(name, prefix, attrs);}
在最后,还要判断这个布局是不是ViewStub的子类(如果对性能优化有了解的朋友,应该对ViewStub很明白)
if (view instanceof ViewStub) { // Use the same context when inflating ViewStub later. final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0]));}强转一波
反射终于谈完了,现在讲正题。
rInflate:加载子布局
加载子布局就很有趣了,因为view是树形结构,所以这里是通过递归,不断遍历加载的,涉及到了算法,也很值得一讲!
当前树的深度:他会不断往里面探索,而parser.getDepth()也会不断的在变。这里在最上面声明了初始深度,方便后面进行比较。
final int depth = parser.getDepth();
判断从根节点开始:在刚进入循环的时候进行判定当前是不是初始节点,如果不是,continue,本次循环结束,直到找到初始节点为止
if (type != XmlPullParser.START_TAG) { continue;}
进行优雅的递归
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);一看,这里是rInflateChildren,哪里是递归啊但是实际上,这个方法内部又调用了rInflater
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { rInflate(parser, parent, parent.getContext(), attrs, finishInflate);}为什么要这样做?有些人会认为多此一举,我个人想法,如果就自身调用自身的话,你会创造出许多许多个rInfalter方法来,虽然你看不见,但是资源消耗过大。可以看看这里,如果递归的话就是两个方法之间跳来跳去,资源无疑会节省许多。所以我认为,这是一种优雅的递归,用于处理大而负责的递归情况,不然数据一大,调用自身的那种递归会报StackOverFlow(堆栈溢出)的错。
这就基本讲完了。
其实源码并不可怕,虽然我们今天遇到许多生疏的知识点:反射,xml解析,等等。这些都是值得学习的方向,你应该感到快乐,应该有一种遇到难题的兴奋(这种兴奋多去刷刷hdu的acm算法题会培养出来,嘿嘿(学安卓的人,等你学了算法和数据结构之后,你会发现,你比以前自信了一百倍,耐心了一千倍!))
下面贴源码,不过你们as里也有。
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 { // 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 (DEBUG) { System.out.println("**************************"); System.out.println("Creating root view: " + name); System.out.println("**************************"); } 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); } } if (DEBUG) { System.out.println("-----> start inflating children"); } // Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // 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) { final InflateException ie = new InflateException(e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (Exception e) { final InflateException ie = new InflateException(parser.getPositionDescription() + ": " + e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } return result; }}createView
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { Constructor<? extends View> constructor = sConstructorMap.get(name); if (constructor != null && !verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); } Class<? extends View> clazz = null; try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); 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); 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 = 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; 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; } catch (NoSuchMethodException e) { final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (ClassCastException e) { // If loaded class is not a View subclass final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (ClassNotFoundException e) { // If loadClass fails, we should propagate the exception. throw e; } catch (Exception e) { final InflateException ie = new InflateException( attrs.getPositionDescription() + ": Error inflating class " + (clazz == null ? "<unknown>" : clazz.getName()), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}rinflate
void rInflate(XmlPullParser parser, View parent, Context context, 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_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } 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); } } if (finishInflate) { parent.onFinishInflate(); }}
最后面提出一个问题:
如果某个控件在xml中被设置为setVisblity = gone,那么这个情况他什么时候会被发现并且被略过,不加载?
我找遍了源码,发现都没有。那么可以推断出来,他是在xml布局被提交上去的时候就被过滤了。
- LayoutInflater源码最简解读 带你轻松领略源代码之美
- 视图绘制三部曲之onMeasure()源码最简解析 带你轻松领略源代码之美
- 领略计算机科学之美
- 领略webService之美
- android之LayoutInflater以及setFactory源码解读
- APP源码分享-你最美源码
- android 带你从源码分析LayoutInflater工作原理
- 带你领略AutoHotkey无限魅力
- 带你领略 Google Collections 1
- 带你领略 Google Collections 2
- 走进OpenCV,领略视觉之美.1
- 走进OpenCV,领略视觉之美.2
- 带你轻松看源码---AsyncTask(异步任务)
- 源码分析之LayoutInflater
- 源码分析之LayoutInflater
- sogou带你领略互联网如此逼格的年会
- 古人航海怎么定位?带你领略古人的智慧
- 带你领略世豪先生的风采
- vue2.x 中如何获取 DOM
- Leetcode 368 Largest Divisible Subset
- 链表
- SSIS DSN contains an architecture mismatch between the Driver and Application
- 利用nodejs制作爬虫获取数据的案例
- LayoutInflater源码最简解读 带你轻松领略源代码之美
- string初始化
- 移植Linux 3.4.2 内核
- jsp/servlet 回顾学习
- ios 总结
- STM32之中断与事件---中断与事件的区别
- RabbitMQ官方中文入门教程(PHP版) 第一部分:Hello World
- nodejs 客户端拍照调用azure face api对比身份证照片进行验证
- (BP进阶1)从M-P模型到BP神经网络