基于 Android API 26 Platform 源碼
寫作背景
Android 開發(fā)框架中,使用 Xml 文件描述 Ui 頁面,通過setContentView(resId)
或者LayoutInflater.inflate(resId,……)
的方式把 Xml 文件描述的頁面轉(zhuǎn)換成 Java 對(duì)象。Xml 文件加上 AndroidStudio 提供的預(yù)覽功能,使得 Android 開發(fā)過程中頁面和業(yè)務(wù)邏輯可以并行開發(fā),極大地提高了開發(fā)效率。
但是大部分 Android 工程師對(duì) xml 文件如何轉(zhuǎn)換成 Java 不是十分了解,本文將帶大家一起探究 View 從 xml 文件到 Java 對(duì)象的轉(zhuǎn)換過程
xml 轉(zhuǎn)成成 Java 對(duì)象有幾種方式?
我們先羅列一下 xml 轉(zhuǎn)換成 Java 對(duì)象的方式
1. 在 Activity中調(diào)用 setContentView(resId)
2. LayoutInflater.from(context).inflate(resId,……)
跟蹤一下 Activity.setContentView(resId)
我們一般在項(xiàng)目使用的 Activity 可能是
1. android.support.v7.app.AppCompatActivity
2. android.support.v4.app.FragmentActivity
3. android.app.Activity
4. 其他 Activity
所有的 Activity 都是 android.app.Activity 的子類。
但是!每個(gè)繼承 android.app.Activity 的子類 setContentView(resId) 實(shí)現(xiàn)方式都被重載了。我們這里先看最基礎(chǔ)的 android.app.Activity
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
查看一下 getWindow()
源碼
public Window getWindow() {
return mWindow;
}
全局搜索 mWindow
對(duì)象賦值的地方找到以下代碼
mWindow = new PhoneWindow(this, window, activityConfigCallback);
這里 PhoneWindow
的源碼在 sdk 里面是隱藏的,我們?nèi)?androidxref ->PhoneWindow.java 查看 PhoneWindow.setContentView(layoutResID)
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
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);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
當(dāng)我們沒有設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫的時(shí)候會(huì)執(zhí)行
mLayoutInflater.inflate(layoutResID, mContentParent);
在 PhoneWindow 的構(gòu)造函數(shù)中我們找到了 mLayoutInflater
對(duì)象賦值語句
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
所以我們得出一個(gè)結(jié)論 Activity.setContentView(resId) 最終還是使用LayoutInflater.from(context).inflate(resId, ……)
再回頭看下 android.support.v7.app.AppCompatActivity
和android.support.v4.app.FragmentActivity
我們發(fā)現(xiàn) android.support.v4.app.FragmentActivity
沒有重載 android.app.Activity.setContentView(resId)
但是 android.support.v7.app.AppCompatActivity
重載了
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
再跟蹤一下源代碼我們發(fā)現(xiàn)最終會(huì)調(diào)用到 android.support.v7.app.AppCompatDelegateImplV9.setContentView(resId)
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
這里我們又發(fā)現(xiàn)了 LayoutInflater
的身影。
這里我們可以總結(jié)一下 xml 轉(zhuǎn)成成 Java 對(duì)象是通過 LayoutInflater 的 inflate() 方法來完成的
LayoutInflater 對(duì)象如何實(shí)例化
看下一下 LayoutInflater
的源碼第一行
public abstract class LayoutInflater {……}
LayoutInflater
是一個(gè)抽象類, 抽象類是不能實(shí)例化的
先想一下 LayoutInflater 對(duì)象獲取的方式
1. 在 Activity 中通過 getLayoutInflater() 獲取
2. 通過 LayoutInflater.from(context) 獲取
3.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) 獲取
看 Activity 的 getLayoutInflater()
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
這里我們就可以看出 Activity 通過 getLayoutInflater() 獲取的是 PhoneWindow 的 mLayoutInflater (如果忘記了可以往上翻一下,或者去參考資料的鏈接里找找源碼)
再看一下 LayoutInflater.from(context)
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;
}
此時(shí),我們必須請(qǐng)出柯南君幫我們宣布
真相只有一個(gè)!最終都是通過服務(wù)獲取 LayoutInflater 實(shí)例對(duì)象
下一步,源碼追蹤context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
這里先說明一個(gè)前提,context 的實(shí)現(xiàn)類是 ContextImpl
如果對(duì)該前提有疑問請(qǐng)移步 Android Context完全解析,你所不知道的Context的各種細(xì)節(jié)
所以我們直接查看 ContextImpl.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
繼續(xù)跟蹤 SystemServiceRegistry
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
這時(shí)候我們?cè)?SystemServiceRegistry 類停留一下,發(fā)現(xiàn)這里似乎只注冊(cè)各種系統(tǒng)服務(wù)的地方。我們找到了 Context.LAYOUT_INFLATER_SERVICE 注冊(cè)代碼。
static {
……
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
……
}
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
然后我們終于找到 LayoutInflater 的實(shí)現(xiàn)類是 PhoneLayoutInflater
此時(shí)我們可以休息一下,喝口水,上個(gè)衛(wèi)生間,進(jìn)入下個(gè)階段
LayoutInflater 讀取 xml 文件并創(chuàng)建 View 對(duì)象
LayoutInflater.inflate()
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
再去源碼查看一下,發(fā)現(xiàn)兩個(gè)方法其實(shí)只有一個(gè)方法是核心,另一個(gè)只是做了一下封裝,讓我們少傳入一個(gè)參數(shù)。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
所以我們重點(diǎn)看一下 inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
的源碼
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();
}
}
我們看到首先通過 res
對(duì)象把 resId 指向的 xml 文件轉(zhuǎn)換為 XmlResourceParser
然后執(zhí)行 inflate(parser, root, attachToRoot)
方法,該方法比較長(zhǎng),這里只貼出核心步驟。
public View inflate(XmlPullParser parser, @Nullable 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 {
……
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
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
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 against its context.
rInflateChildren(parser, temp, attrs, 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;
}
}
} ……
省略異常處理部分
……
return result;
}
}
以上步驟還是很長(zhǎng),我們將拆分幾部分分析。
第一部分
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);
}
如果 xml 根標(biāo)簽是 merge
,則 root 不能為空, attachToRoot 必須是 true。
然后執(zhí)行 rInflate(parser, root, inflaterContext, attrs, false)
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 {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
上面這個(gè)方式我們需要重點(diǎn)記一下
1. 遍歷該節(jié)點(diǎn)的子節(jié)點(diǎn)
2. 子節(jié)點(diǎn)有 "requestFocus"、"tag"、""、"include"
3. 子節(jié)點(diǎn)不能是 "merge"
4. 子節(jié)點(diǎn)的其他情況,則是各種 View 的標(biāo)簽
5. View 標(biāo)簽和 "include" 標(biāo)簽會(huì)創(chuàng)建 View 對(duì)象
6. 遍歷結(jié)束以后執(zhí)行 parent.onFinishInflate()
如果子節(jié)點(diǎn)是 include
則執(zhí)行 parseInclude()
,parseInclude()
的源碼和 inflate(parser, root, attachToRoot)
類似,都是讀取xml對(duì)應(yīng)的文件,轉(zhuǎn)換成 XmlResourceParser 然后遍歷里的標(biāo)簽
經(jīng)過層層調(diào)用,我們可以找到最終創(chuàng)建 View 的代碼在
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
第一部分代碼,我們的到的結(jié)論是, createViewFromTag(parent, name, context, attrs)
負(fù)責(zé)創(chuàng)建 View 對(duì)象
第二部分
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
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 against its context.
rInflateChildren(parser, temp, attrs, 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;
}
因?yàn)檫@里排除了merge
標(biāo)簽,這里的根標(biāo)簽肯定是一個(gè) View,所以調(diào)用了 createViewFromTag(root, name, inflaterContext, attrs)
方法創(chuàng)建 View 。再次印證了第一部分得出的結(jié)論 createViewFromTag(parent, name, context, attrs)
負(fù)責(zé)創(chuàng)建 View 對(duì)象
然后看下后面的代碼我們就明白 inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
三個(gè)參數(shù)的關(guān)系了
1. root 不為 null 的時(shí)候,才會(huì)讀取 xml 跟布局的 params 屬性。
(這里可以解釋為啥我們有時(shí)候用 LayoutInflater 加載的 xml 根標(biāo)簽的屬性總是無效 )
2. attachToRoot 為 True ,返回的是 root 對(duì)象。否則返回的是 xml 創(chuàng)建的根標(biāo)簽指定的 View
LayoutInflater.createViewFromTag()創(chuàng)建 View 對(duì)象
通過上面的判斷我們終于找到了最最核心的方法 createViewFromTag()
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
有包裹了一層,并且把 ignoreThemeAttr 設(shè)置為 false,表示這里會(huì)收到 Theme 的影響。我們?cè)?createViewFromTag(parent, name, context, attrs, false)
中找到了創(chuàng)建 View 的代碼
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 {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
這里又出現(xiàn)了 mFactory2
、mFactory
、mPrivateFactory
三個(gè)對(duì)象,似乎都是可以創(chuàng)建 View 。 對(duì)于android.app.Activity
來說,這三個(gè)對(duì)象為 null 或者空實(shí)現(xiàn)(下一節(jié)會(huì)講這個(gè)) 所以我們直接看
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
這里需要說明一下,如果 name
屬性里面含有 .
表示這是一個(gè)自定義 View,系統(tǒng)自帶 View 我們可以省略類的路徑,而自定義 View 則不能省略。
對(duì)于自定義 View 的創(chuàng)建,這里省略了大部分代碼
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
……
try {
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;
return view;
} ……
}
僅僅看到 constructor.newInstance(args)
我們已經(jīng)明白這里使用了 反射創(chuàng)建 View 對(duì)象
而對(duì)于 Android 內(nèi)置的各種 View 我們?cè)?LayoutInflater 的實(shí)現(xiàn)類 PhoneLayoutInflater 中找到了重載
/**
* @hide
*/
public class PhoneLayoutInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
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);
}
}
再看下 LayoutInflater 中的代碼
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
我們可以看到, 對(duì)于系統(tǒng)內(nèi)置的 View,會(huì)依次在 View 的標(biāo)簽前面加上"android.widget."、"android.webkit.","android.app." 、"android.view." 然后通過反射的方法創(chuàng)建 View。
最后補(bǔ)充一點(diǎn),Activity 和 mFactory2
、mFactory
、mPrivateFactory
的關(guān)系
我們前面說過 對(duì)于android.app.Activity
來說,mFactory2
、mFactory
、mPrivateFactory
這三個(gè)對(duì)象為 null或者空實(shí)現(xiàn)
我們回到 Activity 的源碼中
final void attach(……) {
……
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
……
}
這里省略了很多代碼,但是我們看到在創(chuàng)建完 PhoneWindow
以后,緊接著調(diào)用了mWindow.getLayoutInflater().setPrivateFactory(this)
這里看到 Activity 實(shí)現(xiàn)了 LayoutInflater.Factory2
接口,并且通過mWindow.getLayoutInflater().setPrivateFactory(this)
,把 Activity 設(shè)置為 LayoutInflater 的 mPrivateFactory
成員變量。
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (!"fragment".equals(name)) {
return onCreateView(name, context, attrs);
}
return mFragments.onCreateView(parent, name, context, attrs);
}
@Nullable
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
這里可以看到 Activity 通過自己的實(shí)現(xiàn)的 LayoutInflater.Factory2
接口,增加了對(duì)fragment
標(biāo)簽的處理。
順便說一下 AppCompat 組件的安裝
android.support.v7
包中提供了一系列 AppCompatXXX
替代Android自帶的 XXX
。例如 android.view.View
被替代為android.support.v7.widget
第一步 AppCompatActivity.onCreate(@Nullable Bundle savedInstanceState)
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
……
super.onCreate(savedInstanceState);
}
這里可以看到 delegate.installViewFactory()
,該方法的實(shí)現(xiàn)類在 android.support.v7.app.AppCompatDelegateImplV9
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
這里看到 LayoutInflaterCompat.setFactory2(layoutInflater, this)
,跟蹤下去
public static void setFactory2(
@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
IMPL.setFactory2(inflater, factory);
}
public void setFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
inflater.setFactory2(factory);
final LayoutInflater.Factory f = inflater.getFactory();
if (f instanceof LayoutInflater.Factory2) {
// The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
// We will now try and force set the merged factory to mFactory2
forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
} else {
// Else, we will force set the original wrapped Factory2
forceSetFactory2(inflater, factory);
}
}
這里看到 inflater.setFactory2(factory)
,表示已經(jīng)安裝 AppCompatDelegateImplV9
到 LayoutInflater.mFactory2
然后看 AppCompatDelegateImplV9.(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs)
@Override
public View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
mAppCompatViewInflater = new AppCompatViewInflater();
}
boolean inheritContext = false;
if (IS_PRE_LOLLIPOP) {
inheritContext = (attrs instanceof XmlPullParser)
// If we have a XmlPullParser, we can detect where we are in the layout
? ((XmlPullParser) attrs).getDepth() > 1
// Otherwise we have to use the old heuristic
: shouldInheritContext((ViewParent) parent);
}
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
true, /* Read read app:theme as a fallback at all times for legacy reasons */
VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
);
}
最后到 AppCompatViewInflater.createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext)
public final View createView(……) {
……
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
view = new AppCompatTextView(context, attrs);
break;
……
}
if (view == null && originalContext != context) {
……
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check its android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
到此 ,Xml 文件到 View 對(duì)象的轉(zhuǎn)換過程全部結(jié)束