inflate流程分析

2024-01-13 14:20
文章标签 分析 流程 inflate

本文主要是介绍inflate流程分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一.inflate的三参数重载方法else里面逻辑

我们先看到setContentView里面的inflate的调用链:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {return inflate(resource, root, root != null);}public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();if (DEBUG) {Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("+ Integer.toHexString(resource) + ")");}View view = tryInflatePrecompiled(resource, res, root, attachToRoot);if (view != null) {return view;}XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {parser.close();}}

重点看三参数的方法:

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 {advanceToRootNode(parser);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 xmlfinal 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 suppliedparams = 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(getParserStateDescription(inflaterContext, attrs)+ ": " + 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;}}

 在这个方法里面,先拿到我们xml的根布局对象,如果是merge的话,直接取merge的子view添加。我们先看一下不是merge会怎么样。

创建根布局View

先执行createViewFromTag方法,这个传入的name是根布局的name,这个方法就是创建布局的根View:

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.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();}try {View view = tryCreateView(parent, name, context, attrs);if (view == null) {final Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = context;try {if (-1 == name.indexOf('.')) {view = onCreateView(context, parent, name, attrs);} else {view = createView(context, name, null, attrs);}} finally {mConstructorArgs[0] = lastContext;}}return view;} catch (InflateException e) {throw e;} catch (ClassNotFoundException e) {final InflateException ie = new InflateException(getParserStateDescription(context, attrs)+ ": Error inflating class " + name, e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} catch (Exception e) {final InflateException ie = new InflateException(getParserStateDescription(context, attrs)+ ": Error inflating class " + name, e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;}}

重点看到这段代码:

if (-1 == name.indexOf('.')) {view = onCreateView(context, parent, name, attrs);} else {view = createView(context, name, null, attrs);}

 这里分为两种情况了,一种是有点,一种是全路径没有点的。

全路径名称有点(自定义的view)

先看看看有点是什么情况:

 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<? 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 itclazz = 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 constructorif (mFilter != null) {// Have we seen this name before?Boolean allowedState = mFilterMap.get(name);if (allowedState == null) {// New class -- remember whether it is allowedclazz = 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;}}......}

可以看到有点的直接反射创建出这个view了。

全路径名称没有点(android系统的view)

再来看看没有点的情况:

   public View onCreateView(@NonNull Context viewContext, @Nullable View parent,@NonNull String name, @Nullable AttributeSet attrs)throws ClassNotFoundException {return onCreateView(parent, name, attrs);}protected View onCreateView(View parent, String name, AttributeSet attrs)throws ClassNotFoundException {return onCreateView(name, attrs);}

实际上LayoutInflater是一个抽象类,它的实现类是PhoneLayoutInflater:

public class PhoneLayoutInflater extends LayoutInflater {private static final String[] sClassPrefixList = {"android.widget.","android.webkit.","android.app."};/*** Instead of instantiating directly, you should retrieve an instance* through {@link Context#getSystemService}** @param context The Context in which in which to find resources and other*                application-specific things.** @see Context#getSystemService*/public PhoneLayoutInflater(Context context) {super(context);}protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {super(original, newContext);}/** Override onCreateView to instantiate names that correspond to thewidgets known to the Widget factory. If we don't find a match,call through to our super class.*/@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {for (String prefix : sClassPrefixList) {try {View view = createView(name, prefix, attrs);if (view != null) {return view;}} catch (ClassNotFoundException e) {// In this case we want to let the base class take a crack// at it.}}return super.onCreateView(name, attrs);}public LayoutInflater cloneInContext(Context newContext) {return new PhoneLayoutInflater(this, newContext);}
}

 所以在LayoutInflater里面调用的两参方法最后会执行PhoneLayoutInflater的两参方法:

   @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {for (String prefix : sClassPrefixList) {try {View view = createView(name, prefix, attrs);if (view != null) {return view;}} catch (ClassNotFoundException e) {// In this case we want to let the base class take a crack// at it.}}return super.onCreateView(name, attrs);}

这个方法先进行三个参数的处理,如果这三个参数对不上,会执行super的两参方法,再进行一个参数的处理。实际上是进行了四个参数的处理:

private static final String[] sClassPrefixList = {"android.widget.","android.webkit.","android.app."};protected View onCreateView(String name, AttributeSet attrs)throws ClassNotFoundException {return createView(name, "android.view.", attrs);}

从没有点的情况来看,先把全路径给补全了,然后再和有点的情况一样,反射创建出View。 

总结

其实创建xml的根布局的时候,如果是全路径名称有点的话,就是我们自己创建的view,比如:

androidx.constraintlayout.widget.ConstraintLayout

这个是谷歌自定义的根布局,直接反射创建view就行。

如果在布局文件里面根布局没有点:

LinearLayout

这就是android系统自带的根布局,我们无法反射创建它,所以补全全路径后再反射创建就行了。

创建根布局包裹的子View

在inflate的三参重载方法里面,我们通过:

final View temp = createViewFromTag(root, name, inflaterContext, attrs);

创建出了根布局view。接下来我们执行下面代码创建子view:

rInflateChildren(parser, temp, attrs, true);
  void rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {final int depth = parser.getDepth();int type;boolean pendingRequestFocus = false;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)) {pendingRequestFocus = true;consumeChildElements(parser);} 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 (pendingRequestFocus) {parent.restoreDefaultFocus();}if (finishInflate) {parent.onFinishInflate();}}

从这个函数我们可以看到循环解析我们的xml文件,如果是include不能定义在xml的根里面,merge又只能定义在xml的根里面。

这几种情况都除外的话,接下来又是进入和xml根view一样的解析流程了。

二.inflate函数几个参数的作用

根布局是merge

主要文件

R.layout.activity_main:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/ll"tools:context=".MainActivity2"><Buttonandroid:id="@+id/button1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Button" /></LinearLayout>

R.layout.merge_layout:

<merge xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/button1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Button" /></merge>

 我在mainActivty里面通过LayoutInflater把R.layout.merge_layout加载到R.layout.activity_main里面。

第一种方式:

//第一种LayoutInflater.from(this).inflate(R.layout.merge_layout, layout, true);

可以看到第一种方式完美加入。 

 第二种,第三种方式:

//第二种LayoutInflater.from(this).inflate(R.layout.merge_layout, layout, false);
//第三种LayoutInflater.from(this).inflate(R.layout.merge_layout, null, true);

第二种和第三种方式会报错,这是为什么呢?

在inflate的三参构造方法里面:

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);}

如果xml的根布局是merge的话,root为空和attachToRoot是false都会报错。

根布局不是merge

涉及到inflate的参数的几处地方分别是在下面:

if (root != null) {if (DEBUG) {System.out.println("Creating params from root: " +root);}// Create layout params that match root, if suppliedparams = 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 (root != null && attachToRoot) {root.addView(temp, params);}
if (root == null || !attachToRoot) {result = temp;}

这里的root就是我们传入的root,temp是xml的根布局。

R.layout.inflater_activity:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/ll"></LinearLayout>

R.layout.inflater_layout:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="200dp"android:layout_height="200dp"android:background="@color/teal_200"><Buttonandroid:layout_gravity="center"android:background="@color/cardview_dark_background"android:layout_width="wrap_content"android:layout_height="wrap_content"/></LinearLayout>

第一种方式: 

public class InflaterActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);supportRequestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.inflater_activity);LinearLayout layout = findViewById(R.id.ll);//第一种方式成功添加LayoutInflater.from(this).inflate(R.layout.inflater_layout, layout, true);}

第一种方式成功添加。

第二种方式:

public class InflaterActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);supportRequestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.inflater_activity);LinearLayout layout = findViewById(R.id.ll);//第一种方式成功添加//LayoutInflater.from(this).inflate(R.layout.inflater_layout, layout, true);//第二种方式,报错The specified child already has a parent. You must call removeView() on the child's parent first.View view = LayoutInflater.from(this).inflate(R.layout.inflater_layout, layout, true);layout.addView(view);}
}

