Android中View的创建流程解析

article/2023/9/24 22:11:22

        在AppCompatActivity的onCreate方法中我们都知道setContentView这个方法是加载布局文件。这个方法使用很简单直接把layout布局文件放进去就可以了。那么具体内部是怎么将它显示到桌面的呢,今天就从setContentView()来解析一下Android中View的创建过程。

打开androidx/appcompat/app/AppCompatActivity.java,这里setContentView就一行代码,调用AppCompatDelegate的setContentView()。

    @Overridepublic void setContentView(@LayoutRes int layoutResID) {getDelegate().setContentView(layoutResID);}

AppCompatDelegate是一个抽象类,我们分析一下它的实现类AppCompatDelegateImpl在setContentView()方法中做了什么。

@Overridepublic void setContentView(int resId) {ensureSubDecor();//获取content视图ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);//移除viewGroup下的所有子ViewcontentParent.removeAllViews();//解析布局文件添加到content视图中LayoutInflater.from(mContext).inflate(resId, contentParent);mAppCompatWindowCallback.getWrapped().onContentChanged();}

打开/frameworks/base/core/java/android/view/LayoutInflater.java查看解析布局文件的LayoutInflater.inflate() 方法:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {...//获取布局解析器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);//取得XML标签名字final String name = parser.getName();if (TAG_MERGE.equals(name)) {//如果是merge标签调用rInflate方法,递归执行解析rInflate(parser, root, inflaterContext, attrs, false);} else {// 通过xml的tag来构建Viewfinal View temp = createViewFromTag(root, name, inflaterContext, attrs);ViewGroup.LayoutParams params = null;if (root != null) {//获取布局参数params = root.generateLayoutParams(attrs);if (!attachToRoot) {//设置临时的布局参数temp.setLayoutParams(params);}}//递归执行解析,继续解析temp下的子项,也走到了rInflate()方法rInflateChildren(parser, temp, attrs, true);// 如果root不为空并且attachToRoot为trueif (root != null && attachToRoot) {//将View填充到ViewGrouproot.addView(temp, params);}// 如果root为空 或者 attachToRoot为false,直接返回tempif (root == null || !attachToRoot) {result = temp;}}} catch (XmlPullParserException e) {...}return result;}}

在inflate方法中主要做了一下几步:

1.解析layout.xml文件的根标签

2.判断是否是merge,如果是merge那么调用rInflate()递归方法,实例化视图将View添加到ViewGroup中,然后调用onFinishInflate()。

3.如果不是merge,调用createViewFromTag()来创建view并添加到ViewGroup中,之后调用rInflate()继续递归解析子View;

4.通过attachToRoot,返回视图。

rInflate方法同样也调用createViewFromTag方法,打开createViewFromTag():

 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {...try {View view = tryCreateView(parent, name, context, attrs);if (view == null) {final Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = context;try {//通过.来判断是自定义View还是内置Viewif (-1 == name.indexOf('.')) {//创建内置Viewview = onCreateView(context, parent, name, attrs);} else {//创建自定义Viewview = createView(context, name, null, attrs);}} finally {mConstructorArgs[0] = lastContext;}}return view;} ...}
通过继续跟踪代码,发现自定义View还是内置View,最后都调用了createView方法:
@Nullablepublic 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 {if (constructor == null) {//类载入器, 初始化对象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);}}//从class中获取构造constructor = clazz.getConstructor(mConstructorSignature);constructor.setAccessible(true);//加入缓存中sConstructorMap.put(name, constructor);} else {...}Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = viewContext;Object[] args = mConstructorArgs;args[1] = attrs;try {//设置attrs属性,通过反射获取View实例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;}} ...}
  • 总结:

  • 通过 pull解析XML文件获取到 View 的标签;
  • 通过标签中是否有.判断是自定义View还是内置View
  • 通过反射的方式来创建 View 对象;
  • 通过深度优先的顺序遍历View, 形成View树;

http://www.ngui.cc/article/show-456513.html

相关文章

SWUST OJ#978 #979 #980 二叉树的遍历

目录 深度优先遍历 输出利用先序遍历创建的二叉树的前序遍历序列 思路 代码 #978 输出利用先序遍历创建的二叉树的中序遍历序列 题目 思路 代码 #979 输出利用先序遍历创建的二叉树的后序遍历序列 题目 思路 代码 广度优先遍历 #980 输出利用先序遍历创建的二叉树…

来可车载2路CAN/FD脱机总线数据记录仪WiFi通讯收发下载离线回放

CANLog-VCI 车用诊断记录设备是一款支持 WIFI 实 时传输、自带长时间报文存储功能、可以脱离 PC 独立运 行的 2 路 CAN/CAN FD 总线数据记录仪。该产品通过内 置 SD 卡记录数据&#xff0c;可通过 WIFI 实时传输数据到 PC 端&#xff0c; 便于用户监测数据和设备故障排查&#…

《大话设计》——观察者模式

目录UML类图代码&#xff1a;抽象观察者Observer&#xff1a;具体观察者ConcreteObserver:抽象通知者Subject:具体通知者ConcreteSubject:客户端Client:运行结果&#xff1a;举例&#xff1a; 观察者模式实现注册的投资者在股票市场发生变化是&#xff0c;可以自动得到通知的功…

windows扫雷的模拟实现(C语言)

扫雷是之前windows自带的经典小游戏&#xff08;目前已经没有默认安装了&#xff0c;需要store里下载&#xff09;&#xff0c;到现在全世界还拥有极多的粉丝。这里我们用C语言在控制台简单地模拟实现一下这个游戏&#xff0c;游戏的功能如下&#xff1a; 展示一张N*N的棋盘&am…

十四讲——2.4.3和2.4.4

CMake CMakeLists.txt告诉cmake我们要做什么&#xff0c;要遵守cmake语法。 cmake处理了文件之间的关系&#xff0c;make相当于调用g编译文件 #cmake版本 cmake_minimum_required(VERSION 2.8) #声明cmake工程 project(HelloSLAM) #添加一个可执行程序 可执行是带main函数的…

独家提出新概念,铸态机械性能

工业需求&#xff1a; 铸件在交付给制造厂之前&#xff0c;都经过了结构分析&#xff0c;力学性能分析&#xff0c;例如ABAQUS&#xff0c;也会考虑到结构的优化和裕度。并指定了相对应的力学性能测试标准。但在铸件实际生产之后&#xff0c;经过多次调试&#xff0c;都无法满足…

mysql中的字符串函数

– 字符串函数 – 1.获取字符串 select char_length(hello); -- 5– length 取长度&#xff0c;返回的单位是字节 select length(hellow) -- 5\ select length(你好吗); -- 9– 2.字符串合并 select concat(hellow,world);– 3.指定分隔符进行字符串的合并 select concat_w…

【串口服务器rs485通信教程】存储型网关工作模式

首先需要明白的是串口的通讯速率是远低于网口&#xff0c;主机在请求RTU设备通常只处理几个寄存器&#xff0c;“存储型网关”就是应用于这种工作环境。 “存储型网关”对主机请求的读取指令进行存储&#xff0c;当主机再次请求或者其他主机请求相同设备&#xff08;地址码相同…

【第一章】Python数据模型

Python数据模型1.1一摞Python风格的纸牌1.1一摞Python风格的纸牌

Janusgraph学习记录(4)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 Janusgraph学习记录&#xff08;4&#xff09; 前言 格式化输出结果 一、值操作 values(属性)和valueMap(属性)&#xff0c;分别返回值和键-值对&#xff0c;另外不建议直接…