流程圖
LayoutInflater用來把一個xml文件實例化成對應的View對象。
我們從Activity的onCreate方法開始:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//注釋1處
setContentView(R.layout.activity_layout_inflate)
}
AppCompatActivity的setContentView方法
@Override
public void setContentView(@LayoutRes int layoutResID) {
//注釋1處,調用代理對象的setContentView方法
getDelegate().setContentView(layoutResID);
}
注釋1處,調用AppCompatDelegateImpl的setContentView方法。
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
//注釋1處
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
注釋1處,進入正題,LayoutInflater的from方法內部就是使用context.getSystemService來獲取LayoutInflater實例的。
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實例以后,就可以調用它的inflate方法來加載布局文件了。
這是Activity的布局文件activity_layout_inflate.xml
。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/clRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.LayoutInflateActivity">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="200dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello_world"
android:textAllCaps="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/webview" />
</android.support.constraint.ConstraintLayout>
</RelativeLayout>
接下來看看LayoutInflater的inflate方法究竟是怎么把布局文件轉換成view對象的。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
//調用3個參數的重載函數
return inflate(resource, root, root != null);
}
/**
* 根據指定的xml布局文件中生成一個view層級。有錯誤拋出InflateException。
* @param resource xml布局文件ID
* @param root 可選的view,如果attachToRoot是true,使用root作為生成的view層級的父布局;
* 如果attachToRoot是false,為返回的view層級的根view提供一系列的LayoutParams值。
* @param attachToRoot 用來標志生成的view層級是否應該被添加到root中去。如果是false,
* root只是用來為返回的view層級的根view創建正確的LayoutParams。
* @return 返回生成的view層級的根view。如果root不為null并且attachToRoot 是true,就返回root;
* 否則返回view層級的根view。
*/
public View inflate(int resource, ViewGroup root, boolean attachToRoot){
final Resources res = getContext().getResources();
//...
//注釋1處,根據資源id生成xml解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
//注釋2處
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
注釋1處,調用Resources的getLayout方法
@NonNull
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
這個方法返回了一個XmlResourceParser對象,我們可以通過這個對象讀取布局文件中的XML數據。
注釋2處,調用LayoutInflater的inflate(XmlPullParser parser,ViewGroup root, boolean attachToRoot)方法開始讀取解析數據。
public View inflate(XmlPullParser parser,ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
//...
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// 查找根節點
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//...
//注釋0處,布局文件中第一個標簽名字
final String name = parser.getName();
//處理merge標簽,標簽的情況暫時不去看
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 {
//注釋1處,temp是xml文件中的根view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
//注釋2處
//創建與root匹配的布局參數
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//注釋3處
//將布局參數設置給temp
temp.setLayoutParams(params);
}
}
//注釋4處,填充temp下面的所有子view
rInflateChildren(parser, temp, attrs, true);
// 注釋5處,如果條件滿足,應該把temp添加到root中去。
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 注釋6處,判斷是返回root還是返回result。
if (root == null || !attachToRoot) {
result = temp;
}
}
}
//...
return result;
}
}
注釋0處,布局文件中第一個標簽名字,在這個例子中就是布局中的RelativeLayout
。
final String name = parser.getName();
在注釋1處,調用createViewFromTag方法獲取xml文件中的根view。
private View createViewFromTag(View parent, String name, Context context,
AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
/**
* 從一個標簽名和提供的屬性集合創建一個view。
*
* 這個方法的可見性是default,所以BridgeInflater可以覆蓋這個方法。
*
* @param parent 父view,用來生成layout params
* @param name XML標簽名,用來定義一個view。
* @param context 用來生成view的上下文。
* @param attrs 布局文件中view的屬性集
* @param ignoreThemeAttr 用來標志要生成的view是否忽略 {@code android:theme}中定義的屬性。
*/
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// 如果允許并指定了一個主題屬性集,則使用。
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();
}
//...
try {
View view;
if (mFactory2 != null) {//注釋1處
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {//注釋2處
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
//注釋3處
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {//注釋4處
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//注釋5處
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
//返回創建的View
return view;
} catch (InflateException e) {
throw e;
}
}
注釋1處,調用了mFactory2
的onCreateView
方法。并且發現mFactory2
是一個AppCompatDelegateImpl對象。mFactory2
是何時賦值的我們暫且不管。
AppCompatDelegateImpl的onCreateView方法。
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return this.createView(parent, name, context, attrs);
}
AppCompatDelegateImpl的createView方法。
public View createView(View parent, String name, Context context, AttributeSet attrs) {
//內部調用了AppCompatViewInflater的createView方法
return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
}
AppCompatViewInflater的createView方法,我們從中可以看到一些端倪。
final View createView(View parent, final String name, Context context, AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case "Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case "SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
default:
//默認是返回null
view = createView(context, name, attrs);
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
//我們在布局中有時候會設置`android:onClick="click"` 這樣一個屬性,難道是在這里處理的?
checkOnClickListener(view, attrs);
}
return view;
}
最后發現返回的view為null。
我們回到createViewFromTag
方法的注釋2處、注釋3處、返回的都是null。
注釋5處,調用LayoutInflater的onCreateView方法。
protected View onCreateView(View parent, String name, AttributeSet attrs) throws ClassNotFoundException {
return onCreateView(name, attrs);
}
注意,這里調用的是PhoneLayoutInflater
類里面的onCreateView(String name, AttributeSet attrs)
方法。
@Override
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
//注釋1處
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
sClassPrefixList對象
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
注釋1處,調用LayoutInflater
的createView(String name, String prefix, AttributeSet attrs)
方法。傳入的name是RelativeLayout
,prefix是android.widget
。
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//根據要創建的View的名稱獲取構造函數
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
if (constructor == null) {
//如果構造函數為null,就通過類加載器加載類對象并獲取構造函數,然后將構造函數緩存在sConstructorMap中
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);
constructor.setAccessible(true);
//加入緩存
sConstructorMap.put(name, constructor);
} else {
// 通過debug,發現mFilter為null
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
//創建View
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]));
}
mConstructorArgs[0] = lastContext;
//返回View
return view;
} catch (Exception e) {
//拋出各種異常
}
}
方法內部邏輯就是根據控件名稱和前綴prefix加載對應的類對象,然后通過反射創建View對象并返回。到這里我們布局文件中最外層的RelativeLayout
就創建好了。
LayoutInflater的inflate(XmlPullParser parser,ViewGroup root, boolean attachToRoot)方法的注釋1處
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
在這個例子中,temp就是我們創建好的RelativeLayout對象。
然后我們回到LayoutInflater的inflate(XmlPullParser parser,ViewGroup root, boolean attachToRoot)方法的注釋4處,填充temp下面的所有子view。
//注釋4處,填充temp下面的所有子view
rInflateChildren(parser, temp, attrs, true);
LayoutInflater的rInflateChildren方法
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
LayoutInflater的rInflate方法
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
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)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} 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");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
//注釋1處
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//注釋2處遞歸創建子View。
rInflateChildren(parser, view, attrs, true);
//注釋3處,添加創建的View
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
//結束填充
parent.onFinishInflate();
}
}
注釋1處:創建View。
注釋2處:遞歸創建創建子View。
注釋3處:添加創建的View。
創建完所有的View以后,我們回到LayoutInflater的inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
方法注釋2處,創建temp的布局參數。
ViewGroup.LayoutParams params = null;
if (root != null) {
//注釋2處
//創建與root匹配的布局參數
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//注釋3處
//將布局參數設置給temp
temp.setLayoutParams(params);
}
}
在這個例子中,root就是 android:id/content
,是一個FrameLayout。
FrameLayout的generateLayoutParams方法。
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new FrameLayout.LayoutParams(getContext(), attrs);
}
FrameLayout.LayoutParams,是ViewGroup.MarginLayoutParams的子類。
public static class LayoutParams extends MarginLayoutParams {
//...
}
注意:這里想說一點,我們平時用的LinearLayout.LayoutParams
、RelativeLayout.LayoutParams
、FrameLayout.LayoutParams
都是ViewGroup.MarginLayoutParams
的子類。
創建完所有的View以后,我們回到LayoutInflater的inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
方法注釋5處,如果條件滿足,應該把temp添加到root中去。
if (root != null && attachToRoot) {
root.addView(temp, params);
}
到這里,LayoutInflater的整個inflate流程結束。
參考鏈接