第二种方式会报错,因为root不为空,attachToRoot为true的情况下,会执行:

if (root != null && attachToRoot) {root.addView(temp, params);}

第三种方式:

public class InflaterActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);supportRequestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.inflater_activity);LinearLayout layout = findViewById(R.id.ll);//第一种方式成功添加//LayoutInflater.from(this).inflate(R.layout.inflater_layout, layout, true);//第二种方式,报错The specified child already has a parent. You must call removeView() on the child's parent first.//View view = LayoutInflater.from(this).inflate(R.layout.inflater_layout, layout, true);//layout.addView(view);//第三种方式,布局成功//想让R.layout.inflater_layout的根节点的属性(宽高)有效,又不想让其处于某一个容器里面View view = LayoutInflater.from(this).inflate(R.layout.inflater_layout, layout, false);layout.addView(view);}
}

第三种方式布局有效,会执行:

               if (root != null) {// Create layout params that match root, if suppliedparams = 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);}}

第四种方式:

public class InflaterActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);supportRequestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.inflater_activity);LinearLayout layout = findViewById(R.id.ll);//第一种方式成功添加//LayoutInflater.from(this).inflate(R.layout.inflater_layout, layout, true);//第二种方式,报错The specified child already has a parent. You must call removeView() on the child's parent first.//View view = LayoutInflater.from(this).inflate(R.layout.inflater_layout, layout, true);//layout.addView(view);//第三种方式,布局成功//想让R.layout.inflater_layout的根节点的属性(宽高)有效,又不想让其处于某一个容器里面//View view = LayoutInflater.from(this).inflate(R.layout.inflater_layout, layout, false);//layout.addView(view);//第四种方式,布局成功//root为空的时候,不管第三个参数是什么,效果都是一样的//R.layout.inflater_layout根布局的宽高失效,只是包裹子View//但是子View(button)有效,因为Button是在容器下的View view = LayoutInflater.from(this).inflate(R.layout.inflater_layout, null, false);layout.addView(view);}
}

 总结:

