Activtiy完全解析(二、layout的inflate过程)
转载请标明出处:
http://blog.csdn.net/xmxkf/article/details/52457893
本文出自:【openXu的博客】
在上一篇文章《Activtiy完全解析(一、Activity的创建过程)》中,我们探讨了Activity
的启动,从startActivity()
到进程创建,再到activity的创建,最后调用onCreate()
方法。本篇博客我们接着onCreate()
方法继续研究Activity
加载layout
的过程。我们写好layout
布局后,在onCreate()
方法中调用setContentView(layoutID)
就能将我们的布局加载显示。这一过程到底做了什么?布局中的控件是怎样被加载并显示出来的?我们从setContentView()
方法开始一步步分析 。(参考源码版本为API23 )
step1.Activity.setContentView()
通常我们的做法是在OnCreate()
方法中设置布局资源ID,Activity
提供了三个设置视图的方法,如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
//setContentView(new TextView(this));
//setContentView(new TextView(this),new ViewGroup.LayoutParams(...));
}
再看看Activity
中的调用关系:
public class Activity ...{
private Window mWindow;
//三个设置视图的方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
//初始化ActionBar
initWindowDecorActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
final void attach(...) {
...
//初始化mWindow
mWindow = new PhoneWindow(this);
...
}
public Window getWindow() {
return mWindow;
}
}
原来Activity
中调用setContentView()
方法最终调用的是PhoneWindow
对象mWindow
的setContentView(...)
方法。
mWindow
是在Activity
创建的时候在attach()
方法中初始化的,attach()
方法上一篇博客http://blog.csdn.net/xmxkf/article/details/52452218 中有讲解。
每一个Activity
组件都有一个关联的Window
的实现类PhoneWindow
的对象mWindow
,用来描述一个应用程序窗口,它封装了顶层窗口的外观和行为策略,它提供了标准的用户界面策略,如背景、标题区域、默认键处理等。PhoneWindow
管理着整个屏幕的内容,不包括屏幕最顶部的系统状态条。所以,PhoneWindow
或者Window
是与应用的一个页面所相关联。下面我们查看PhoneWindow.setContentView()
方法:
step2. PhoneWindow.setContentView()
@Override
public void setContentView ( int layoutResID){
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//移除该mContentParent内所有的所有子View
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
@Override
public void setContentView (View view){
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView (View view, ViewGroup.LayoutParams params){
//此处判断mContentParent是否为null,如果是null,则是第一次调用setContentView
if (mContentParent == null) {
//第一次需要初始化窗口和根布局(id为content的)
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//如果不是第一次调用,需要清空根布局中的内容
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
PhoneWindow
中setContentView(view)
接着调用setContentView(view, layoutParams)
,而setContentView(layoutResID)
和setContentView(view, layoutParams)
方法中的步骤是差不多的,唯一的区别就是setCOntentView(layoutResID)
中是通过mLayoutInflater.inflate(layoutResID, mContentParent)
将布局填充到mContentParent
上,而setContentView(view, layoutParams)
是将传过来的view
直接 add到mContentParent
中 。到这一步我们发现,我们为某个activity
写的layout
视图最终是添加到一个叫mContentParent
的ViewGroup
中了。
在加入mContentParent
中之前,首先判断如果mContentParent==null
时,执行了installDecor()
方法,我们猜想,installDecor()
的作用大概就是初始化mContentParent
,如果mContentParent
已经初始化,而且窗口不是透明的,就清除mContentParent
中的所有视图。mContentParent
只会实例化一次,所以如果我们在Activity
中多次调用setContentView()
只是改变了mContentParent
的子视图(也就是我们写的布局文件)。接着看看installDecor()
:
step3. PhoneWindow.installDecor()
上一步我们猜测installDecor()
方法的作用是实例化mContentParent
,接下来我们深入PhoneWindow
中验证这个猜测对不对,下面是PhoneWindow
的关键代码:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//id为content的容器,这个容器就是用于盛放我们写的layout视图的
private ViewGroup mContentParent;
//mContentRoot是整个界面内容,包括title和content等等
private ViewGroup mContentRoot;
//窗口顶层FrameLayout,用于盛放mContentRoot
private DecorView mDecor;
/**
* DecorView是FrameLayout的子类
*/
private final class DecorView extends FrameLayout{
...
}
//实例化了一个DecorView对象
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
/**
* 初始化顶层窗口mDecor和根布局mContentParent
*/
private void installDecor() {
if (mDecor == null) {
//①.初始化窗口
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
...
}
if (mContentParent == null) {
//②.如果根布局
mContentParent = generateLayout(mDecor);
//③.初始化title和一些设置
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
...
} else {
mTitleView = (TextView) findViewById(R.id.title);
if (mTitleView != null) {
mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
...
} else {
mTitleView.setText(mTitle);
}
}
}
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
...
}
}
}
PhoneWindow
中引用了一个DecorView
的对象,DecorView
是FrameLayout
的子类,相信你们应该多多少少知道Activity
的顶层窗口是一个FramLayout
,也正是这个DevorView
的对象mDecor。在installDecor()
方法中,第①步就是判断mDecor
是否为null,如果为null,将会调用generateDecor()
方法实例化一个DecorView
对象,紧接着第②步判断mContentParent
是否为null,如果为null,将调用generateLayout(mDecor)
方法初始化mContentParent
。现在就有一个疑问了,mDecor
和mContentParent
都是容器,他们是什么关系?各自代表的是屏幕中那一块的内容?带着问题我们看看generateLayout(mDecor)
方法:
step 4. PhoneWindow.generateLayout(mDecor )
/**
* 初始化根布局mContentParent
*/
protected ViewGroup generateLayout(DecorView decor) {
//①.获取AndroidManifest.xml中指定的themes主题
TypedArray a = getWindowStyle();
//设置当前窗口是否有标题
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
//请求指定Activity窗口的风格类型
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
requestFeature(FEATURE_ACTION_BAR);
}
...
//设置窗口是否全屏
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
...
//②.根据上面设置的窗口属性Features, 设置相应的修饰布局文件layoutResource,这些xml文件位于frameworks/base/core/res/res/layout下
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if
...
mDecor.startChanging();
//③.将第2步选定的布局文件inflate为View树,这也是整个窗口的内容(包括title、content等等)
View in = mLayoutInflater.inflate(layoutResource, null);
//④.将整个窗口内容添加到根mDecor中
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
//⑤.将整个窗口内容赋值给mContentRoot
mContentRoot = (ViewGroup) in;
//⑥.将窗口修饰布局文件中id="@android:id/content"的View赋值给mContentParent
ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
mDecor.finishChanging();
return contentParent;
}
- 在
generateLayout()
中,第①步是初始化了一些样式属性值,我们发现有一个很熟悉的类TypedArray
,没错,这在之前讲解自定义控件属性的时候用到过,其实此处也是一样的,之前自定义控件的属性是用来描述我们自定义的View
,而这里的样式属性是用来描述窗口,而我们知道窗口实质上也是View
,唯一的区别就是,控件的属性是在layout
中设置,而窗口的属性是在AndroidManifest.xml
中配置的,通过getWindowStyle()
获取当前Window
在theme
中定义的属性,window
支持的属性可以参考\frameworks\base\core\res\res\values\attrs.xml 中的<declare-styleable name="Window">
。获取到属性值之后有与大堆代码是调用setFlags()
和requestFeature()
给当前window
设置属性值,这就是为什么我们一般在Activity
的onCreate()
中设置全屏等属性需要在setContentView()
之前设置,因为setContentView()
之后installDecor()
方法已经执行完毕,所以设置是没有效的。 - 第①步执行完后,
Window
的相关属性已经设置完毕,比如是否是全屏?是否有标题等等。然后第②步就是根据Window
的各种属性,设置相应的布局文件,如果是全屏layoutResource
(布局文件ID)是多少,如果有标题…; - 确定
layoutResource
之后,第③步就是mLayoutInflater
解析出layoutResource
对应的视图in; - 第④步将in这个视图add到
mDecor
顶层窗口中; - 第⑤步将in赋值给mContentRoot;
- 第⑥步调用
findViewById()
找到id为content
的View
赋值给mContentParent
。
通过generateLayout ()
方法,我们发现了三个比较重要的视图对象mDecor
、mContentRoot
、mContentParent
。他们的关系如下:
mDecor
是Activity
的顶层窗体,他是FramLayout
的子类对象;mContentRoot
是根据设置给窗体加载的整个Activity
可见的视图,这个视图包含标题栏(如果主题设置有标题),用于容纳我们自定义layout
的id为content
的容器,mContentRoot
被添加到了顶层窗口mDecor
中;mContentParent
是mContentRoot
中id为content
的容器,这个容器就是用来添加我们写的layout
布局文件的,mContentParent
是嵌套在mContentRoot
中,mContentRoot
嵌套在mDecor
。所以在上面第⑥步可以直接调用findViewById()
找到mContentParent
。(跟踪findViewById()
方法,发现调用的是PhoneWindow
中mDecor
这个顶层窗口的findViewById()
方法 )
关系图如下:
step5. LayoutInflater.inflate()
经过上面的步骤,Activity
的顶层窗口mDecor
和用于容纳我们写的layout
的容器mContentParent
已经初始化完毕,接下来就是将我们的布局layout
添加到mContentParent
中,先把step2 中的PhoneWindow.setContentView(int layoutResID)
方法贴出来:
@Override
public void setContentView ( int layoutResID){
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//移除该mContentParent内所有的所有子View
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
在分析PhoneWindow.setContentView()
方法的过程中,我们穿插讲解了installDecor()
方法,接着有一个判断hasFeature(FEATURE_CONTENT_TRANSITIONS)
,这个属性是Android5.0引入的,这篇博客中参照的是API23的源码 ,他的意思根据设置的主题属性,判断当前窗口内容变化的过程是否需要动画,如果有动画标志,将执行动画。Scene
是场景,这个对象中引用了mSceneRoot
和mlayout
两个视图,mSceneRoot
就是mContentParent
,而mLayout
就是我们的布局视图。我们发现另一个setContentView(view)
方法中直接将view
传递进去了Scene newScene = new Scene(mContentParent, view);
,而setContentView(layoutResID)
是将layoutResID
传递进去,可以想象在Scene
中也会根据此layoutResID
将我们的布局layout
加载并赋值给mLayout
。由于我们主要研究的是View
的加载过程,所以就不深入讲解动画了。直接看下面的mLayoutInflater.inflate(layoutResID, mContentParent)
,这一步的目的就是解析我们的布局layout
,并将布局视图挂载到mContentParent
上,那下面看看LayoutInflater.inflate()
方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
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();
}
}
这个方法有三个参数:
resource
:需要解析的布局layout
的idroot
:解析layout
之后得到的视图层级的父视图attachToRoot
:是否将解析出来的视图添加到父视图中,如果传入true
,并且root
不为null,这个方法返回的是root
,而且将解析出的视图添加到root
中。而我们看到inflate(resource)
方法在inflate(resource,root,attachToRoot)
方法时,传入的attachToRoot
是root!=null
,所以inflate
返回的是已经将布局layout
视图添加到mContentParent
后的mContentParent
。
inflate(resource,root,attachToRoot )
方法中通过res.getLayout(resource)
将layout
关联到一个XmlResourceParser
中(Android内置的pull解析器),然后调用inflate(parser,root,attachToRoot )
方法:
/**
* 将layout解析为view树,并附加到root(mContentParent)中
*/
public View inflate(XmlPullParser parser, 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初始化为root(也就是mContentParent)
View result = root;
try {
int type;
//②.循环直到解析到开始标签<>或者结尾标签</>
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//第一次解析到的不是开始标签<>,说明layout文件没有<>标签,xml格式错误
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//③.找到第一个开始标签,这个标签对应的name就是整个layout最外层的父控件
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 {
// Temp is the root view that was found in the xml
//★④.根据layout中第一个开始标签的名称创建一个View对象temp,temp就是整个xml中的根控件
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// Create layout params that match root, if supplied
// 根据父控件获取布局参数,后面将解析的view树添加到root中是要使用
params = root.generateLayoutParams(attrs);
//如果不需要附加到root父控件中
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
//为temp设置布局参数如果我们不附加。(如果我们是,我们使用addView,下同)
temp.setLayoutParams(params);
}
}
// Inflate all children under temp against its context.
//★⑤.调用rInflateChildren递归解析temp中的所有子控件,通过这行代码整个layout就被解析为view树了
rInflateChildren(parser, temp, attrs, true);
//★⑥.如果root不为空,将view树添加到root中
//此处root为mContentParent,也就是将layout布局添加到mContentParent中了
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
//如果不用附加到root中,直接返回解析的view树
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}
这个方法的作用就是将layout
填充到一个view
树上,然后将view
树附加到root
(也就是mContentParent
)中,然后返回root
。
方法中有6个重要步骤,上面注释已经写得很清楚了,注意签名带★的步骤是很重要的。在①-④步是将layout
最外层的控件解析出来,在第④步中调用了createViewFromTag()
方法(请看step6) 根据name
实例化一个View
对象,然后第⑤步调用rInflateChildren()
方法,将剩余的控件解析出来后填充进最外层控件,这样就完成了整个layout
的填充,最后第⑥步将解析出来的view
树添加到root
中。我们发现真正完成inflate
的并不是这个方法,这个方法只是解析了在外层的控件,剩余的控件是由rInflateChildren()
方法完成的,而rInflateChildren()
中调用的是rInflate()
方法(请看step7)
step6. LayoutInflater.createViewFromTag()
/**
* 根据控件名实例化控件对象
* @param parent 父控件
* @param name 控件名
* @param context
* @param attrs
* @param ignoreThemeAttr
* @return
*/
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
try {
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//先判断name中是否有'.'字符,如果没有,此控件为android自带的View,此时会在name的前面加上包名"android.view."
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
//如果有这个'.',则认为是自定义View,因为自定义View在使用的时候使用的全名,所以直接创建
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
}
}
step7. LayoutInflater.rInflate()
/**
* 解析layout最外层parent中的所有子控件
* 此方法为递归方法,layout中有多少个ViewGroup就会递归调用多少次
* 每一次调用就会完成layout中某一个ViewGroup中所有的子控件的解析
*/
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
//如果遇到结束标签(</>)就结束,说明此parent中所有的子view已经解析完毕
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
//1.找到开始标签<>
final String name = parser.getName();
//2.根据name类型分别解析
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
/*
* 如果是<include />,调用parseInclude方法用于解析<include/>标签:
* ①.根据include标签的name属性找到对应的layout的id
* ②.遍历开始标签解析layout中的view
* ③.调用rInflateChildren(childParser, view, childAttrs, true)解析view中的子控件
* ④.将view添加(add)进parent中
*/
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
//如果是普通View,调用createViewFromTag创建view对象
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//★递归调用rInflateChildren解析view中的子控件
//如果view不是ViewGroup,rInflateChildren()会在while的第一次循环结束
//如果view是ViewGroup,并且里面有子控件,通过这行代码view中的所有子控件就被挂到view上了
rInflateChildren(parser, view, attrs, true);
//将view树添加到viewGroup中,到此为止完成一个view及其所有子控件的填充
viewGroup.addView(view, params);
}
}
if (finishInflate) {
/*
* ★parent的所有子控件都inflate完毕后调用onFinishInflate方法
* 这个方法在自定义ViewGroup的时候经常用到,自定义ViewGroup中
* 不能在构造方法中find子控件,因为构造方法中并没有完成子控件的实例化,
* 只能在onFinishInflate回调方法中findViewById来初始化子控件
*/
parent.onFinishInflate();
}
}
rInflate()
方法无非就是根据剩余的xml找到开始标签的name
,然后根据name
的类型分别解析,如果判断是普通控件,调用createViewFromTag()
创建一个控件view
,接着递归调用rInflateChildren()
解析view
中的所有子控件(如果view
是ViewGroup
),最后将view
添加到parent
中。rInflateChildren()
递归调用执行完毕后,整个layout
就被填充为view
树了,最后在inflate()
中,layout
的view
树会被add
到root
中,也就是mContentParent
中,整个窗体的view
树mDecor
就算是填充完毕。
总结一下这篇博客的内容,这篇博客分析的是View
的加载填充原理,也就是从调用setContentView()
方法开始,我们的布局layout
是怎样填充为整个View
树,并被挂载到Activity
上的。其中有几个重要的知识点如下:
- 每一个
Activity
组件都有一个关联的Window
的实现类PhoneWindow
的对象mWindow
,mWindow
管理着整个屏幕的内容,不包括屏幕最顶部的系统状态条 ,它描述一个应用程序窗口,它封装了顶层窗口的外观和行为策略,它提供了标准的用户界面策略,如背景、标题区域、默认键处理等; Activity
的setContentView()
方法里面调用的是PhoneWindow
的setContentView()
方法;PhoneWindow
中引用了mDecor
(顶层窗口,FramLayout
的子类)、mContentRoot
(整个Activity
的内容,包括TitleActionBar
等)、mContentParent
(mContentRoot
中id为content
的容器,用于放置我们的layout
的容器);他们三者的关系是mDecor
嵌套mContentRoot
,mContentRoot
嵌套mContentParent
;- 如果
mContentParent
不为null,将清空其中的内容,然后重新加载layout
到mContentParent
中 ; 如果mContentParent
为null说明是第一次调用setContentView
,这时候需要调用installDecor()
为Activity
加载一个顶层窗口mDecor
,mContentParent
; installDecor()
方法中初始化了mDecor
,然后调用generateLayout(mDecor)
;generateLayout(mDecor)
中首先设置了window
的主题样式,并根据这些样式设置为Activity
加载一个合适的布局视图,并将这个视图赋值给mContentRoot
,然后将此视图add到mDecor
顶层窗口中;然后通过mDecor.findViewById(R.id.content)
初始化mContentParent
。通过②-⑥步,Activity
中的顶层窗体的View
树算是搭建完毕了;setContentView(layoutId)
中完成上面步骤后紧接着调用LayoutInflater.inflate()
将我们传入的layoutId
填充为View
树,inflate()
只是解析了layout
布局的最外层父控件,里面的子控件是通过rInflateChildren()
方法递归解析完成的。在解析的过程中如果遇到开始<>
标签会调用createViewFromTag()
方法实例化一个View
对象,并解析为view
设置的属性attrs
。inflate()
方法执行完毕后,layout
就被映射为View
树了,然后将此View
树add
到mContentParent
中,整个Activity
的view
树就形成了。
下一篇博客我们讲解
Activity
的显示过程(Activtiy
中的View
是怎样显示出来的)。
Activtiy完全解析(二、layout的inflate过程)的更多相关文章
- Activtiy完全解析(三、View的显示过程measure、layout、draw)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/52840065 本文出自:[openXu的博客] 在Activity完全解析的第一篇文章A ...
- Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)
View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇 ...
- Spring Security 解析(二) —— 认证过程
Spring Security 解析(二) -- 认证过程 在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把Spring Security .S ...
- Fixflow引擎解析(二)(模型) - BPMN2.0读写
Fixflow引擎解析(四)(模型) - 通过EMF扩展BPMN2.0元素 Fixflow引擎解析(三)(模型) - 创建EMF模型来读写XML文件 Fixflow引擎解析(二)(模型) - BPMN ...
- APS.NET MVC4生成解析二维码简单Demo
一.视图 @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewpor ...
- Flink 源码解析 —— JobManager 处理 SubmitJob 的过程
JobManager 处理 SubmitJob https://t.zsxq.com/3JQJMzZ 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1 ...
- Flink 源码解析 —— TaskManager 处理 SubmitJob 的过程
TaskManager 处理 SubmitJob 的过程 https://t.zsxq.com/eu7mQZj 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink ...
- Sentinel源码解析二(Slot总览)
写在前面 本文继续来分析Sentinel的源码,上篇文章对Sentinel的调用过程做了深入分析,主要涉及到了两个概念:插槽链和Node节点.那么接下来我们就根据插槽链的调用关系来依次分析每个插槽(s ...
- Netty 源码解析(九): connect 过程和 bind 过程分析
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...
随机推荐
- 遗传算法详解(LINGO及MatlabGA工具箱求解实现)
遗传算法 1.前言 遗传算法是一种基于生物界自然群体遗传进化机制的自适应全局优化概率搜索算法.它与传统算法不同,不依赖梯度信息,而是通过模拟自然进化过程来搜索最优解. 例子:兔子的遗传进化 有人说,现 ...
- 解决-Django使用filter过滤时间,无法获取月份的问题
django中的filter日期查询属性有:year.month.day.week_day.hour.minute.second 但是但我在使用过滤查询是却总是无法过滤出月份,各种查资料,最后才发现是 ...
- [LeetCode] Merge Two Binary Trees 合并二叉树
Given two binary trees and imagine that when you put one of them to cover the other, some nodes of t ...
- ABP领域层知识回顾之---实体
标题:重温ABP领域层 1. 前言 最近一段时间一直在看<ABP的开发指南>(基于DDD的经典分层架构思想).因为之前一段时间刚看完<领域驱动设计:软件核心复杂性应对之道>, ...
- [Codeforces 235B]Let's Play Osu!
Description 题库链接 有一个长度为 \(n\) 的 "XO" 序列,其中第 \(i\) 个位置 "O" 出现的概率为 \(p_i\) .一个序列的价 ...
- [SDOI 2008]沙拉公主的困惑
Description 大富翁国因为通货膨胀,以及假钞泛滥,政府决定推出一项新的政策:现有钞票编号范围为1到N的阶乘,但是,政府只发行编号与M!互质的钞票.房地产第一大户沙拉公主决定预测一下大富翁国现 ...
- [Awson原创]洪水(flood)
Description Awson是某国际学校信竞组的一只菜鸡.今年,该市发生了千年难遇的洪水.被监禁在学校的Awson不甘怠堕,想将自己投入到公益服务事业中去.这天,他偷了H老师的小电驴,偷偷地溜出 ...
- HDU 2082 找单词
Problem Description 假 设有x1个字母A, x2个字母B,..... x26个字母Z,同时假设字母A的价值为1,字母B的价值为2,..... 字母Z的价值为26.那么,对于给定的字 ...
- codeforces 868C Qualification Rounds
Snark and Philip are preparing the problemset for the upcoming pre-qualification round for semi-quar ...
- 【NOIP 2017】逛公园
Description 策策同学特别喜欢逛公园.公园可以看成一张N个点M条边构成的有向图,且没有 自环和重边.其中1号点是公园的入口,N号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要 ...