上头已经完整的介绍了Activity的启动流程,Activity是如何绑定Window,Window的décor view是如何通过ViewRootImpl与WMS建立关联的,也就是说,整个框架已经有了,唯一缺的就是Activity如何初始化创建DecorView了。
接下去通过相对不是那么复杂的LinearLayout,来完整的介绍Décor View以及其内部
ContentView的详细创建过程
首先定义一个Layout文件activity_main.xml:
android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World --- One" /> android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World --- Two" /> |
这个Layout布局很简单,LinearLayout内置两个TextView,接着在Activity.onCreate的时候将其作为ContentView设置到Activity:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } |
接着看setContentView:
//Activity.java public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); } |
最终会把content view设置到Activity关联Window那,接着看PhoneWindow.setContentView:
public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } |
这里ContentParent肯定为null,接着调用installDecor:
private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } if (mContentParent == null) { mContentParent = generateLayout(mDecor); …… } } |
mDecor View在初始状态下肯定为null,所以这里肯定会调用generateDecor创建DecorView:
protected DecorView generateDecor() { return new DecorView(getContext(), -1); } |
DecorView派生自FrameLayout:
private final class DecorView extends FrameLayout |
由于mContentParent在初始状态下也为空,所以接着调用generateLayout创建Content Root
View:
protected ViewGroup generateLayout(DecorView decor) { …… TypedArray a = getWindowStyle(); …… mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } …… mDecor.finishChanging(); return contentParent; } |
先根据layoutResource对应的layout文件创建View作为mContentRoot并添加到DecorView,接着从mContentRoot查找名称为ID_ANDROID_CONTENT的child view作为content Parent并最终保存到mContentParent.
installDecor完成后,接着我们回到setContentView, 接着执行的代码为:
mLayoutInflater.inflate(layoutResID, mContentParent); |
根据layoutResID指定的layout文件创建View并添加到mContentParent中
setContentView结束后,DecorView创建完成,接下去我们看下DécorView完整的布局结构图:
通过图可以很清晰的看出,DecorView是top level view,Content Root是其子视图,Content Root用于显示内容,主要包含:
1) Action bar view
2) Content Parent view
我们在Activity.onCreate中调用setContentView设置的自定义View是添加到Content Parent
View中的
在开发过程中,我们可以通过编写layout文件,然后在代码中使用inflate并传入该layout文件实现对View的创建,比如上文中ContentView的创建:
mLayoutInflater.inflate(layoutResID, mContentParent); |
上面代码通过inflate创建layoutResID对应的View并添加到mContentParent中,接下去详细介绍下inflate的实现
为了便于后续分析,先简单介绍下在app工程编译时,layout文件是被怎样处理的,从代码上看,Android应该是支持两种处理方式的
比如原先的Layout内部描述是这样的:
|
第一种处理方式:
将layout内部所有View的tag从类名改成view,原先的tag在处理完后,会被保存到名称为class的属性字段中,编译处理后,会变成:
class=”LinearLayout”> |
Tag统一改为view,然后添加class属性用于描述view的class path,如果是自定义视图,这里需要给出全路径,如果是系统定义视图,则给出相对路径即可,inflate在解析的时候,会自动补全android.view.前缀
第二种处理方式:
编译处理后,会变成:
|
也就是说,在TAG name中把class path补全了
最新的系统版本应该都用的第二种方式,所以后续我们就基于第二种方式来分析
最后,android打包工具会将layout数据重新格式化并保存到新的数据结构中,然后保存到layout同名文件并最终打包到apk中。
接下去通过代码,一步一步分析inflate的整个过程
先看下LayoutInflater的初始化代码:
mLayoutInflater = LayoutInflater.from(context); |
接着看LayoutInflater.from:
public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); …… return LayoutInflater; } |
其实就是对context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)调用做下封装,ContextImpl里头最终调用PolicyManager.makeNewLayoutInflater->
Policy. makeNewLayoutInflater创建LayoutInflater:
//Policy.java public LayoutInflater makeNewLayoutInflater(Context context) { return new PhoneLayoutInflater(context); } |
LayoutInflater创建结束,接着看对其inflate函数的调用
PhoneLayoutInflate派生自LayoutInflater,不过它没重新实现啥函数,基本上还是使用LayoutInflate的实现,inflate函数也是:
//LayoutInflater.java public View inflate(int resource, ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } } |
由于root不为空,所以attachToRoot为true
先调用res.getLayout获取resource对应layout文件的配置数据并保存到parser中,接着调用inflate对应的重载函数:
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; View result = root;
try { // Look for the root node. int 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!"); }
final String name = parser.getName(); …… if (TAG_MERGE.equals(name)) { …… } else { // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, attrs, false);
ViewGroup.LayoutParams params = null;
if (root != null) { …… // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } …… // Inflate all children under temp rInflate(parser, temp, attrs, true, true); …… // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); }
// Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; } }
} catch (XmlPullParserException e) { …… } catch (IOException e) { …… } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; } return result; } } |
首先通过parser拿到关联的AttributeSet,接着通过while循环,直到解析到类型为
XmlPullParser.START_TAG为止,由于我们的TAG类型肯定不是TAG_MERGE,所以接着走到else部分代码,这里先调用createViewFromTag创建tag对应的view, 也就是Layout文件中的Top Parent View,接着调用rInflate递归创建其全部childviews
先看createViewFromTag的实现:
//LayoutInflater.java View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); }
Context viewContext; if (parent != null && inheritContext) { viewContext = parent.getContext(); } else { viewContext = mContext; }
// Apply a theme wrapper, if requested. final TypedArray ta = viewContext.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId != 0) { viewContext = new ContextThemeWrapper(viewContext, themeResId); } ta.recycle();
if (name.equals(TAG_1995)) { // Let's party like it's 1995! return new BlinkLayout(viewContext, attrs); }
if (DEBUG) System.out.println("******** Creating view: " + name);
try { View view; 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); }
if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = viewContext; try { if (-1 == name.indexOf('.')) { view =onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; } catch (InflateException e) { …… } catch (ClassNotFoundException e) { …… } catch (Exception e) { …… } } |
由于这里name本身就对应类的class path,所以equals(“view”)肯定为false,在默认情况下,
mFactory2,mFactory以及mPrivateFactory都是未设置的,类名肯定是包含”.”的,接着调用
createView(name, null, attrs):
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { Constructor extends View> constructor = sConstructorMap.get(name); Class extends View> clazz = null;
try { if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class);
if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, attrs); } } constructor = clazz.getConstructor(mConstructorSignature); sConstructorMap.put(name, constructor); } else { …… }
Object[] args = mConstructorArgs; args[1] = attrs;
constructor.setAccessible(true); final View view = constructor.newInstance(args); if (view instanceof ViewStub) { // Use the same context when inflating ViewStub later. final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } return view;
} …… } |
sConstructorMap是缓存map,如果已经load过的class会被缓存在其中,后续就可直接通过name从map中拿到对应的class数据,由于咱们是第一次创建,这里constructor肯定为空,接着调用loadclass加载name对应的class,然后保存到sConstructorMap中
最后调用view对应的构造函数并传入attrs,就这样,view被创建成功了,由于在构造的时候传入了AttributeSet对象,所以,View必须在构造时通过attrs读取并保存对应的属性数据以完成View的初始化。
createViewFromTag介绍完后,在inflate内部,接着rinflate函数被调用用以递归创建当前元素的所有child views:
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, boolean finishInflate, boolean inheritContext) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type;
while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) { continue; }
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) { …… } else if (TAG_TAG.equals(name)) { …… } else if (TAG_INCLUDE.equals(name)) { …… } else if (TAG_MERGE.equals(name)) { …… } else { final View view = createViewFromTag(parent, name, attrs, inheritContext); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs, true, true); viewGroup.addView(view, params); } }
if (finishInflate) parent.onFinishInflate(); } |
看while循环的条件,只有两种情况while循环才会退出或者不进入:
1) Xml标签类型为XmlPullParser.END_TAG并且parser.getDepth() <=depth
2) Xml标签类型为XmlPullParser.END_DOCUMENT
再加上循环内部:
if (type != XmlPullParser.START_TAG) { continue; } |
代码的存在,也就是说,循环只会处理XML START_TAG标签类型
整个逻辑总结如下:
1) 根据当前元素的START_TAG标签来触发调用createViewFromTag创建对应view,接着将这个View作为parent view参数在rinflate被调用时传入,这时通过rinflate的while函数的判断条件,就会存在两种情况
2) 第一种是,这个元素没有子元素了,那parser.next获取的必定是当前元素的END_TAG,并且由于深度相同,即parser.getDepth() == depth,while循环不会进入,表明元素已经创建完成,接着调用if(finishInflate) parent.onFinishInflate(),然后结束本次rinflate调用
3) 第二种是,这个元素存在子元素,rinflate的while循环会遍历其下一层的所有子元素,每个子元素的处理流程回到(1)步骤,子元素通过rinflate递归调用结束后,再将创建的子元素view通过viewGroup.addView(view,params)添加到当前元素对应的viewgroup中,
最后parser.next碰到当前元素的END_TAG标签,并且parser.getDepth() == depth,当前元素全部子元素创建结束,接着调用if (finishInflate) parent.onFinishInflate(),然后结束本次rinflate调用
感觉递归在处理这种内部层层嵌套的数据结构解析,真的非常好用
通过上头还可以看出,View.onFinishInflate这个回调,是在View被构造结束并且完成对其child view的创建添加后才被调用的,也就是说,咱们在这个回调函数里,能够读取View的默认参数和child views,仅此而已.