您的位置:首页 > 移动开发 > Android开发

Android中View创建的流程

2017-08-08 15:27 357 查看

一、了解LayoutInflater

LAYOUT_INFLATER_SERVICE与 AMS 和 WS 一样都是属于Android Framework层的服务,它们都是单例存在的,第一次加载ContextImpl的时候会将LayoutInflater的ServiceFetcher注入容器中。

/**
* 该方法在ContextImpl中, 作用是将服务注入Service容器的Map中, 只有第一次创建ContextImpl时执行
* 创建Activity时其对应的ContextImpl对象就会被创建, 它是Context的具体实现
*/
registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext);
}
});

//PolicyManager的makeNewLayoutInflater方法会调用Policy的makeNewLayoutInflater方法
public LayoutInflater makeNewLayoutInflater(Context context) {
return new PhoneLayoutInflater(context);
}


LayoutInflater是一个抽象类,我们通常通过:
LayoutInflater.from().inflate();
这样的方式让LayoutInflater去加载我们在XML中定义的布局。

其中的from()方法即为我们获取LAYOUT_INFLATER_SERVICE的方式, 代码如下:

public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
//省略异常捕捉的代码..
return LayoutInflater;
}


由此可知LayoutInflater的实例是通过getSystemService(Context.LAYOUT_INFLATER_SERVICE)获取到的, 但是LayoutInflater是一个抽象类,具体实现它的子类是什么呢?从源码中可知,它的具体实现是PhoneLayoutInflater(),这个原理与Window和PhoneWindow的关系很像(源码可自行查阅)。getSystemService(Context.LAYOUT_INFLATER_SERVICE)方法返回的即为实现了LayoutInflater中抽象方法的PhoneLayoutInflater的实例。

二、View构建的流程

我们从最常见的Activity中的setContentView开始分析

public void setContentView(int layoutResID) {
//getWindow()获取的即为Window的实例
getWindow().setContentView(int layoutResID);
initActionBar();
}


由源码可知, Activity中的setContentView会调用Window类下的setContentView方法。前面叙述过Window与LayoutInflater都是抽象的, 所以Window类下的setContentView方法的具体实现是为PhoneWindow中的setContentView(int layoutResID), 源码如下

@Override
public void setContentView(int layoutResID) {
//如果mContentParent没有被创建,那么将执行installDecor方法
//该方法会创建顶层的DecorView,以及其内部的mCotentParent。
if(mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
//解析我们传入的layoutResId(R.layout.XXXXX)
//由此处可知,我们传入的xml布局文件将加入mContentParent中,
//这也是为什么叫setContentView而不是setDecorView的原因
mLayoutInflater.inflate(layoutResID, mContentParent);
}


PhoneLayoutInflater中的inflate方法有很多:

/**
* @param resource:为我们定义的xml布局(R.layout.xxx)
* @param root:可用于存放我们xml布局的父容器
*/
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}

/**
* @param resource:为我们定义的xml布局(R.layout.xxx)
* @param root:可用于存放我们xml布局的父容器
* @param attachToRoot:该boolean变量为是否将我们xml布局加入root中
*/
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
/**
* @param parser: 为xml解析器
* @param root: 可用于存放我们xml布局的父容器
* @param attachToRoot: 该boolean变量为是否将我们xml布局加入root中
*/
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot);


由上述的源码可知: 无论我们调用哪一种inflate方法,最终都会由第三的方法执行最终的操作,第三个方法即为View构建的核心之处。下面开始解析源码:

/**
* @param parser: 为xml解析器
* @param root: 可用于存放我们xml布局的父容器
* @param attachToRoot: 该boolean变量为是否将我们xml布局加入root中
*/
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
//可用于存放我们xml布局的父容器
View result = root;

try {
int type;
//1.通过XML解析器解析我们传入的xml布局中的顶层容器(根标签)信息,并把它赋给type
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!");
}
//解析xml布局中顶层容器(根标签)的控件名,例如(LinearLayout)
final String name = parser.getName();

//2.判断xml中的顶层容器(根标签)是否为merge
if (TAG_MERGE.equals(name)) {
//如果为merge标签,则通过rInflate方法遍历该xml树,创建view实例
rInflate(parser, root, attrs, false);

//3.若不是merge标签,则创建该顶层容器(根标签)的实例为temp
} else {
//创建根标签实例
final View temp = createViewFromTag(root, name, attrs);
//声明一个布局参数
ViewGroup.LayoutParams params = null;
if (root != null) {
// 根据传入的root获取布局参数的信息
params = root.generateLayoutParams(attrs);
// 则给temp设置布局参数
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
//遍历temp下所有的子视图,创建其实例
rInflate(parser, temp, attrs, true, true);
//若root不为空且attachToRoot为true,则将xml布局加入root中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//若root为空,或者attachToRoot为false,则我们的xml布局即相当于root,将其直接返回
//所以方法上面的注释中的root为:可用于存放我们xml布局的父容器, 若传入null的话, temp的LayoutParams将会无效, 因为LayoutParams是由root生成的
if (root == null || !attachToRoot) {
result = temp;
}
}
}
//省略catch,finally代码...
return result;
}
}


上述代码即为inflate的最终执行过程,笔者精简了很多代码,并且加了很多注册, 非常清晰, 总结一下主要分为以下几步:

(1) 解析到xml中的顶层容器(根标签)

(2)如果根标签为merge,根标签的parentView即为root,此时会直接调用rInflate进行解析, rInflate中会通过递归的方式使用深度优先搜索解析xml,创建其childView的实例,并且加入其对应的parentView中。

(3)如果根标签为普通标签,则会创建该根标签的实例,并且设置其布局参数,同样会调用rInflate进行解析。若attachToRoot为true且root不为null,则将该标签将会被加入root中,否则该根标签自身即为root。

上述代码中sInflate方法的作用是遍历xml中的view树创建实例且将其加入对应的parentView中。(篇幅限制就不再赘述了),但具体创建view的操作时交给谁执行的呢? 其实上面的源码中已经出现了,是createViewFromTag()该方法完成了View创建的重任, 下面贴出源码:

/**
* @param parent: 该View的parent
* @param name: 该View的Name(LinearLayout)
* @param attrs: 该View的属性值
*/
View createViewFromTag(View parent, String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}

try {
//用户可以设置LayoutInflater的factory自行解析View,默认的Factory方法都为空, 忽略这一段
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, viewContext, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, viewContext, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs);
}
//重点部分从这儿开始
//判断name中的是否存在'.',若不存在则说明是系统控件
//因为系统控件我们在xml中使用的时候不需要加前缀
if (view == null) {
//解析系统控件
if (-1 == name.indexOf('.')) {
//onCreateView方法即为PhoneLayoutInflater中的重写的方法
//会给当前的View补全name前缀再调用createView()方法
view = onCreateView(parent, name, attrs);
} else {
//解析用户自定义控件,createView中会通过name利用反射的机制去构造View对象
//不再赘述
view = createView(name, null, attrs);
}
}
return view;
}
}


上述操作完成后,View的实例就已经创建好了, 但此时View虽有实例,并没有宽高。所以在Activity中的onCreate方法中去获取View的宽高是会报错的。接下来会调用onStart方法,并且在onResume方法之前将DecorView添加到WindowManager中,并且设置Activity为可见,然后通知AMS,该Activity已经变为resume状态了,使得系统能够渲染Activity的视图,至此Activity的视图就会显示出来了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: