一、簡介
setContentView我們在Activity中經(jīng)常見到,它的作用就是把我們的布局文件放在Activity中顯示,下面我們根據(jù)源碼分析setContentView是如何做到的
二、源碼分析
1.兩種setContentView
注意Activity的setContentView和AppCompatActivity的setContentView是有一些區(qū)別的,所以我們要分析兩鐘setContentView,下面先分析Activity的
2.Activity的setContentView
(1).從Activity的setContentView這個方法開始
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看到第一句getWindow().setContentView(layoutResID),這個getWindow是獲取當(dāng)前Activity的Window,在Android中Window的實現(xiàn)類是phoneWindow,所以我們要看phoneWindow的setContentView
順便提一下Activity的window的創(chuàng)建時機是在Activity的attach方法:
(2).繼續(xù)跟蹤到phoneWindow的setContentView
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(); //?這句關(guān)鍵流程
} 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);//?這句關(guān)鍵流程
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true; //?這個flag記一下
}
上面代碼中我標(biāo)記了三處重點,我們下面繼續(xù)分析這三個重點都干了什么,先分析第一個installDecor()
(2.1).分析phoneWindow的setContentView的第一個關(guān)鍵點installDecor()
installDecor主要是我用紅框標(biāo)記出來的是重點,我們先分析generateDecor(-1)這個方法:
(2.1.1).分析installDecor()方法中的generateDecor(-1)方法
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, this);
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());//?重點
}
創(chuàng)建了一個DecorView并且返回之后賦值給了mDecor,我們先看一下這個DecorVIew是什么:
很明顯是一個FrameLayout,這下我們知道了創(chuàng)建了一個FrameLayout類型的DecorView然后賦值給了mDecor變量,下面繼續(xù)分析installDecor的第二個重點:generateLayout(mDecor)
(2.1.2).分析installDecor的第二個重點:generateLayout(mDecor)
protected ViewGroup generateLayout(DecorView decor) {
...
else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //?重點下面的方法
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
return contentParent;
}
//?DecorView的onResourcesLoaded方法
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);//?重點主線流程
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views. ?重點主線流程
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
這個方法的作用就是,通過我們設(shè)置的style或者requestWindowFuture等來選出一個系統(tǒng)自帶的布局文件,默認(rèn)的是R.layout.screen_simple,選出布局文件后,通過調(diào)用mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);方法inflate出來后,add到DecorView上,我們詳細(xì)看一下R.layout.screen_simple這個布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
就是這個布局文件,被inflate到DecorView上,然后通過
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
這一句獲取到這個是content的FrameLayout,然后返回了這個contentParent
到這里installDecor這個方法分析完了,總結(jié)一下,installDecor方法主要就是創(chuàng)建DecorView,然后把選出的布局文件add到DecorView上,然后再通過findViewbyId找到類型是FrameLayout的content賦值給contentParent 返回,其實還有一步是DecorView和phoneWindow結(jié)合,這里不細(xì)說,以后有FrameWorker源碼解析再說。
(2.2).繼續(xù)分析phoneWindow的setContentView的第二個關(guān)鍵流程重點
mLayoutInflater.inflate(layoutResID, mContentParent);
這句很明顯,layoutResID是我們的activity_main.layout這種自己寫的布局文件,把它inflate到mContentParent中,通過圖片讓大家有一個更清晰的感官:
(2.3).繼續(xù)分析phoneWindow的setContentView的第三個關(guān)鍵流程重點
mContentParentExplicitlySet = true;
這個flag的作用首先我們先看一段代碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestWindowFeature(Window.FEATURE_NO_TITLE);
}
}
這段代碼運行會報錯:
requestFeature() must be called before adding content
為什么會報這個錯誤呢,從代碼上來找:
public final boolean requestWindowFeature(int featureId) {
return getWindow().requestFeature(featureId);
}
//我們已經(jīng)知道,getWindow其實獲取的是PhoneWindow所以調(diào)用的是PhoneWindow的requestFeature
@Override
public boolean requestFeature(int featureId) {
if (mContentParentExplicitlySet) { //?這就是報錯的根源
throw new AndroidRuntimeException("requestFeature() must be called before adding content");
}
final int features = getFeatures();
final int newFeatures = features | (1 << featureId);
if ((newFeatures & (1 << FEATURE_CUSTOM_TITLE)) != 0 &&
(newFeatures & ~CUSTOM_TITLE_COMPATIBLE_FEATURES) != 0) {
// Another feature is enabled and the user is trying to enable the custom title feature
// or custom title feature is enabled and the user is trying to enable another feature
throw new AndroidRuntimeException(
"You cannot combine custom titles with other title features");
}
if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) {
return false; // Ignore. No title dominates.
}
if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) {
// Remove the action bar feature if we have no title. No title dominates.
removeFeature(FEATURE_ACTION_BAR);
}
if (featureId == FEATURE_INDETERMINATE_PROGRESS &&
getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
throw new AndroidRuntimeException("You cannot use indeterminate progress on a watch.");
}
return super.requestFeature(featureId);
}
看到了報錯的根源,其實就是mContentParentExplicitlySet這個flag,在setContentView執(zhí)行完就設(shè)置成了true,所以調(diào)用requestWindowFeature(Window.FEATURE_NO_TITLE);方法必須在setContentView之前,否則就會拋出異常,最后從設(shè)計的角度分析,為什么要設(shè)計這么一個flag呢,或者說為什么非要在setContentView之前執(zhí)行requestFeature,因為在setContentView中需要通過設(shè)置的這些requestWindowFeature的flag去選擇一個布局文件然后add到DecorView上,如果在setContentView后面設(shè)置就起不到作用,所以有了這個設(shè)計。
3.AppCompatActivity的setContentView
(1).AppCompatActivity的setContentView源碼
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
(1.1).getDelegate()
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
實際上實現(xiàn)類是AppCompatDelegate,getDelegate().setContentView(layoutResID);的setContentView實際上是AppCompatDelegate的setContentView方法
(2).AppCompatDelegate的setContentView方法
@Override
public void setContentView(int resId) {
ensureSubDecor(); //?重點主線
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); //?重點主線
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);//?重點主線
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
(2.1).分析ensureSubDecor()方法
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor(); // ?重點主線流程
// If a title was set before we installed the decor, propagate it now
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
if (mDecorContentParent != null) {
mDecorContentParent.setWindowTitle(title);
} else if (peekSupportActionBar() != null) {
peekSupportActionBar().setWindowTitle(title);
} else if (mTitleView != null) {
mTitleView.setText(title);
}
}
applyFixedSizeWindow();
onSubDecorInstalled(mSubDecor);
mSubDecorInstalled = true;//?這個flag參數(shù)
// Invalidate if the panel menu hasn't been created before this.
// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
// A pending invalidation will typically be resolved before the posted message
// would run normally in order to satisfy instance state restoration.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!mIsDestroyed && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}
主要的第一句:
mSubDecor = createSubDecor();
mSubDecor是一個ViewGroup類型的對象,下面我們分析createSubDecor()
(2.1.1).ensureSubDecor()方法中的createSubDecor()方法
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
//?這個錯誤是不是曾經(jīng)見到過,如果用的Theme不是AppCompatTheme的就會報錯
if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
a.recycle();
throw new IllegalStateException(
"You need to use a Theme.AppCompat theme (or descendant) with this activity.");
}
......
// Now let's make sure that the Window has installed its decor by retrieving it
ensureWindow();
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
if (!mWindowNoTitle) {
......
} else {
......
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
......
}
......
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
// There might be Views already added to the Window's content view so we need to
// migrate them to our content view
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
// Change our content FrameLayout to use the android.R.id.content id.
// Useful for fragments.
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
// The decorContent may have a foreground drawable set (windowContentOverlay).
// Remove this as we handle it ourselves
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
......
return subDecor;
}
上面顯示出來的基本上都很重要,我們一句一句分析:
(2.1.1.1).ensureWindow();
private void ensureWindow() {
// We lazily fetch the Window for Activities, to allow DayNight to apply in
// attachBaseContext
if (mWindow == null && mHost instanceof Activity) {
attachToWindow(((Activity) mHost).getWindow());
}
if (mWindow == null) {
throw new IllegalStateException("We have not been given a Window");
}
}
首先我們要明確AppCompatActivity是繼承自Activity的,所以window也是在attach方法中創(chuàng)建的,在AppCompatDelegateImpl中也維護了一個Window類型的變量是mWindow,就是通過這個ensureWindow方法經(jīng)過檢查后賦值過來的。說白了ensureWindow方法就是把AppCompatActivity中的Window對象賦值到AppCompatDelegateImpl對象中,當(dāng)然對window設(shè)置的callBack啥的也換成AppCompatDelegateImpl中的。
(2.1.1.2).mWindow.getDecorView()方法
Window的實現(xiàn)類,所以我們要看PhoneWindow的getDecorView()方法:
@Override
public final @NonNull View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
可以看到是調(diào)用了installDecor,和Activity有了相同的部分,我們簡單回憶一下installDecor干了啥,首先創(chuàng)建了DecorView,然后通過解析我們style的設(shè)置選出合適的系統(tǒng)自帶布局文件,把它add到DecorView上,并且返回了一個id是com.android.internal.R.id.content的FrameLayout(將來我們要把我們的activity_mai的layout文件add到這個content上面)。
(2.1.1.3).后面要分析的這一堆代碼就是Activity和AppCompatActivity的setContentView的主要區(qū)別
....
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
....
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
// There might be Views already added to the Window's content view so we need to
// migrate them to our content view
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
// Change our content FrameLayout to use the android.R.id.content id.
// Useful for fragments.
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
......
}
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
......
return subDecor;
}
通過我們設(shè)置的style選出一個布局文件,這一步好像在installDecor中已經(jīng)做過了,這樣好像重復(fù)了,為什么有這樣的一個重復(fù)?有這樣一個重復(fù)是為了不影響原來的代碼的同時,把一部分對style處理的邏輯轉(zhuǎn)移到AppCompatDelegateImpl中,例如對windowTitle的隱藏與顯示,這里可看出來設(shè)計師的設(shè)計,通過下面的學(xué)習(xí)慢慢體會,先看一下這個布局文件:
<androidx.appcompat.widget.FitWindowsLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/action_bar_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
<androidx.appcompat.widget.ViewStubCompat
android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/abc_action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include layout="@layout/abc_screen_content_include" />
</androidx.appcompat.widget.FitWindowsLinearLayout>
abc_screen_content_include的布局文件:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.appcompat.widget.ContentFrameLayout
android:id="@id/action_bar_activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</merge>
把這個布局文件inflate成view賦值給subDecor,subDecor是一個ViewGroup:
下面重點來了:
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
這一句是獲取subDecor中的id是 action_bar_activity_content的ContentFrameLayout類型的View賦值給contentView
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
windowContentView是獲取installDecor中選擇的那個布局的id是android.R.id.content的那個FrameLayout
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
如果windowContentView中有子View,那就全部轉(zhuǎn)移到contentView上
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
然后把windowContentView的id設(shè)置成NO_ID,把contentView的id設(shè)置成android.R.id.content,這就是想把AppCompatDelegateImple中選的這個系統(tǒng)自帶布局文件的content替換掉之前的installDecor方法中布局文件的content,以后我們的activity_main的layout布局文件就加載在替換之后的content上
mWindow.setContentView(subDecor);
這句調(diào)用的是phoneWindow的setContentView方法
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params); //?把contentView添加到mContentParent上
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
我們看一下mContentParent是什么:
mContentParent = generateLayout(mDecor);
是我們?nèi)∠鸌D的那個之前的content,也就是說把AppCompatDelegateImpl的選擇的系統(tǒng)自帶布局文件(subDecor),添加到之前的content中,最后返回subDecor注意這個subDecor是系統(tǒng)自帶布局inflate出來的,接下來我們通過一張圖加深理解:
(2.1.2).ensureSubDecor()方法中的一個boolean值mSubDecorInstalled
這個mSubDecorInstalled和Activity中的mContentParentExplicitlySet一樣,作用也是防止在setContentView之后調(diào)用
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
,我們發(fā)現(xiàn)和Activity的不一樣了,我們思考一下,為什么調(diào)用supportRequestWindowFeature而不是調(diào)用requestWindowFeature?
Activity中處理這些style是在PhoneWIndow的installDecor中,而AppCompatActivity在處理一些style時是在AppCompatDelegateImpl中,requestWindowFeature是影響的installDecor,supportRequestWindowFeature是影響的AppCompatDelegateImpl,拿window的Title來舉例,看上面放過的兩張圖,Activity的title是要在installDecor方法中決定的顯示與隱藏的,而AppCompatActivity的title是放在AppCompatDelegateImpl中決定顯示與隱藏的,我們用AppCompatActivity肯定是要在AppCompatDelegateImpl進行一些操作,而不是對installDecor中進行操作。
下面看一下supportRequestWindowFeature調(diào)用邏輯:
public boolean supportRequestWindowFeature(int featureId) {
return getDelegate().requestWindowFeature(featureId);
}
@Override
public boolean requestWindowFeature(int featureId) {
featureId = sanitizeWindowFeatureId(featureId);
if (mWindowNoTitle && featureId == FEATURE_SUPPORT_ACTION_BAR) {
return false; // Ignore. No title dominates.
}
if (mHasActionBar && featureId == Window.FEATURE_NO_TITLE) {
// Remove the action bar feature if we have no title. No title dominates.
mHasActionBar = false;
}
switch (featureId) {
case FEATURE_SUPPORT_ACTION_BAR:
throwFeatureRequestIfSubDecorInstalled();
mHasActionBar = true;
return true;
case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
throwFeatureRequestIfSubDecorInstalled();
mOverlayActionBar = true;
return true;
case FEATURE_ACTION_MODE_OVERLAY:
throwFeatureRequestIfSubDecorInstalled();
mOverlayActionMode = true;
return true;
case Window.FEATURE_PROGRESS:
throwFeatureRequestIfSubDecorInstalled();
mFeatureProgress = true;
return true;
case Window.FEATURE_INDETERMINATE_PROGRESS:
throwFeatureRequestIfSubDecorInstalled();
mFeatureIndeterminateProgress = true;
return true;
case Window.FEATURE_NO_TITLE:
throwFeatureRequestIfSubDecorInstalled();
mWindowNoTitle = true;
return true;
}
return mWindow.requestFeature(featureId);
}
//?mSubDecorInstalled 這參數(shù)眼熟吧
private void throwFeatureRequestIfSubDecorInstalled() {
if (mSubDecorInstalled) {
throw new AndroidRuntimeException(
"Window feature must be requested before adding content");
}
}
(2.2).AppCompatDelegateImpl中的setContentView(View v)剩下的一起說
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); //?重點主線
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);//?重點主線
獲取contentview,注意這個contentView和Activity的contentView的區(qū)別,看上面那張圖,把所有的view清除掉,然后把我們的activity_main的layout這種我們自己的布局加載上去
3.分析LayoutInflater.from(mContext).inflate(resId, contentParent);方法
(1).很經(jīng)典的一道面試題,inflate三個參數(shù)都有什么作用
或者說這三種寫法有什么區(qū)別:
我們先看源碼,看完之后總結(jié):
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) + ")");
}
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource); //?把布局文件用xml解析器解析
try {
return inflate(parser, root, attachToRoot); //調(diào)用inflate重載的方法
} finally {
parser.close();
}
}
繼續(xù)往下看inflate的重載方法,這個方法中就有這道面試題的答案:
public View inflate(XmlPullParser parser, @Nullable 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 result = root; //?重點
try {
advanceToRootNode(parser); //?確保下面的代碼是首先解析的根布局標(biāo)簽
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
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 { //?重點 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) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// 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);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// 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) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(inflaterContext, attrs)
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
上面的代碼太多我把關(guān)于這道題的主要代碼邏輯拿出來:
(1.1).advanceToRootNode()這個方法就是確保第一個解析的是布局的根標(biāo)簽
private void advanceToRootNode(XmlPullParser parser)
throws InflateException, IOException, XmlPullParserException {
// 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!");
}
}
(1.2).else的邏輯單獨分析
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) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// 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);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// 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;
}
}
下面逐句分析:
(1.2.1)
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
這一句就是通過name,來反射創(chuàng)建view,下面會詳細(xì)分析如何創(chuàng)建的view,這個temp就是布局文件的根view
(1.2.2)
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// 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方法的第二個參數(shù)root不是null,那就執(zhí)行這段代碼:
params = root.generateLayoutParams(attrs);
這段代碼的意思是:我們把新視圖的根布局參數(shù)傳遞給root。讓root進行轉(zhuǎn)換成適合它自身布局的布局參數(shù)(因為不同的布局有不同的特性,例如LinearLayout和FrameLayout,我們要把布局文件的根temp這個View放到root中,就要讓temp原來的布局參數(shù)轉(zhuǎn)換成適合root這個ViewGroup布局的參數(shù)),如果inflate第三個參數(shù)attachToRoot是false就把布局文件的根view設(shè)置成轉(zhuǎn)換的params
(1.2.3)
rInflateChildren(parser, temp, attrs, true);
之后就繼續(xù)解析布局文件,通過反射創(chuàng)建View,具體如何解析和創(chuàng)建之后詳細(xì)分析
(1.2.4)
if (root != null && attachToRoot) {
root.addView(temp, params);
}
如果root不是null并且attachToRoot是true,把布局文件根生成的temp這個view添加到root上面,注意這個params還是上面轉(zhuǎn)換的那個。
(1.2.5)
if (root == null || !attachToRoot) {
result = temp;
}
如果root是null或者attachToRoot是false,方法最后的返回值就是這個布局文件生成的View
(1.2.6)
到這里我們就知道了:
1.當(dāng)root不為null時,attachTORoot是true的時候就直接把我們布局生成的View添加到root(這個root是inflate方法參數(shù)的第二個參數(shù))上面,并且方法最后的返回結(jié)果是root,如果attachTORoot是false,直接返回我們布局文件生成的View注意這個生成View的layoutParams已經(jīng)set了,所以可以說:
LayoutInflater.from(this).inflate(R.layout.activity_main,root,true);
等價于
View view = LayoutInflater.from(this).inflate(R.layout.activity_main,root,false);
root.addView(view);
2.當(dāng)root為null時,直接返回我們布局文件生成的view,注意這個生成的View沒有l(wèi)ayoutParams
,而且一旦root為null,后面的attachToRoot這個參數(shù)就失效了。
(2).分析inflate方法如何解析和創(chuàng)建View的
(2.1).在inflate布局文件的根標(biāo)簽時要注意merge標(biāo)簽
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);
}
當(dāng)一個布局文件的根標(biāo)簽是merge時,如果root是null或者attachToRoot是false就報錯,說明布局文件跟標(biāo)簽是merge時不能直接生成一個View,必須依附于其他View上。
(2.2).解析不是根標(biāo)簽的布局
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();
}
}
(2.2.1).解析不是根布局時,注意對merge、include標(biāo)簽的處理
對merge標(biāo)簽時直接報錯,因為merge標(biāo)簽只能用于根標(biāo)簽,對include標(biāo)簽的處理,判斷如果include是跟標(biāo)簽則報錯,因為include標(biāo)簽不能用作根標(biāo)簽。
(2.2.2).其他的view標(biāo)簽會走創(chuàng)建view,然后通過父布局生成對應(yīng)的布局參數(shù)LayoutParams,然后添加在副布局上
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);
}
這一段中,createViewFromTag這個方法最重要,后面都是生成LayoutParams和添加到父布局上的邏輯,最后我們分析createViewFromTag這個方法
(2.2.3).createViewFromTag創(chuàng)建view,注意這個Activity和AppCompatActivity有差別
a.先分析Activity的createViewFromTag創(chuàng)建view的流程
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
@UnsupportedAppUsage
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();
}
try {
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) { //?重點
view = onCreateView(context, parent, name, attrs); //?重點
} else {
view = createView(context, name, null, attrs); //?重點
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
我們分析重點:
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
if (-1 == name.indexOf(‘.’))這個判斷是標(biāo)簽是不是自定義View,帶"."的是自定義View,如果不是自定義View,通過onCreateView創(chuàng)建View,如果是自定義View,通過createView創(chuàng)建View
所以我們比較一下為啥不是自定義View要通過onCreateView創(chuàng)建而是自定義VIew的用createView創(chuàng)建
先說一個知識點,LayoutInflater的實現(xiàn)類是PhoneLayoutInflater,onCreateView方法也被重寫了:
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);
}
最后還調(diào)用了super:
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
可以看到onCreateView最后還是調(diào)用了createVIew,所以onCreateView和createView主要的差別就是這個prefix的前綴,不是自定義View需要有前綴,想想LinearLayout這個類的全類名是"android.widget.LinearLayout",這下知道了這些SDK自帶的不是自定義View標(biāo)簽都會在這里補全全類名,最后看一下createView
(2.2.4).createView方法
static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class}; //View的兩個參數(shù)
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Objects.requireNonNull(viewContext);
Objects.requireNonNull(name);
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {//?主要就是通過反射去創(chuàng)建view
// Class not found in the cache, see if it's real, and try to add it
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);//通過反射獲取兩個參數(shù)的構(gòu)造方法,兩個參數(shù)分別是context與AttributeSet(XML的參數(shù)集合)
constructor.setAccessible(true);
sConstructorMap.put(name, constructor); //用map緩存起來
} else { //constructor不為null說明map里面有直接用map里面的
// If we have a filter, apply it to cached constructor
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 = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
}
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
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]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
} catch (NoSuchMethodException e) {
final InflateException ie = new InflateException(
getParserStateDescription(viewContext, attrs)
+ ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassCastException e) {
// If loaded class is not a View subclass
final InflateException ie = new InflateException(
getParserStateDescription(viewContext, attrs)
+ ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(viewContext, attrs) + ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
這么一大堆代碼其實就是通過反射去創(chuàng)建View,注意里面的注釋
b.再分析AppCompatActivity的createViewFromTag創(chuàng)建view的流程
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
@UnsupportedAppUsage
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();
}
try {
View view = tryCreateView(parent, name, context, attrs);//?重點
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
這個tryCreatView方法如果嘗試創(chuàng)建View失敗之后才輪到Activity的創(chuàng)建方式,我們看一下tryCreateView方法:
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
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);
}
return view;
}
發(fā)現(xiàn)有幾個變量不認(rèn)識,mFactory2和mFactory還有mPrivateFactory,我們的AppCompatActivity就是使用的mFactory2,看AppCompatActivity的onCreate方法:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory(); // ?
delegate.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
我們繼續(xù)跟蹤這個方法:
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
//Factory2是一個接口
public interface Factory2 extends Factory {
@Nullable
View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs);
}
//LayoutInflater實現(xiàn)了這個接口
class AppCompatDelegateImpl extends AppCompatDelegate
implements MenuBuilder.Callback, LayoutInflater.Factory2
這下看到了,AppCompatActivity的oncreate方法中調(diào)用installViewFactory方法,獲取到layoutInflater對象,AppCompatDelegateImpl實現(xiàn)了Factory2的接口,
LayoutInflaterCompat.setFactory2(layoutInflater, this);
public static void setFactory2(
@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
inflater.setFactory2(factory); // ?
if (Build.VERSION.SDK_INT < 21) {
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);
}
}
}
//LayoutInflater類中的方法
public void setFactory2(Factory2 factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
可以看到吧Factory2傳進了infalter中,所以Inflater的對象中Factory2不是null了。
Factory2不是null了,在執(zhí)行創(chuàng)建createViewFromTag方法的tryCreateView時:
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
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);
}
return view;
}
mFactory2不是null了,就會執(zhí)行 view = mFactory2.onCreateView(parent, name, context, attrs);
我們知道,這個mFactory2其實就是AppCompatDelegateImpl的實例對象,這個設(shè)計挺巧妙和上面ensureDecor有點像,添加了一些邏輯,使對view的創(chuàng)建邏輯轉(zhuǎn)移到了AppCompatDelegateImpl中,所以我們下面看AppCompatDelegateImpl的onCreateView方法:
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return createView(parent, name, context, attrs);
}
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
String viewInflaterClassName =
a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
if ((viewInflaterClassName == null)
|| AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
// Either default class name or set explicitly to null. In both cases
// create the base inflater (no reflection)
mAppCompatViewInflater = new AppCompatViewInflater();
} else {
try {
Class<?> viewInflaterClass = Class.forName(viewInflaterClassName);
mAppCompatViewInflater =
(AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
.newInstance();
} catch (Throwable t) {
Log.i(TAG, "Failed to instantiate custom view inflater "
+ viewInflaterClassName + ". Falling back to default.", t);
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 */
);
}
可以看到創(chuàng)建了一個mAppCompatViewInflater ,最后調(diào)用了mAppCompatViewInflater的createView方法:
final View createView(View parent, final String name, @NonNull Context context,
@NonNull 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;
case "ToggleButton":
view = createToggleButton(context, attrs);
verifyNotNull(view, name);
break;
default:
// The fallback that allows extending class to take over view inflation
// for other tags. Note that we don't check that the result is not-null.
// That allows the custom inflater path to fall back on the default one
// later in this method.
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) {
// If we have created a view, check its android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
可以看到有一個switch,如果View是case中的一種,那就把布局參數(shù)傳進去重新創(chuàng)建一個AppCompat的View,如果不是case中的一種,進default,這個createView方法直接返回null,
protected View createView(Context context, String name, AttributeSet attrs) {
return null;
}
然后走下面這句:
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);
}
private View createViewFromTag(Context context, String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
try {
mConstructorArgs[0] = context;
mConstructorArgs[1] = attrs;
if (-1 == name.indexOf('.')) {
for (int i = 0; i < sClassPrefixList.length; i++) {
final View view = createViewByPrefix(context, name, sClassPrefixList[i]);
if (view != null) {
return view;
}
}
return null;
} else {
return createViewByPrefix(context, name, null);
}
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
// try
return null;
} finally {
// Don't retain references on context.
mConstructorArgs[0] = null;
mConstructorArgs[1] = null;
}
}
private View createViewByPrefix(Context context, String name, String prefix)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
Class<? extends View> clazz = Class.forName(
prefix != null ? (prefix + name) : name,
false,
context.getClassLoader()).asSubclass(View.class);
constructor = clazz.getConstructor(sConstructorSignature);
sConstructorMap.put(name, constructor);
}
constructor.setAccessible(true);
return constructor.newInstance(mConstructorArgs);
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
// try
return null;
}
}
這個方法和Activity的邏輯一致了
總結(jié)一下:AppCompatActivity通過LayoutInfater解析創(chuàng)建View時,會通過setFactory2攔截原有Activity的邏輯,去執(zhí)行AppCompatDelegateImpl的邏輯,在View解析和創(chuàng)建的時候,會先檢查,如果是AppCompat新設(shè)計的View就是case里的那一堆,就把這個View轉(zhuǎn)換成AppCompat新設(shè)計的View,如果不是還是按照之前的邏輯來。
三、總結(jié)
1.setContentView的總結(jié)
setContentView總的來說就是創(chuàng)建DecorView,DecorView是一個FrameLayout,然后根據(jù)style選擇系統(tǒng)自帶的布局文件,(例如有沒有title,這里說一下這個布局文件根布局是linearLayout,如果有title則是有一個viewStub和兩個FrameLayout:title的和content的,如果沒有title則是一個viewstub和一個content的FrameLayout),添加到DecorView上,最后再把我們自己的的activity_mian這種layout添加到content這個FrameLyout上。
注意Activity和AppCompatActivity有一些區(qū)別,但總體上的邏輯是不變的。文章來源:http://www.zghlxwxcb.cn/news/detail-798947.html
2.inflate的總結(jié)
總結(jié)了inflate三個參數(shù)的作用,總結(jié)了解析到include、merge等標(biāo)簽時的注意點,總結(jié)了如何解析xml文件和如何創(chuàng)建View的重要流程。文章來源地址http://www.zghlxwxcb.cn/news/detail-798947.html
到了這里,關(guān)于Android學(xué)習(xí)之路(12) setContentView詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!