我們?nèi)粘T陂_發(fā)中在Activity設(shè)置布局的時(shí)候都會(huì)用到setContentView方法,那setContentView到底是如何實(shí)現(xiàn)添加布局的呢,帶著這個(gè)疑問本篇文章來詳細(xì)查看源碼分析setContentView加載布局的流程,首先我們先看Activity的setContentView方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
在這里 getWindow()獲取的是mWindow這個(gè)變量就是Window,mWindow實(shí)例化就是PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
我們查看PhoneWindow中的setContentView方法
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
第一次會(huì)走進(jìn)這里
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 {
最終獲取的mContentParent加載顯示布局
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
.....省略
我們看下mContentParent到底是怎么實(shí)例化的,進(jìn)入installDecor方法
mForceDecorInstall = false;
if (mDecor == null) {
創(chuàng)建一個(gè)DecorView
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
創(chuàng)建mContentParent
mContentParent = generateLayout(mDecor);
...設(shè)置一些屬性
}
所以從這里可以看出Activity的布局加載是先創(chuàng)建一個(gè)PhoneWindow的窗口布局,然后在PhoneWindow內(nèi)又創(chuàng)建一個(gè)窗口布局DecorView就是個(gè)FrameLayout,嵌套了兩層,我們再看generateLayout方法
...獲取一些屬性
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
獲取系統(tǒng)布局
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
獲取到layoutResource,填充DecorView
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
找一個(gè)叫做android.id.content的FrameLayout系統(tǒng)布局
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
....
找到返回
return contentParent;
這個(gè)方法主要是加載系統(tǒng)一些資源做了一系列的判斷,先獲取到layoutResource去填充DecorView,然后獲取系統(tǒng)布局中的FrameLayout的id,這個(gè)id就是從screen_simple布局中獲取的
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
獲取到root
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 {
系統(tǒng)布局添加到DecorView中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
<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>
從源碼可以看出DecorView又添加了一個(gè)系統(tǒng)的布局,所以現(xiàn)在已經(jīng)嵌套第三層了,但是在系統(tǒng)布局中還有個(gè)FrameLayout,這樣就嵌套了四層,也就是說最終加載我們自己的布局也就是加到系統(tǒng)布局中的FrameLayout,如果有頭部布局就會(huì)加載系統(tǒng)的頭部布局,沒有則不加。下面畫的一張圖加以說明:

接下來我們再來看下AppCompateActivity的setContentView源碼
@Override
public void setContentView(@LayoutRes int layoutResID) {
initViewTreeOwners();
getDelegate().setContentView(layoutResID);
}
public abstract void setContentView(@LayoutRes int resId);
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
跟進(jìn)去看下發(fā)現(xiàn)是一個(gè)抽象方法,抽象方法一定會(huì)有實(shí)現(xiàn)類,我們找到它的實(shí)現(xiàn)類,點(diǎn)擊進(jìn)入AppCompatDelegate.create方法,找到對應(yīng)的實(shí)現(xiàn)類
@NonNull
public static AppCompatDelegate create(@NonNull Activity activity,
@Nullable AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, callback);
}
我們進(jìn)入AppCompatDelegateImpl中找到setContentView方法看看具體怎么實(shí)現(xiàn)的
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
寫好的布局添加到contentParent中
contentParent.addView(v);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
發(fā)現(xiàn)和Activity的寫法類似也是獲取android.R.id.content,我們進(jìn)入ensureSubDecor方法看看到底怎么處理的
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
獲取到mSubDecor 是一個(gè)ViewGroup
mSubDecor = createSubDecor();
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;
onCreateOptionsMenu
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!mDestroyed && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}
看看mSubDecor 是怎樣創(chuàng)建的,進(jìn)入createSubDecor方法
private ViewGroup createSubDecor() {
....設(shè)置一些屬性
這個(gè)方法是獲取PhoneWindow
ensureWindow();
這是獲取PhoneWindow內(nèi)創(chuàng)建的DecorView
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
if (!mWindowNoTitle) {
if (mIsFloating) {
// If we're floating, inflate the dialog title decor
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
// Floating windows can never have an action bar, reset the flags
mHasActionBar = mOverlayActionBar = false;
} else if (mHasActionBar) {
/**
* This needs some explanation. As we can not use the android:theme attribute
* pre-L, we emulate it by manually creating a LayoutInflater using a
* ContextThemeWrapper pointing to actionBarTheme.
*/
TypedValue outValue = new TypedValue();
mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
Context themedContext;
if (outValue.resourceId != 0) {
themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
} else {
themedContext = mContext;
}
// Now inflate the view using the themed context and set it as the content view
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
mDecorContentParent = (DecorContentParent) subDecor
.findViewById(R.id.decor_content_parent);
mDecorContentParent.setWindowCallback(getWindowCallback());
/**
* Propagate features to DecorContentParent
*/
if (mOverlayActionBar) {
mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (mFeatureProgress) {
mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
}
if (mFeatureIndeterminateProgress) {
mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
}
}
} else {
if (mOverlayActionMode) {
默認(rèn)會(huì)走到這里
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_screen_simple_overlay_action_mode, null);
} else {
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
}
}
if (subDecor == null) {
throw new IllegalArgumentException(
"AppCompat does not support the current theme features: { "
+ "windowActionBar: " + mHasActionBar
+ ", windowActionBarOverlay: "+ mOverlayActionBar
+ ", android:windowIsFloating: " + mIsFloating
+ ", windowActionModeOverlay: " + mOverlayActionMode
+ ", windowNoTitle: " + mWindowNoTitle
+ " }");
}
.... 一些兼容的判斷
if (mDecorContentParent == null) {
mTitleView = (TextView) subDecor.findViewById(R.id.title);
}
// Make the decor optionally fit system windows, like the window's decor
ViewUtils.makeOptionalFitsSystemWindows(subDecor);
獲取系統(tǒng)布局中的ContentFrameLayout
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
獲取PhoneWindow中的系統(tǒng)布局FrameLayout
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
如果有子布局
while (windowContentView.getChildCount() > 0) {
獲取PhoneWindow中的子布局
final View child = windowContentView.getChildAt(0);
刪除PhoneWindow中的根布局
windowContentView.removeViewAt(0);
子布局重新添加到contentView中
contentView.addView(child);
}
// Change our content FrameLayout to use the android.R.id.content id.
清除PhoneWindow的id
windowContentView.setId(View.NO_ID);
重新設(shè)置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);
}
}
重新添加到DecorView
mWindow.setContentView(subDecor);
contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
@Override
public void onAttachedFromWindow() {}
@Override
public void onDetachedFromWindow() {
dismissPopups();
}
});
返回重新設(shè)置的一個(gè)id為android.R.id.content的ViewGroup
return subDecor;
}
從這個(gè)方法可以看出跟Activity中的原理邏輯是一樣的,只不過處理方式不太一樣,我們先來看下PhoneWindow是怎樣獲取的,進(jìn)入ensureWindow方法
private void ensureWindow() {
if (mWindow == null && mHost instanceof Activity) {
這個(gè)就很明了了其實(shí)就是獲取到Activity中的實(shí)例化的PhoneWindow對象
attachToWindow(((Activity) mHost).getWindow());
}
if (mWindow == null) {
throw new IllegalStateException("We have not been given a Window");
}
}
接下來我們看看subDecor是怎樣處理的,我們先看布局文件到底是咋搞的,查看abc_screen_simple_overlay_action_mode布局
<androidx.appcompat.widget.FitWindowsFrameLayout
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:fitsSystemWindows="true">
<include layout="@layout/abc_screen_content_include" />
<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" />
</androidx.appcompat.widget.FitWindowsFrameLayout>
<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>
這個(gè)地方有點(diǎn)饒,我們詳細(xì)看下源碼發(fā)現(xiàn)id為action_bar_activity_content是一個(gè)ContentFrameLayout,所以根據(jù)源碼可以看出來在下面獲取的第一步contentView是ContentFrameLayout,然后第二步mWindow獲取的android.R.id.content是PhoneWindow中的系統(tǒng)布局id,第三步先判斷有沒有子布局,如果有的話則刪除自己的跟布局和id,替換成ContentFrameLayout根布局設(shè)置id為android.R.id.content,第四步修改好的subDecor布局添加到DecorView中,最后setContentView中我們自己設(shè)置的布局添加到android.R.id.content中
源碼分析完了,現(xiàn)在我們再看Activity和AppCompateActivity的區(qū)別在哪里
通過AppCompateActivity的源碼分析,我們在AppCompatDelegateImpl中發(fā)現(xiàn)實(shí)現(xiàn)了LayoutInflater.Factory2接口
public interface Factory2 extends Factory {
@Nullable
View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs);
}
既然實(shí)現(xiàn)接口一定會(huì)有事件,果然發(fā)現(xiàn)在AppCompatDelegateImpl的事件處理,LayoutInflater源碼分析本篇文章就不寫了,可自行查看,這里直接講一些,后續(xù)文章中會(huì)有補(bǔ)充。接者往下看
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
設(shè)置一個(gè)Factory事件
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");
}
}
}
有個(gè)Factory對象就一定會(huì)走onCreateView方法,我們查看LayoutInflater中的具體實(shí)現(xiàn)方法
private static class FactoryMerger implements Factory2 {
private final Factory mF1, mF2;
private final Factory2 mF12, mF22;
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
mF1 = f1;
mF2 = f2;
mF12 = f12;
mF22 = f22;
}
@Nullable
public View onCreateView(@NonNull String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
View v = mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF2.onCreateView(name, context, attrs);
}
@Nullable
public View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs) {
View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
: mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
: mF2.onCreateView(name, context, attrs);
}
}
所以我們現(xiàn)在來查看AppCompatDelegateImpl中實(shí)現(xiàn)的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) {
關(guān)注此方法
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, );
}
進(jìn)入AppCompatViewInflater中的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;
...省略
View view = null;
如果是TextView,則轉(zhuǎn)換成AppCompatTextView,以此類推
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:
view = createView(context, name, attrs);
}
......省略
if (view == null && originalContext != context) {
這個(gè)方法是自定義控件,構(gòu)造方法通過反射進(jìn)行創(chuàng)建的
view = createViewFromTag(context, name, attrs);
}
return view;
}
@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
從這里我們就看清楚了,其實(shí)它就是把我們在布局中設(shè)置的控件轉(zhuǎn)換成帶有 AppCompat兼容控件
最后總結(jié)一下:AppCompateActivity創(chuàng)建View的時(shí)候會(huì)被攔截,不會(huì)走系統(tǒng)的LayoutInflater的創(chuàng)建,就會(huì)替換一些系統(tǒng)兼容的View