当xml的根布局是merge的话,添加到root里面。三个参数必须都填且最后一个参数是true。

当xml的根布局不是merge的话:

当root为空时,不管第三个参数是true还是false,xml的根布局的宽高都无效,且需要手动addView到root里面;

当root不为空时,第三个参数为true的话,xml根布局宽高有效,且自动帮我们添加到root里面;第三个参数为false的话,xml根布局宽高有效,需要我们手动addView到root里面。

三.标签特性

merge

1.优化布局,使用merge减少一次循环解析

2.必须用作更布局

include

1.如果include的标签设置了id,findViewById去查找通过include标签映入的xml的根布局的id是找不到的,会被include的标签的id给覆盖

2.不能用作根布局

ViewStub

1.与include差不多

2.viewStub构造函数里面会隐藏viewStub,具有懒加载作用

(在调用inflate()或者setVisibility()时,ViewStub才会加载真正的布局资源并在控件层级结构中替换为真正的控件,同时ViewStub从控件层级结构中移除,这是“懒加载”的核心思想)

这篇关于inflate流程分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/601757

相关文章

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型的操作流程

《0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeekR1模型的操作流程》DeepSeekR1模型凭借其强大的自然语言处理能力,在未来具有广阔的应用前景,有望在多个领域发... 目录0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型,3步搞定一个应

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

springboot启动流程过程

《springboot启动流程过程》SpringBoot简化了Spring框架的使用,通过创建`SpringApplication`对象,判断应用类型并设置初始化器和监听器,在`run`方法中,读取配... 目录springboot启动流程springboot程序启动入口1.创建SpringApplicat

通过prometheus监控Tomcat运行状态的操作流程

《通过prometheus监控Tomcat运行状态的操作流程》文章介绍了如何安装和配置Tomcat,并使用Prometheus和TomcatExporter来监控Tomcat的运行状态,文章详细讲解了... 目录Tomcat安装配置以及prometheus监控Tomcat一. 安装并配置tomcat1、安装

MySQL的cpu使用率100%的问题排查流程

《MySQL的cpu使用率100%的问题排查流程》线上mysql服务器经常性出现cpu使用率100%的告警,因此本文整理一下排查该问题的常规流程,文中通过代码示例讲解的非常详细,对大家的学习或工作有一... 目录1. 确认CPU占用来源2. 实时分析mysql活动3. 分析慢查询与执行计划4. 检查索引与表

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

Git提交代码详细流程及问题总结

《Git提交代码详细流程及问题总结》:本文主要介绍Git的三大分区,分别是工作区、暂存区和版本库,并详细描述了提交、推送、拉取代码和合并分支的流程,文中通过代码介绍的非常详解,需要的朋友可以参考下... 目录1.git 三大分区2.Git提交、推送、拉取代码、合并分支详细流程3.问题总结4.git push

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一