以常规的Activity启动开始,我们追一下详细的调用栈。
android sdk版本是30。
Activity#setContent方法中的布局如何生成View对象的:
--> ActivityThread#handleLaunchActivity
--> ActivityThread#performLaunchActivity
--> Instrumentation#callActivityOnCreate
--> Activity#performCreate(Bundle icicle)
--> Activity#performCreate(Bundle icicle, PersistableBundle persistentState)
--> Activity#onCreate(Bundle savedInstanceState)
--> AppCompatActivity#setContentView(@LayoutRes int layoutResID)
--> AppCompatDelegateImpl#setContentView(int resId)
------① AppCompatDelegateImpl#ensureSubDecor():初始化好DecorView。
------> PhoneWindow#getDecorView()
------> PhoneWindow#installDecor()
------> PhoneWindow#generateLayout(DecorView decor)
------> DecorView#onResourcesLoaded(LayoutInflater inflater, int layoutResource):返回一个View。
------> LayoutInflate#inflate(@LayoutRes int resource, @Nullable ViewGroup root)
----------先调用LayoutInflate#tryInflatePrecompiled方法,尝试获取View。默认mUseCompiledView为false,直接返回null。
----------上面方法获取不到时,接着获取XmlResourceParser,通过LayoutInflate#inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法获取View。
------② 调用LayoutInflater.from(mContext).inflate(resId, contentParent):将Activity#setContentView方法中设置的资源文件生成对应的View并添加到contentParent中。
LayoutInflater#inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法中:
①AttributeSet attrs = Xml.asAttributeSet(parser):从XmlPullParser中获取到AttributeSet
②通过advanceToRootNode(parser)方法,找到START_TAG后面的第一个元素;通过parser.getName()获取到name——"LinearLayout"
③通过LayoutInflater#createViewFromTag(View parent, String name, Context context, AttributeSet attrs)获取View
——> LayoutInflater#createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr)
----调用LayoutInflater#tryCreateView方法:尝试创建View。本次返回null。
--------先使用mFactory2#onCreateView方法,调用到AppCompatViewInflater#createView方法中,这里做一些hook,将TextView、ImageView、Button、EditText、CheckBox等基础组件,替换成AppCompat系列组件。不过没有处理LinearLayout,所以返回view为null。
--------如果view为null,接着调用mPrivateFactory.onCreateView方法,这里会先调用FragmentActivity#dispatchFragmentsOnCreateView方法,看是否是"fragment"标签;如果不是,就调用Activity#onCreateView方法进行传递。这里也返回为null。
----如果tryCreateView创建View失败,则接着调用LayoutInflater#onCreateView(@NonNull Context viewContext, @Nullable View parent, @NonNull String name, @Nullable AttributeSet attrs)方法;
------>调用LayoutInflater#onCreateView(View parent, String name, AttributeSet attrs);
------>调用LayoutInflater#onCreateView(String name, AttributeSet attrs)方法;设置prefix为"android.view."。
------>调用LayoutInflater#createView(String name, String prefix, AttributeSet attrs)
------>调用LayoutInflater#createView(Context viewContext, String name, String prefix, AttributeSet attrs):
----------通过prefix和name获取Constructor对象。
----------构建Object[] args参数,依次传入上下文、attrs,然后通过反射生成LinearLayout的对象,调用的是LinearLayout(Context context, AttributeSet attrs)构造方法。
----------返回生成的View对象。
④如果root != null,就调用root.generateLayoutParams方法生成LayoutParams;如果attachToRoot为false,就将LayoutParams设置给③生成的View。
⑤调用LayoutInflater#rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate):生成child对象
--> LayoutInflater#rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate)
---- 开启while循环:((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT
----------通过parser.getName()获取name,针对"requestFocus"、"tag"、"include"、"merge"和else情况做不同的处理。
----------else情况下:
--------------通过LayoutInflater#createViewFromTag(View parent, String name, Context context, AttributeSet attrs)获取View。
--------------通过parent#generateLayoutParams方法,从xml中读取parent对应的数据,生成对应的LayoutParams数据,包括基础的layout_width和layout_height,一个给个ViewGroup自定义的LayoutParams数据。
--------------递归调用LayoutInflater#rInflateChildren,对View的child创建。
--------------通过ViewGroup#addView(View child, LayoutParams params)方法,将child添加进parent。
⑥如果root != null && attachToRoot,通过ViewGroup#addView(View child, LayoutParams params)方法,将child和④生成的LayoutParams数据,添加进root。
⑦如果root == null || !attachToRoot,将temp赋值给result。
结论:
在从xml文件变成View对象,并添加到View树的过程中,必然会调用parent#generateLayoutParams
方法。
在ViewGroup#generateLayoutParams
方法中,会读取layout_width
和layout_height
基础属性。
由于各个控件都是继承自ViewGroup
的,他们一般会继承ViewGroup.LayoutParams
,并据此重写自己的generateLayoutParams
方法,在generateLayoutParams方法中一般都会从child的属性中读取自己关注的属性,所以此时写在child中的xml
属性会被读取,从而生效。