点赞关注,不再迷路,你的支持对我意义重大!
🔥 Hi,我是丑丑。本文 GitHub · Android-NoteBook 已收录,这里有 Android 进阶成长路线笔记 & 博客,欢迎跟着彭丑丑一起成长。(联系方式在 GitHub)
前言
- 在 Android UI 开发中,经常需要用到 LayoutInflater 类,它的基本作用是将 xml 布局文件解析成 View / View 树。除了基本的布局解析功能,LayoutInflater 还可以用于实现 动态换肤、视图转换、属性转换 等需求。
- 在这篇文章里,我将带你理解 LayoutInflater 的源码。另外,文末的应试建议也不要错过哦,如果能帮上忙,请务必点赞加关注,这真的对我非常重要。
相关文章
- 《Android | 一个进程有多少个 Context 对象(答对的不多)》
- 《Android | 带你探究 LayoutInflater 布局解析原理》
- 《Android | View & Fragment & Window 的 getContext() 一定返回 Activity 吗?》
- 《Android | 说说从 android:text 到 TextView 的过程》
目录
- 获取 LayoutInflater 对象
1 | kotlin复制代码@SystemService(Context.LAYOUT_INFLATER_SERVICE) |
首先,你要获得 LayoutInflater 的实例,由于 LayoutInflater 是抽象类,不能直接创建对象,因此这里总结一下获取 LayoutInflater 对象的方法。具体如下:
1. View.inflate(...)
1 | less复制代码public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) { |
2. Activity#getLayoutInflater()
1 | scss复制代码public LayoutInflater getLayoutInflater() { |
3. PhoneWindow#getLayoutInflater()
1 | csharp复制代码private LayoutInflater mLayoutInflater; |
4. LayoutInflater#from(Context)
1 | java复制代码public static LayoutInflater from(Context context) { |
可以看到,前面 3 种方法最后走到LayoutInflater#from(Context)
,这其实也是平时用的最多的方式。现在,我们看getSystemService(...)
内的逻辑:
ContextImpl.java
1 | typescript复制代码@Override |
SystemServiceRegistry.java
1 | typescript复制代码private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new ArrayMap<String, ServiceFetcher<?>>(); |
可以看到,ContextImpl 内部通过 SystemServiceRegistry 来获取服务对象,逻辑并不复杂:
1、静态代码块注册了 name - ServiceFetcher 的映射
2、根据 name 获得 ServiceFetcher
3、ServiceFetcher 创建对象
ServiceFetcher 的子类有三种类型,它们的getSystemService()
都是线程安全的,主要差别体现在 单例范围,具体如下:
ServiceFetcher子类 | 单例范围 | 描述 | 举例 |
---|---|---|---|
CachedServiceFetcher | ContextImpl域 | / | LayoutInflater、LocationManager等(最多) |
StaticServiceFetcher | 进程域 | / | InputManager、JobScheduler等 |
StaticApplicationContextServiceFetcher | 进程域 | 使用 ApplicationContext 创建服务 | ConnectivityManager |
对于 LayoutInflater 来说,服务获取器是 CachedServiceFetcher 的子类,最终获得的服务对象为 PhoneLayoutInflater。
这里有一个重点,这句代码非常隐蔽,要留意:
1 | arduino复制代码 return new PhoneLayoutInflater(ctx.getOuterContext()); |
LayoutInflater.java
1 | csharp复制代码public Context getContext() { |
可以看到,实例化 PhoneLayoutInflater 时使用了 getOuterContext(),也就是参数使用的是 ContextImpl 的代理对象,一般就是 Activity 了。也就是说,在 Activity / Fragment / View / Dialog 中,获取LayoutInflater#getContext()
,返回的就是 Activity。
小结:
- 1、获取 LayoutInflater 对象只有通过
LayoutInflater.from(context)
,内部委派给Context#getSystemService(...)
,线程安全; - 2、使用同一个 Context 对象,获得的 LayoutInflater 是单例;
- 3、LayoutInflater 的实现类是 PhoneLayoutInflater。
- inflate(…) 主流程源码分析
上一节,我们分析了获取 LayoutInflater 对象的过程,现在我们可以调用inflate()
进行布局解析了。LayoutInflater#inflate(...)
有多个重载方法,最终都会调用到:
1 | less复制代码public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { |
tryInflatePrecompiled(...)
是解析预编译的布局,我后文再说;- 构造 XmlPull 解析器 XmlResourceParser
- 执行解析,是解析的主流程
提示: 在这里,我剔除了与 XmlPull 相关的代码,只保留了我们关心的逻辑:
1 | java复制代码public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { |
关于 <include> & <merge>
,我后文再说。对于参数 root & attachToRoot
的不同情况,对应得到的输出不同,我总结为一张图:
- createViewFromTag():从 到 View
在 第 2 节 主流程代码中,用到了 createViewFromTag()
,它负责由 创建 View 对象:
1 | ini复制代码已简化 |
- 应用 ContextThemeWrapper 以支持
android:theme
,这是处理针对特定 View 设置主题;
- 应用 ContextThemeWrapper 以支持
- 使用
Factory2 / Factory
实例化 View,相当于拦截,我后文再说;
- 使用
- 使用
mPrivateFactory
实例化View,相当于拦截,我后文再说;
- 使用
- 调用 LayoutInflater 自身逻辑,分为:
- 4.1 中没有
.
,这是处理<linearlayout>、<TextView>
等标签,依次尝试拼接 3 个路径前缀,进入 3.2 实例化 View - 4.2 中有
.
,真正实例化 View 的地方,主要分为 4 步:1
2
3
4sql复制代码1) 缓存的构造器
2) 新建构造器
3) 实例化 View 对象
4) ViewStub 特殊处理
- 4.1 中没有
- 调用 LayoutInflater 自身逻辑,分为:
小结:
- 使用 Factory2 接口可以拦截实例化 View 对象的步骤;
- 实例化 View 的优先顺序为:Factory2 / Factory -> mPrivateFactory -> PhoneLayoutInflater;
- 使用反射实例化 View 对象,同时构造器对象做了缓存;
- Factory2 接口
现在我们来讨论Factory2
接口,上一节提到,Factory2
可以拦截实例化 View 的步骤,在 LayoutInflater 中有两个方法可以设置:LayoutInflater.java
1 | ini复制代码方法1: |
现在,我们来看源码中哪里调用这两个方法:
4.1 setFactory2()
在 AppCompatActivity & AppCompatDialog 中,相关源码简化如下:
AppCompatDialog.java
1 | scss复制代码@Override |
AppCompatActivity.java
1 | scss复制代码@Override |
AppCompatDelegateImpl.java
1 | csharp复制代码public void installViewFactory() { |
LayoutInflaterCompat.java
1 | less复制代码public static void setFactory2(@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) { |
可以看到,在 AppCompatDialog & AppCompatActivity 初始化时,都通过setFactory2()
设置了拦截器,设置的对象是 AppCompatDelegateImpl:
AppCompatDelegateImpl.java
1 | less复制代码已简化 |
AppCompatViewInflater 与 LayoutInflater 的核心流程差不多,主要差别是前者会将<TextView>
等标签解析为AppCompatTextView
对象:
AppCompatViewInflater.java
1 | java复制代码final View createView(...) { |
4.2 setPrivateFactory()
setPrivateFactory()
是 hide 方法,在 Activity 中调用,相关源码简化如下:
Activity.java
1 | java复制代码final FragmentController mFragments = FragmentController.createController(new HostCallbacks()); |
可以看到,这里设置的 Factory2 其实就是 Activity 本身(this),这说明 Activity 也实现了 Factory2 :
1 | scala复制代码public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2,...{ |
原来<fragment>
标签的处理是在这里设置的 Factory2 处理的,关于FragmentController#onCreateView(...)
内部如何生成 Fragment 以及返回 View 的逻辑,我们在这篇文章里讨论,请关注:《你真的懂 Fragment 吗?—— AndroidX Fragment 核心原理分析》。
小结:
- 使用 setFactory2() 和 setPrivateFactory() 可以设置 Factory2 接口(拦截器),其中同一个 LayoutInflater 的
setFactory2()
不能重复设置,setPrivateFactory() 是 hide 方法; - AppCompatDialog & AppCompatActivity 初始化时,调用了
setFactory2()
,会将一些<tag>
转换为AppCompat
版本; - Activity 初始化时,调用了
setPrivateFactory()
,用来处理<fragment>
标签。
& &
这一节,我们专门来讨论<include> & <merge> & <ViewStub>
的用法与注意事项:
5.1 布局重用
5.2 降低布局层次
5.3 布局懒加载
Editting…
- 总结
- 应试建议
- 理解 获取 LayoutInflater 对象的方式,知晓 3 种 getSystemService() 单例域的区别,其中 Context 域是最多的,LayoutInflater 是属于 Context 域。
- 重点理解 LayoutInflater 布局解析的 核心流程;
- Factory2 是一个很实用的接口,需要掌握通过 setFactory2() 拦截布局解析 的技巧。
推荐阅读
- 密码学 | Base64是加密算法吗?
- 算法面试题 | 回溯算法解题框架
- 算法面试题 | 链表问题总结
- Java | 带你理解 ServiceLoader 的原理与设计思想
- Android | 面试必问的 Handler,你确定不看看?
- Android | 带你理解 NativeAllocationRegistry 的原理与设计思想
- 计算机组成原理 | Unicode 和 UTF-8是什么关系?
- 计算机组成原理 | 为什么浮点数运算不精确?(阿里笔试)
- 计算机网络 | 图解 DNS & HTTPDNS 原理
感谢喜欢!你的点赞是对我最大的鼓励!欢迎关注彭旭锐的GitHub!
本文转载自: 掘金