本文出自博客Vander丶CSDN博客,如需转载请标明出处,尊重原创谢谢
博客地址:http://blog.csdn.net/l540675759/article/details/78099609
前言
如果读者没有阅读过该系列博客,建议先阅读下博文说明,这样会对后续的阅读博客思路上会有一个清晰的认识。
Android 中LayoutInflater(布局加载器)系列博文说明
导航
Android 中LayoutInflater(布局加载器)系列博文说明
Android 中LayoutInflater(布局加载器)系列之介绍篇
Android 中LayoutInflater(布局加载器)系列之源码篇
Android 中LayoutInflater(布局加载器)源码篇之createViewFromTag方法
Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
Android 中LayoutInflater(布局加载器)源码篇之parseInclude方法
Android 中LayoutInflater(布局加载器)之实战篇
概述
(1)Activity 的 getSystemService的实现过程
(2)LayoutInflater 如果将布局资源转换为 View 的过程
(3)LayoutInflater的 Factory,Factory2是什么,在解析过程中的作用是什么?
(4)LayoutInflater 的 inflater 方法的各个参数的含义,不同的情况的含义
LayoutInflater的构造方法
protected LayoutInflater(Context context) {
mContext = context;
}
这种是LayoutInflater常规的构造方法,将Context传入,最后生成的LayoutInflater与对应的Context相绑定。
protected LayoutInflater(LayoutInflater original, Context newContext) {
mContext = newContext;
mFactory = original.mFactory;
mFactory2 = original.mFactory2;
mPrivateFactory = original.mPrivateFactory;
setFilter(original.mFilter);
}
而这种构造方法来说,只是复制原LayoutInflater的内容,然后将Context对象替换,一般来说只会在cloneInContext()方法中使用。
LayoutInflater#form()方法分析
根据介绍篇的内容,LayoutInflater在Android开发中一般是通过
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LayoutInflater.from(context);
因为第一种方式,已经是LayoutInflater介绍中声明获取的方式之一,那么这里我们看一下LayoutInflater#form的方法。
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
从源码上看,LayoutInflater#form()方法内部也是通过getSystemService()方法获得,那么接下来我们看一下context#getSystemService()这个方法:
public abstract Object getSystemService(@ServiceName @NonNull String name);
发现这个只是一个抽象方法,而我们知道Activity也是Context的一个实现。
Activity#getSystemService()这个方法:
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
//获取WindowManager
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
//系统的搜索框SearchManager
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
从上面看到,在Activity中只处理了两种类型的服务,分别是获取WindowManager、获取SearchManager,那我们接着看其父类的SystemService()方法:
@Override
public Object getSystemService(String name) {
//找到我们要的东西,注意这是个单例
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
在Activity的父类即ContextThemeWrapper的getSystemService()方法中,我们发现了LayoutInflater的创建过程,从上面的代码我们可以看出:
每个Activity内包含的LayoutInflater是一个单例。
Activity创建LayoutInflater时,是先使用最原始的BaseContext创建,然后在将Activity的父类ContextThemeWrapper的信息通过cloneInContext()方法与其绑定。
然后我们在看下LayoutInflater的cloneInContext的实现:
public abstract LayoutInflater cloneInContext(Context newContext);
先看下,这个方法的介绍:
这个方法通过现有的LayoutInflater创建一个新的LayoutInflater副本,唯一变化的地方是指向不同的上下文对象。
在ContextThemeWrapper通过这个方法创建的新的LayoutInflater还包含了主题的信息。
在ContextThemeWrapper中使用cloneInContext是想将更多的信息,赋予LayoutInflater中,与其相互绑定。
Activity中LayoutInflater创建
对于Activity的LayoutInflater,其实在Activity创建之时就已经创建完成,但是这一块内容属于FrameWork层的内容,博主道行太浅了,只想带大家看下from这个方法的实现过程。
这里如果大家想了解可以参考下这篇文章
而Activity#getLayoutInflater方法:
@NonNull
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
这个Window对象即PhoneWindow,此时创建出来的LayoutInflater即PhoneLayoutInflater。
这里给大家看下PhoneLayoutInflater的cloneInContext()方法:
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
}
可以发现PhoneLayoutInflater中cloneInContext()的实现,调用了第二个构造方法。
这里在Android Studio是无法查阅的,有条件的可以下载源码,如果下载源码麻烦,可以在这里查阅。
将R.layout.xxx转换为View的过程分析
其实这个过程即LayoutInflater.inflater()这个过程:
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) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
在这个方法中,只是先拿到XmlResourceParser,用于后续节点的解析,我们接着往下看:
这里只看一些关键的信息,具体代码大家自行查看
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
//》》》》》》》》》》》》》》》》》第一部分》》》》》》》》》》》》》》》》》》》
try {
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName()
//》》》》》》》》》》》》》》》》》第二部分》》》》》》》》》》》》》》》》》》》
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 {
//》》》》》》》》》》》》》》》》》第三部分》》》》》》》》》》》》》》》》》》》
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
return result;
}
}
第一部分:
这里第一部分的内容,主要是一个XML文件的读取过程,这里有两个判断:
(1)遍历XML内容寻找XML标签的开始的标志或者文档结尾的标志才可以跳出循环。
(2)如果该XML没有开始的标识,则抛出异常。
下面给大家介绍下,几种常见的解析标识:
XmlPullParser.START_DOCUMENT 文档开始
XmlPullParser.END_DOCUMENT 文档结束
XmlPullParser.START_TAG XML标签的开始
XmlPullParser.END_TAG XML标签的结束
XmlPullParser.TEXT XML标签的内容
第二部分
这部分的一开始先进行了Merge标签的检验,如果发现该节点是Merge,必须满足父View存在,并且与父View绑定的状态。
转换为代码:
root != null && attachToRoot ==true
这里Merge是减少布局层级存在的标签,通常和include标签一起使用,所以其必须存在父View,而且merge标签的内容必须与父View绑定。
这里调用rInflate()方法去解析Merge的标签,而rInflate()方法,在另一篇文章已经单独分析。
Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
第三部分
我们再看一下第三部分的代码,代码中会有一些简要的说明:
//》》》》》》》》》》》》》》》》》第三部分》》》》》》》》》》》》》》》》》》》
//createViewFromTag是一个根据name来创建View的方法
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
//解析子标签
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
return result;
}
将第三部分内容分拆一下主要分为以下几块内容:
排除标签为include,或者merge之后,就会通过createViewFromTag()方法来创建View
root是inflater()方法的第二个参数,而attachToRoot是第三个参数,最后会根据这两个参数来决定返回的View
在这部分中,createViewFromTag()是根据name(名称),来创建View的一个方法。
由于createViewFromTag()方法的通用性,这块内容博主给单独拿出来,链接如下:
Android 中LayoutInflater(布局加载器)源码篇之createViewFromTag方法
接下来,我们要介绍的是inflater()方法中的参数,到底有什么作用?
ViewGroup.LayoutParams params = null;
//当Root存在
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//设置View在父布局下Params
temp.setLayoutParams(params);
}
}
//遍历子节点
rInflateChildren(parser, temp, attrs, true);
//如果Root存在并且attachToRoot为true,即与父View绑定
//这里在解析的同时,就会将其添加至父View上
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//如果父Viewwe为null或者没有绑定父View都会将当前解析的View返回,否则返回父View
if (root == null || !attachToRoot) {
result = temp;
}
}
仔细分析上述代码,可以得出如下结论:
从这段代码中,得出以下几个结论:
当root为null时,attachToRoot参数无效,而解析出的View作为一个独立的View存在(不存在LayoutParams)。
当root不为null时,attactToRoot为false,那么会给该View设置一个父View的约束(LayoutParams),然后将其返回。
当root不为null时,attactToRoot为true,那么该View会被直接addView进父View,然后会将父View返回。
当root不为null的话,attactToRoot的默认值是true。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
上面的代码中,我们还少分析了一处代码rInflateChildren(),即解析子类:
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
可以看到这里面解析子类调用了rInflate方法, 在来一次rInflate()的分析连接。
Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
如果你之前没看过这段代码,其实你会像博主之前一样,一直在试,而不知道这段代码正确的含义,但是有时候源码会是一个很好的老师,通过它能够得到你想要的。