[置顶] Android 中LayoutInflater(布局加载器)源码篇之createViewFromTag方法
2017-09-26 16:14
405 查看
本文出自博客Vander丶CSDN博客,如需转载请标明出处,尊重原创谢谢
博客地址:http://blog.csdn.net/l540675759/article/details/78099702
Android 中LayoutInflater(布局加载器)系列博文说明
Android 中LayoutInflater(布局加载器)系列之介绍篇
Android 中LayoutInflater(布局加载器)系列之源码篇
Android 中LayoutInflater(布局加载器)源码篇之createViewFromTag方法
Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
Android 中LayoutInflater(布局加载器)源码篇之parseInclude方法
Android 中LayoutInflater(布局加载器)之实战篇
一些不常见的标签的解析方法以及使用,例如:view、blink
LayoutInflater中Factory,Factory2是什么,用途是什么?
CreateViewFromTag默认的View创建流程解析
createViewFromTag在LayoutInflater中存在重载,最终还是会调用5个参数的createViewFromTag方法。
根据源码可以将createViewFromTag分为三个流程:
对一些特殊标签,做分别处理,例如:view,TAG_1995(blink)
进行对Factory、Factory2的设置判断,如果设置那么就会通过设置Factory、Factory2进行生成View
如果没有设置Factory或Factory2,那么就会使用LayoutInflater默认的生成方式,进行View的生成
createViewFromTag分析过程:
(1)处理view标签
如果标签的名称是view,注意是小写的view,这个标签一般大家不太常用,具体的使用情况如下:
在使用时,相当于所有控件标签的父类一样,可以设置class属性,这个属性会决定view这个节点会变成什么控件。
(2)如果该节点与主题相关,则需要特殊处理
如果该节点与主题(Theme)相关,需要将context与theme信息包装至ContextWrapper类。
(3)处理TAG_1995标签
这就有意思了,TAG_1995指的是blink这个标签,这个标签感觉使用的很少,以至于大家根本不知道。
这个标签最后会被解析成BlinkLayout,BlinkLayout其实就是一个FrameLayout,这个控件最后会将包裹内容一直闪烁(就和电脑版QQ消息提示一样),有空大家可以自行尝试下,很简单,下面贴一下用法:
(4)判断其是否存在Factory或者Factory2
在这里先对Factory进行判空,这里不管Factory还是Factory2(mPrivateFactory 就是Factory2),本质上都是一种扩展操作,提前解析name,然后直接将解析后的View返回。
Factory
Factory2
从这里可以看出,Factory2和Factory都是一个接口,需要自己实现,而Factory2和Factory的区别是Factory2继承Factory,从而扩展出一个参数,就是增加了该节点的父View。
这里我自定义了一个Factory,下面自定义解析View的过程:
从上面可以看出,Factory和Factory2其实LayoutInflater解析View时的一种扩展实现,在这里可以额外的对View处理,设置Factory和Factory2需要通过setFactory()或者setFactory2()来实现。
setFactory()
setFactory2()
通过上面代码可以看出,Factory和Factory2只能够设置一次,并且Factory和Factory2二者互斥,只能存在一个。
所以一般setFactory()或者setFactory2(),一般在cloneInContext()之后设置,这样生成一个新的LayoutInflater,标记默认是false,才能够设置。
(5)LayoutInflater内置的解析过程
如果Factory或者Factory2没有设置,或者返回View为null,才会使用默认解析方式。
这段就是对自定义View和原生的控件进行判断,这里给大家说明下原生控件和自定义View的name区别:
原生控件的解析方式 onCreateView :
然后调用的还是2个参数的onCreateView()方法
可以看到最终方法的指向还是调用createView方法:
上面代码有点长,就直接在代码里面加注释了,这里额外说一下这个方法:
判断ClassLoader是否安全的verifyClassLoader :
这里简单说明下,几种ClassLoader的作用:
一般的App刚启动的时候,就会有两个ClassLoader被加载,分别是PathClassLoader、DexClassLoader而这两个ClassLoader都是继承BaseDexClassLoader.
而BaseDexClassLoader继承的是ClassLoader,但是在ClassLoader中getParent()方法赋予其Parent为BootClassLoader,这个如果大家感兴趣,可以自行查阅ClassLoader。
博客地址:http://blog.csdn.net/l540675759/article/details/78099702
前言
如果读者没有阅读过该系列博客,建议先阅读下博文说明,这样会对后续的阅读博客思路上会有一个清晰的认识。Android 中LayoutInflater(布局加载器)系列博文说明
导航
Android 中LayoutInflater(布局加载器)系列博文说明Android 中LayoutInflater(布局加载器)系列之介绍篇
Android 中LayoutInflater(布局加载器)系列之源码篇
Android 中LayoutInflater(布局加载器)源码篇之createViewFromTag方法
Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
Android 中LayoutInflater(布局加载器)源码篇之parseInclude方法
Android 中LayoutInflater(布局加载器)之实战篇
概述
本篇博客,是属于Android 中LayoutInflater(布局加载器)源码篇其中一个部分,专门介绍createViewFromTag方法的流程,具体有以下几部分:一些不常见的标签的解析方法以及使用,例如:view、blink
LayoutInflater中Factory,Factory2是什么,用途是什么?
CreateViewFromTag默认的View创建流程解析
CreateViewFromTag源码解析
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) { return createViewFromTag(parent, name, context, attrs, false); }
createViewFromTag在LayoutInflater中存在重载,最终还是会调用5个参数的createViewFromTag方法。
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { //解析view标签 if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } //如果需要该标签与主题相关,需要对context进行包装,将主题信息加入context包装类ContextWrapper 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(); } //BlinkLayout是一种闪烁的FrameLayout,它包裹的内容会一直闪烁,类似QQ提示消息那种。 if (name.equals(TAG_1995)) { return new BlinkLayout(context, attrs); } //设置Factory,来对View做额外的拓展,这块属于可定制的内容 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); } //如果此时不存在Factory,不管Factory还是Factory2,还是mPrivateFactory都不存在,那么会直接对name直接进行解析 if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { //如果name中包含.即为自定义View,否则为原生的View控件 if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view;
根据源码可以将createViewFromTag分为三个流程:
对一些特殊标签,做分别处理,例如:view,TAG_1995(blink)
进行对Factory、Factory2的设置判断,如果设置那么就会通过设置Factory、Factory2进行生成View
如果没有设置Factory或Factory2,那么就会使用LayoutInflater默认的生成方式,进行View的生成
createViewFromTag分析过程:
(1)处理view标签
如果标签的名称是view,注意是小写的view,这个标签一般大家不太常用,具体的使用情况如下:
<view class="RelativeLayout" android:layout_width="match_parent" android:layout_height="match_parent"></view>
在使用时,相当于所有控件标签的父类一样,可以设置class属性,这个属性会决定view这个节点会变成什么控件。
(2)如果该节点与主题相关,则需要特殊处理
如果该节点与主题(Theme)相关,需要将context与theme信息包装至ContextWrapper类。
(3)处理TAG_1995标签
这就有意思了,TAG_1995指的是blink这个标签,这个标签感觉使用的很少,以至于大家根本不知道。
这个标签最后会被解析成BlinkLayout,BlinkLayout其实就是一个FrameLayout,这个控件最后会将包裹内容一直闪烁(就和电脑版QQ消息提示一样),有空大家可以自行尝试下,很简单,下面贴一下用法:
<blink android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="这个标签会一直闪烁"/> </blink>
(4)判断其是否存在Factory或者Factory2
在这里先对Factory进行判空,这里不管Factory还是Factory2(mPrivateFactory 就是Factory2),本质上都是一种扩展操作,提前解析name,然后直接将解析后的View返回。
Factory
public interface Factory { public View onCreateView(String name, Context context, AttributeSet attrs); }
Factory2
public interface Factory2 extends Factory { public View onCreateView(View parent, String name, Context context, AttributeSet attrs); }
从这里可以看出,Factory2和Factory都是一个接口,需要自己实现,而Factory2和Factory的区别是Factory2继承Factory,从而扩展出一个参数,就是增加了该节点的父View。
这里我自定义了一个Factory,下面自定义解析View的过程:
@Override public View onCreateView(String name, Context context, AttributeSet attrs) { View view = null; try { if (-1 == name.lastIndexOf(".")) { if (name.equals("View") || name.equals("ViewGroup")) { view = mInflater.createView(name, "android.view.", attrs); } else { view = mInflater.createView(name, "android.widget.", attrs); } } else { if (name.contains(".")) { String checkName = name.substring(name.lastIndexOf(".")); String prefix = name.substring(0, name.lastIndexOf(".")); view = mInflater.createView(checkName, prefix, attrs); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } if(view != null){ //在这里可以对View做一些额外的操作,并且能够获得View的属性集,可以做一些自定义操作。 view.xxxxxx } return view; }
从上面可以看出,Factory和Factory2其实LayoutInflater解析View时的一种扩展实现,在这里可以额外的对View处理,设置Factory和Factory2需要通过setFactory()或者setFactory2()来实现。
setFactory()
public void setFactory(Factory factory) { //如果已经设置Factory,不可以继续设置Factory if (mFactorySet) { throw new IllegalStateException("A factory has already been set on this LayoutInflater"); } if (factory == null) { throw new NullPointerException("Given factory can not be null"); } //设置Factory会添加一个标记 mFactorySet = true; if (mFactory == null) { mFactory = factory; } else { mFactory = new FactoryMerger(factory, null, mFactory, mFactory2); } }
setFactory2()
public void setFactory2(Factory2 factory) { if (mFactorySet) { throw new IllegalStateException("A factory has already been set on this LayoutInflater"); } if (factory == null) { throw new NullPointerException("Given factory can not be null"); } //注意设置Factory和Factory2的标记是共用的 mFactorySet = true; if (mFactory == null) { mFactory = mFactory2 = factory; } else { mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2); } }
通过上面代码可以看出,Factory和Factory2只能够设置一次,并且Factory和Factory2二者互斥,只能存在一个。
所以一般setFactory()或者setFactory2(),一般在cloneInContext()之后设置,这样生成一个新的LayoutInflater,标记默认是false,才能够设置。
(5)LayoutInflater内置的解析过程
如果Factory或者Factory2没有设置,或者返回View为null,才会使用默认解析方式。
if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); }
这段就是对自定义View和原生的控件进行判断,这里给大家说明下原生控件和自定义View的name区别:
原生 : RelativeLayout 自定义View : com.demo.guidepagedemo.customview.CustomImageView
原生控件的解析方式 onCreateView :
protected View onCreateView(View parent, String name, AttributeSet attrs) throws ClassNotFoundException { return onCreateView(name, attrs); }
然后调用的还是2个参数的onCreateView()方法
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { return createView(name, "android.view.", attrs); }
可以看到最终方法的指向还是调用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 { //如果构造器不存在,这个就相当于Class之前是否被加载过,sConstructorMap就是缓存这些Class的Map if (constructor == null) { //通过前缀+name的方式去加载 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); //缓存Class sConstructorMap.put(name, constructor); } else { //如果Class存在,并且加载Class的ClassLoader合法 //这里先判断该Class是否应该被过滤 if (mFilter != null) { //过滤器也有缓存之前的Class是否被允许加载,判断这个Class的过滤状态 Boolean allowedState = mFilterMap.get(name); if (allowedState == null) { //加载Class对象操作 clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); //判断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; //如果过滤器不存在,直接实例化该View final View view = constructor.newInstance(args); //如果View属于ViewStub那么需要给ViewStub设置一个克隆过的LayoutInflater if (view instanceof ViewStub) { final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } return view;
上面代码有点长,就直接在代码里面加注释了,这里额外说一下这个方法:
判断ClassLoader是否安全的verifyClassLoader :
private final boolean verifyClassLoader(Constructor<? extends View> constructor) { final ClassLoader constructorLoader = constructor.getDeclaringClass().getClassLoader(); if (constructorLoader == BOOT_CLASS_LOADER) { //这里注意BootClassLoader是相当于所有派生出来的ClassLoader的原始基类,所有的ClassLoader都是根据其衍生的。 return true; } //这里是一个遍历操作,一直在遍历加载mContext的ClassLoader的继承树,一直在往上寻找,如果 //constructor的ClassLoader与继承树中某个ClassLoader相同就说明这个ClassLoader是安全的 ClassLoader cl = mContext.getClassLoader(); do { if (constructorLoader == cl) { return true; } cl = cl.getParent(); } while (cl != null); return false; }
这里简单说明下,几种ClassLoader的作用:
(1)BootClassLoader 加载Android FrameWork层的一些字节码文件 (2)PathClassLoader 加载已经安装到系统上的应用App(apk)上的字节码文件 (3)DexClassLoader 加载指定目录中的Class字节码文件 (4)BaseDexClassLoader 是PathClassloader和DexClassLoader的父类
一般的App刚启动的时候,就会有两个ClassLoader被加载,分别是PathClassLoader、DexClassLoader而这两个ClassLoader都是继承BaseDexClassLoader.
而BaseDexClassLoader继承的是ClassLoader,但是在ClassLoader中getParent()方法赋予其Parent为BootClassLoader,这个如果大家感兴趣,可以自行查阅ClassLoader。
流程图
因为图有点大,建议大家查看先拷贝到本地,或者放大120%,就可以清晰观看了。相关文章推荐
- [置顶] Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
- [置顶] Android 中LayoutInflater(布局加载器)源码篇之parseInclude方法
- [置顶] Android 中LayoutInflater(布局加载器)系列博文说明
- [置顶] Android 中LayoutInflater(布局加载器)之实战篇
- [置顶] Android 中LayoutInflater(布局加载器)之源码篇
- [置顶] Android 中LayoutInflater(布局加载器)之介绍篇
- Android 中LayoutInflater(布局加载器)系列博文说明
- [置顶] android 布局之滑动探究 scrollTo 和 scrollBy 方法使用说明
- Android布局中ScrollView与ListView的冲突的最简单方法(listItem.measure(0, 0))
- Android学习-RelativeLayout相对布局属性方法
- 子线程更新UI会发生android.view.ViewRoot$CalledFromWrongThreadException异常的解决方法 .
- [置顶] Android开发12:Preference布局结构小议
- android 加载器loadermanager.initLoader方法的注意事项
- Android软键盘弹出不影响布局的方法
- Android布局中ScrollView与ListView的冲突的方法
- Android中的布局优化方法
- Android实现BaseAdapter布局的两种方法
- 【android开发】之【android动态布局方法总结】
- 【Android动态布局】之【使用addView方法时,如何保持已有动态控件位置不被改变】
- [置顶] android 实现发送彩信方法 (MMS),非调用系统彩信界面