LayoutInflater 介绍
在 Android 中 LayoutInflater 是扮演着很重要的角色,很多时候我们忽略了它的重要性,因为它的重要性完 全被隐藏起来了,可以说是直接隐藏在了Activity , Fragment 等组件的光环之下了。
from(mContext) 源码解析
在 Android 系统中,我们经常以 Context 获取系统级别的服务,比如 AMS, WMS, LayoutInfoater 等,这些服务会在合适的时候注册在系统中,在我们需要的时候 getSS(String name) 通过系统的名字来获取。我们先来看一段代码:
这里我就拿 Activity setContentView() 举例
1 |
|
继续跟下去:
1 | /** |
跟下去发现是一个抽象类,我们找它的实现类:
1 |
|
我们在 AppCompatDelegateImpl 类找到了实现类,眼神好的是不是发现了上面的 LayoutInflater ,没错我们 Activity 最后也是通过 LayoutInflater 解析 XML 加载布局的,继续跟 from 函数:
1 | /** |
通过上面代码可以知道,LayoutInflater 是通过 Context 的 getSystemService(String name) 来获取到的。context 的 getSS 函数怎么获取到的勒,下面我们就来介绍下 Context 的源码。
Context
其实在 Application,Activity,Service 中都会存在一个 Context 对象,我们叫其上下文,可以通过这个上下文,启动 Activity,Service, 注册一个广播,获取系统服务等等操作,那么 Context 是怎么创建出来的勒,先来看一段代码:
1 | public abstract class Context {...} |
Context 是一个抽象类,我们找下它的实现类,我们知道在启动 Activity 的时候有一个 Context 上下文,启动 Activity 的入口在 ActivityThread main 函数,我们就从这里开始找
1 | //通过反射调用执行的 |
1 | private void attach(boolean system) { |
在 main 方法中,我们创建了 ActivityThread 对象后,调用了其 attach 函数,并且参数为 false。在 attach 函数中,参数为 false 的情况下是属于非系统应用,会通过 Binder 机制与 AMS 通信,并且最终调用 H 类的 LAUNCH_ACTIVITY - > handleLaunchActivity 函数,我们看下该函数的实现:
1 | /** |
继续跟:
1 | /***启动 Activity 代码*/ |
1 | /***Context 的实现类 ContextImp*/ |
1 | /** |
通过上面代码 1- 5 的注释分析可知,Context 的实现类是 ContextImpl, 这里我们相当于又带着大家复习了一遍 Application , Activity 启动源码了。
getSystemService
通过上面我们得知 ContextImpl 是 Context 的实现类,我们继续看源码
1 | public class ContextImpl extends Context{ |
这里我们发现返回的是 SystemServiceRegistry 类里面的 getSystemService 函数,继续跟:
1 | /** |
1 | /** |
1 | /** |
到了这里我们知道了通过容器缓存拿到了 LayInflater 服务,那么什么时候注册的?下面我们继续看该类源码
registerService
1 | final class SystemServiceRegistry { |
1 | static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> { |
通过上面的代码可以知道抽象实现返回的是 new PhoneLayoutInflater(ctx.getOuterContext()); 那么这个 Phone… 到底什么了? 我们继续跟
1 | public class PhoneLayoutInflater extends LayoutInflater { |
真相大白啊,PhoneLayoutInflater 就是继承的 LayoutInflater。
总结
:
通过上面的代码可知,在虚拟机第一次加载该类时,通过 静态代码块 会注册各种 ServiceFatcher, 这其中就包含了 LayoutInflater Service, 将这些服务以键值对的形式存储在 Map 中, 用户使用时只需要根据 key 来获取对应的 ServiceFetcher, 然后通过 ServiceFetcher 对象的 getService 来获取具体服务对象。当第一次获取时,会调用 ServiceFetcher 的 createService 函数创建服务,然后缓存到一个列表中,下次再取直接从缓存中获取,从而避免了重复创建对象,从而达到了单例的效果,这不就是我之前介绍的单例模式-容器单例模式嘛,通过容器的单例模式实现方式,系统核心服务以单例形式存在,减少了资源消耗。
inflate 源码解析
1 |
|
跟 inflate
1 | public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { |
1 | public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { |
1 | /** |
上述 inflate 方法中,主要有下面几步:
- 解析 xml 中的根标签
- 如果根标签是 merge ,那么调用 rInflate 进行解析,rInflate 会将所有的子 View 添加到跟标签中
- 如果标签是普通元素,那么调用 createViewFromTag 对元素进行解析;
- 调动 rInflateChildren 解析 temp 根元素下的所有子 View, 并且将这些子 View 都添加到 temp 下
- 返回解析到的根视图;
我们在看一段代码,先从简单的理解:
1 | View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, |
本段代码重点就在注释 2 处,当这个 tag 的名字包含 “.” 时,认为这是一个内置 View, 也就是
1 | <TextView |
这里的 TextView 就是 XMl 标签的名字,因此,在执行 infate 时就会调用注释 3 处的 onCreateView 来解析 TextView 标签。那么,当我们自定义 View 时,就会执行注释 4
1 | <com.t01.TextView |
在上面的 PhoneLayoutInflater 重写了 onCreateView 方法,该方法就是在 View 标签名的前面设置了一个 “android.widget” 前缀,然后传递给 createView 解析。
那么我们来看下 createView 源码具体实现吧
1 | //根据完整的路径的类名通过反射机制构造 View 对象 |
createView 相当来说还比较理解,如果有前缀,那么就构造 View 的完整路径,并且将该类加载到虚拟机中,然后获取该类的构造函数并且缓存下来,在通过构造函数来创建该 View 的对象,最后将对象返回,这就是解析单个 View 的过程。而我们的窗口中时一个视图树, LayoutInflater 需要解析完这棵树,这个功能就交给 rInflateChildren 方法,看下面代码
1 | void rInflate(XmlPullParser parser, View parent, Context context, |
rInflateChildren 通过深度优先遍历来构造视图树,每解析到一个 View 元素就会递归调用 rInflateChildren ,直到这条路径的最后一个元素,然后在回溯过来将每一个 View 元素添加进 parent 中,通过 rInflateChildren 解析之后,整棵树就构建完毕了。当回调了 onResume 之后,setContentView 设置的内容就会出现在屏幕中了。
总结
LayoutInflater 涉及的知识源码还是挺多的,有 Application , Activity 的启动,还有深度广度遍历,XML 节点解析,容器单例模式。这里也相当于带着大家温习了一遍 Activity 启动流程吧。
好了,到了这里相信大家对 setContentView 之后干了些什么事儿,已经有一定了解了。
感谢你的阅读,谢谢!