深入解析Android LayoutInflate机制

更新:11-23 神话故事 我要投稿 纠错 投诉

1.Activity.getLayoutInflater();

2.LayoutInflater.from(上下文);

3.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

每个方法都与Context相关联,方法1和方法2最终都会通过方法3来实现。

获取LayoutInflater后,通过调用inflate方法实例化布局。 inflate 方法有许多重载。我们常用的是inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean AttachToRoot)。所有inflate 方法最终都会调用inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean AttachToRoot)。我们就从这个方法开始分析LayoutInflater的源码。

充气方法

我们先看一下inflate的三个参数(XmlPullParser parser, @Nullable ViewGroup root, boolean AttachToRoot):

XmlPullParser解析器:显然它是一个XML解析器。该解析器由LayoutInflater将要加载的XML布局进行转换,并以PULL模式进行解析。 ViewGroup root:加载要加载的XML布局的根容器。比如Activity的setContentView方法中,就是id为android.R.id.content的FrameLayout根布局。 boolean AttachToRoot:是否将解析的布局添加到根容器,这也会影响解析的布局的宽度和高度。人们广泛讨论的是root和attachToRoot传递的不同参数对加载的布局文件的影响。请参阅下面的代码。

公共视图膨胀(XmlPullParser解析器,@Nullable ViewGroup根,布尔attachToRoot){

同步(mConstructorArgs){

最终上下文inflaterContext=mContext;

//将解析器转换成AttributeSet接口来读取xml中的View属性集

最终AttributeSet attrs=Xml.asAttributeSet(parser);

上下文lastContext=(Context) mConstructorArgs[0];

mConstructorArgs[0]=inflaterContext;

查看结果=根; //该方法返回的View默认为root

尝试{

//寻找根节点。

int 类型;

while ((type=parser.next()) !=XmlPullParser.START_TAG

类型!=XmlPullParser.END_DOCUMENT) {

//空的

}

.

最终字符串名称=parser.getName(); //获取当前标签名

.

if (TAG_MERGE.equals(name)) { //处理标签

if (root==null || !attachToRoot) {

throw new InflateException("只能与有效的"一起使用

+ "ViewGroup 根和attachToRoot=true");

}

//递归处理

rInflate(解析器, root, inflaterContext, attrs, false);

} 别的{

//Temp 是在xml 中找到的根视图

//创建视图对象

最终视图temp=createViewFromTag(root, name, inflaterContext, attrs);

ViewGroup.LayoutParams 参数=null;

如果(根!=空){

.

//创建与root 匹配的布局参数(如果提供)

params=root.generateLayoutParams(attrs); //获取根View的宽度和高度

if (!attachToRoot) { //如果attachToRoot为false,则设置根View的宽度和高度

//如果没有,则设置temp 的布局参数

//附加。 (如果是,我们使用addView,如下)

temp.setLayoutParams(params);

}

}

.

//根据其上下文对temp 下的所有子项进行膨胀。

rInflateChildren(解析器, temp, attrs, true); //递归处理

.

//我们应该附加我们找到的所有视图(int temp)

//到根目录。现在就这样做。

如果(根!=null AttachToRoot){

//如果root不为空且attachToRoot为true,则将根View添加到容器中

root.addView(temp, params);

}

//决定是返回传入的根还是

//在xml 中找到的顶视图。

if (root==null || !attachToRoot) {

//如果root为空或者attachToRoot为false,则将返回结果设置为root View

结果=温度;

}

}

} catch (XmlPullParserException e) {

.

} catch (异常e) {

.

} 最后{

//不要保留上下文的静态引用。

mConstructorArgs[0]=LastContext;

mConstructorArgs[1]=null;

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

}

//root 或创建的root View

返回结果;

}

从代码中我们可以看到root和attachToRoot传递的不同参数的影响:

如果root不为null且attachToRoot设置为true,则加载的布局将添加到父布局,即root,并返回root;如果root不为null且attachToRoot设置为false,则会添加布局文件的最外层。设置所有布局属性并返回布局的根View。当视图添加到父视图时,这些布局属性会自动生效;如果root 为null,attachToRoot 将不起作用,设置任何值都没有意义。返回的也是要加载的布局的根View;

rInflate方法

从上面的方法可以看到,处理标签时会调用rInflate,处理子View时会调用rInflateChildren方法。其实rInflate是在rInflateChildren中调用的,rInflate也调用了rInflateChildren,这样就形成了递归调用,即递归地处理子View。

void rInflate(XmlPullParser 解析器,视图父级,Context 上下文,

AttributeSet attrs, boolean finishInflate) 抛出XmlPullParserException, IOException {

最终int 深度=parser.getDepth();

int 类型;

布尔待处理请求焦点=false;

while (((type=parser.next()) !=XmlPullParser.END_TAG ||

parser.getDepth() 深度) 类型!=XmlPullParser.END_DOCUMENT) {

如果(类型!=XmlPullParser.START_TAG){

继续;

}

最终字符串名称=parser.getName();

如果(TAG_REQUEST_FOCUS.equals(名称)){

//处理标签

待定请求焦点=true;

消耗ChildElements(解析器);

} else if (TAG_TAG.equals(name)) {

//处理标签

parseViewTag(解析器,父级,attrs);

} else if (TAG_INCLUDE.equals(name)) {

//处理标签

if (parser.getDepth()==0) {

throw new InflateException("不能是根元素");

}

parseIninclude(解析器、上下文、父级、属性);

} else if (TAG_MERGE.equals(name)) { //标记异常

throw new InflateException("必须是根元素");

} else { //创建视图对象

最终视图视图=createViewFromTag(parent, name, context, attrs);

最终ViewGroup viewGroup=(ViewGroup) 父级;

最终ViewGroup.LayoutParams params=viewGroup.generateLayoutParams(attrs);

rInflateChildren(解析器,视图,attrs,true); //递归处理子节点

viewGroup.addView(视图, 参数); //将View添加到父布局中

}

}

if (pendingRequestFocus) { //父布局处理焦点

父级.restoreDefaultFocus();

}

if (finishInflate) { //结束加载

onFinishInflate();

}

此方法将处理、 和普通View 标签。在:

就是重新定位焦点。调用的ConsumerChildElements 方法实际上什么也不做,只是结束标签的消耗。标签一般很少使用。它们主要用于标记View,为View设置一个标签值,例如: findViewById(R.id.tv).setOnClickListener(new View.OnClickListener() {

@覆盖

公共无效onClick(查看v){

//尝试标签标签

Toast.makeText(MainActivity.this, (String) v.getTag(R.id.tag), Toast.LENGTH_SHORT).show();

}

});在ListView的自定义Adapter中,应该已经使用了View的setTag方法,即使用ViewHolder来复用View。

解析ViewTag方法:

私人无效parseViewTag(XmlPullParser解析器,视图视图,AttributeSet attrs)

抛出XmlPullParserException,IOException {

最终Context context=view.getContext();

最终TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.ViewTag);

//读取标签的id

最终int key=ta.getResourceId(R.styleable.ViewTag_id, 0);

//读取标签的值

最终CharSequence 值=ta.getText(R.styleable.ViewTag_value);

//设置View的标签

view.setTag(键, 值);

ta.recycle();

//结束标签(子View无效)

消耗ChildElements(解析器);

} 标签不能是根标签,parseInclude 方法单独对其进行分析。标签只能是根标签,这里会抛出异常。

parseInclude方法

private void parseIninclude(XmlPullParser解析器,上下文上下文,视图父级,

AttributeSet attrs) 抛出XmlPullParserException, IOException {

int 类型;

if (parent instanceof ViewGroup) { //必须在ViewGroup 中才有效

//处理主题属性

.

//如果布局指向主题属性,我们必须

//修改该值以从中获取资源标识符。

//获取layout指定的布局

int 布局=attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);

.

if (layout==0) { //必须是合法的id

最终字符串值=attrs.getAttributeValue(null, ATTR_LAYOUT);

throw new InflateException("您必须指定有效的布局"

+ "参考。布局ID "+值+ "无效。");

} else { //与inflate 处理类似

//获取布局解析器

最终XmlResourceParser childParser=context.getResources().getLayout(layout);

尝试{

最终AttributeSet childAttrs=Xml.asAttributeSet(childParser);

while ((type=childParser.next()) !=XmlPullParser.START_TAG

类型!=XmlPullParser.END_DOCUMENT) {

//空的。

}

如果(类型!=XmlPullParser.START_TAG){

抛出新的InflateException(childParser.getPositionDescription() +

": 未找到开始标记!");

}

//布局的根标签

最终字符串childName=childParser.getName();

if (TAG_MERGE.equals(childName)) { //处理//Thetag 不支持android:theme,所以

//这里没什么特别要做的。

rInflate(childParser, 父级, 上下文, childAttrs, false);

} else { //进程视图

最终视图视图=createViewFromTag(parent, childName,

上下文、childAttrs、hasThemeOverride);

最终ViewGroup 组=(ViewGroup) 父级;

最终TypedArray a=context.obtainStyledAttributes(

attrs, R.styleable.Include); // 获取里设置的id final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID); // 获取里设置的visibility final int visibility = a.getInt(R.styleable.Include_visibility, -1); a.recycle();

ViewGroup.LayoutParams params = null; try { // 获取里设置的宽高 params = group.generateLayoutParams(attrs); } catch (RuntimeException e) { // Ignore, just fail over to child attrs. } if (params == null) { // 获取layout里设置的宽高 params = group.generateLayoutParams(childAttrs); } //里设置的宽高优先于layout里设置的 view.setLayoutParams(params); // Inflate all children. rInflateChildren(childParser, view, childAttrs, true); if (id != View.NO_ID) { // include里设置的id优先级高 view.setId(id); } // include里设置的visibility优先级高 switch (visibility) { case 0: view.setVisibility(View.VISIBLE); break; case 1: view.setVisibility(View.INVISIBLE); break; case 2: view.setVisibility(View.GONE); break; } group.addView(view); } } finally { childParser.close(); } } } else { throw new InflateException("can only be used inside of a ViewGroup"); } LayoutInflater.consumeChildElements(parser); }include里必须设置layout属性,且layout的id必须合法;include里设置的id优先级高于layout里设置的id,即:两者同时设置时,后者会失效;include里设置的width和height属性优先级高于layout里设置的宽高;include里设置的visibility属性优先级高于layout设置的visibility。

createViewFromTag方法

正常View标签都是通过createViewFromTag来创建对应的View对象的。 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { if (name.equals("view")) { // 真正的View标签名存在class属性中 name = attrs.getAttributeValue(null, "class"); } ... try { View view; if (mFactory2 != null) { // 先使用Factory2 view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory != null) { // 再使用Factory 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 { // 通过标签名中是否包含"."来区分是否为自定义View if (-1 == name.indexOf(".")) { // 处理系统View view = onCreateView(parent, name, attrs); } else { // 自定义View用的是全限定类名 // 处理自定义View view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; } catch (InflateException e) { ... } catch (ClassNotFoundException e) { ... } catch (Exception e) { ... } }优先通过Factory2和Factory来创建View,这两个Factory等会再说;通过标签名中是否包含"."来区分待创建的View是自定义View还是系统View;系统View会在onCreateView方法中添加android.view.前缀,然后交由createView处理。

createView方法

public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { // 有缓存 Constructorconstructor = sConstructorMap.get(name); if (constructor != null && !verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); } Classclazz = null; try { if (constructor == null) { // 第一次则通过反射创建constructor // 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); } } // 使用的是包含Context, AttributeSet这两个参数的构造函数 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 lastContext = mConstructorArgs[0]; if (mConstructorArgs[0] == null) { // Fill in the context if not already within inflation. mConstructorArgs[0] = mContext; } Object[] args = mConstructorArgs; args[1] = attrs; // 反射创建View实例对象 final View view = constructor.newInstance(args); if (view instanceof ViewStub) { // 如果是ViewStub则懒加载 // Use the same context when inflating ViewStub later. final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } mConstructorArgs[0] = lastContext; return view; } ... }通过反射待创建View的构造函数(两个参数:Context和AttributeSet的构造函数)来实例化View对象,如果是ViewStub对象还会进行懒加载。

LayoutInflater.Factory/Factory2

通过以上流程,使用LayoutInflater的infalte方法加载布局文件的整体流程就分析完了。但出现了Factory2和Factory类,它们会优先创建View,我们来看看着两个类到底是什么! 它们都是LayoutInflater的内部类——两个接口: public interface Factory { public View onCreateView(String name, Context context, AttributeSet attrs); } public interface Factory2 extends Factory { public View onCreateView(View parent, String name, Context context, AttributeSet attrs); }Factory2继承了Factory,增加了一个带View parent参数的onCreateView重载方法。它们是在createViewFromTag中被调用的,默认为null,说明开发人员可以自定义这两个Factory,则通过它们可以改造待加载XML布局中的View标签,来使用自定义规则创建View。 来看一下它们的设置方法: public void setFactory(Factory factory) { if (mFactorySet) { throw new IllegalStateException("A factory has already been set on this LayoutInflater"); } // 和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"); } mFactorySet = true; if (mFactory == null) { mFactory = mFactory2 = factory; } else { // 合并原有的Factory mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2); } }可以看到Factory和Factory2只能设置一次,否则会抛异常。 这两个Factory的区别是什么? Factory2 是API 11 被加进来的; Factory2 继承自 Factory,也就说现在直接使用Factory2即可; Factory2 可以对创建 View 的 Parent 进行操作;

用户评论

葵雨

学习Android开发必备知识啊!

    有9位网友表示赞同!

麝香味

感觉LayoutInflater真是个好用工具,搞定页面布局就容易多了。

    有11位网友表示赞同!

执妄

终于找到解释LayoutInflater机制的文章了,之前总弄不明白...

    有6位网友表示赞同!

没过试用期的爱~

Android系统怎么高效地创建View窗口?这篇应该有答案吧!

    有17位网友表示赞同!

墨城烟柳

想要深入了解Android View层级,这篇文章应该是很有帮助的。

    有16位网友表示赞同!

岁岁年年

学习Android开发的时候,布局和View总是很难理解,希望这篇文章能给我一些启示。

    有17位网友表示赞同!

水波映月

看了标题就感觉这个分析很专业,期待能学到新知识!

    有12位网友表示赞同!

不浪漫罪名

这篇分析应该会从源码层面解释LayoutInflater,很详细的样子。

    有5位网友表示赞同!

剑已封鞘

以前总是在用LayoutInflater的时候遇到问题,希望能通过这篇文章解决疑惑。

    有15位网友表示赞同!

命该如此

对Android系统架构感兴趣的朋友们,这个分析应该会很有帮助。

    有8位网友表示赞同!

艺菲

想看看Android中的View体系到底是怎么运作的,这篇分析是个好起点。

    有17位网友表示赞同!

从此我爱的人都像你

终于有人来讲清楚LayoutInflater的工作原理了,真期待!

    有5位网友表示赞同!

容纳我ii

作为Android开发人员,对系统底层机制了解得越多越好,这篇文章看起来就很有价值。

    有6位网友表示赞同!

微信名字

感觉 LayoutInflater这个工具很神奇,这篇分析应该能让我更理解它的运作方式。

    有6位网友表示赞同!

巷口酒肆

希望这篇文章能解释清楚Inflate的流程和参数设置,提升我的Android开发能力。

    有16位网友表示赞同!

你身上有刺,别扎我

阅读源码总是让人头疼,希望能通过文章来清晰地了解LayoutInflater原理。

    有14位网友表示赞同!

荒野情趣

想要优化自己的Android APP性能,搞清楚系统机制很重要,这篇文章看起来挺实用的。

    有6位网友表示赞同!

龙卷风卷走爱情

分析文章通常更深入,期待能让我对Android开发有更全面的理解。

    有14位网友表示赞同!

【深入解析Android LayoutInflate机制】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活

上一篇:【新春精选】2020年最新新年祝福文案收藏必备 下一篇:追寻内心愿望:愿您的人生精彩纷呈