分析源碼鸳劳,搞清楚Activity的setContent()背后的邏輯
Activity 的setContent()流程
以前的Activity测僵,都是直接繼承Activity.java,而現(xiàn)在的Activity則基本都是繼承AppCompatActivity.java钮孵,自然setContent()是不一樣的,那么先捋一捋舊的
Activity.java
先從Activity.java開始看起。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看到款违,getWindow()方法獲取了一個(gè)自己Activity持有的Window對象的引用,再調(diào)用這個(gè)對象的setContent()群凶,之后做一個(gè)初始化流程插爹。Window類是一個(gè)抽象類:
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
看注釋,這大概是一個(gè)Activity所呈現(xiàn)界面的頂層Window请梢。他的實(shí)現(xiàn)類只有一個(gè)赠尾,是PhoneWindow。那么就來看看這個(gè)PhoneWindow類的setContentView()方法實(shí)現(xiàn):
@Override
public void setContentView(int layoutResID) {
//首先這里有一個(gè)ContentParent毅弧,如果為空則做一個(gè)初始化
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
//根據(jù)是否需要?jiǎng)赢媮碜鲆恍┦? if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
/*
不需要?jiǎng)赢嬈蓿苯娱_始加載布局,這里是將layoutResID布局加載到了mContentParent上
而layoutResID是我們交給setContent()的那個(gè)布局id
因此我們的Activity最終顯示的頁面就是加載 到了mContent上
*/
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
再看看mContentParent的定義:
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;
可以看到够坐,這個(gè)mContentParent其實(shí)就是一個(gè)ViewGroup
所以在setContent()中主要做了兩件事:
- 初始化一個(gè)Window持有的ContentParent(即ViewGroup)對象
- 將布局文件加載到ContentParent上
那么現(xiàn)在看看這個(gè)所謂的初始化過程做了什么寸宵,即installDecor():
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {//第一步,發(fā)現(xiàn)mDecor沒有初始化
//生成一個(gè)mDecor對象元咙,并對其初始化
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
//讓mDecor獲取一個(gè)當(dāng)前window的引用
mDecor.setWindow(this);
}
if (mContentParent == null) {//第二步梯影,發(fā)現(xiàn)mContentParent沒有初始化
//用前面的mDecor生成一個(gè)mContentParent對象并初始化
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
//在mDecor中找一下是否有一個(gè)DecorContentParent
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
//有?對這個(gè)做初始化
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
for (int i = 0; i < FEATURE_MAX; i++) {
if ((localFeatures & (1 << i)) != 0) {
mDecorContentParent.initFeature(i);
}
}
mDecorContentParent.setUiOptions(mUiOptions);
//…………
} else {//沒有庶香?那么從這里開始
//獲取一個(gè)作為title的view并初始化
mTitleView = findViewById(R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
final View titleContainer = findViewById(R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
mContentParent.setForeground(null);
} else {
mTitleView.setText(mTitle);
}
}
}
//對這個(gè)mDecor設(shè)置背景(回調(diào))
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
//之后就是一些無關(guān)緊要的東西了
}
再看看這個(gè)mDecor是何方神圣:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
** @hide */
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
//…………
}
原來mDecor就是一個(gè)FrameLayout了甲棍。
那么這個(gè)初始化過程就分為了兩步:
- 初始化mDecor(一個(gè)FrameLayout)
- 借助mDecor初始化mContentParent
再來分別看看兩者是如何初始化的,顯示mDecor:
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, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
先是用想辦法和獲取一個(gè)context赶掖,然后再調(diào)用新的構(gòu)造器七扰,這里的featureId傳進(jìn)來的是-1颈走。然后看構(gòu)造器:
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
super(context);
mFeatureId = featureId;
mShowInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.linear_out_slow_in);
mHideInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.fast_out_linear_in);
mBarEnterExitDuration = context.getResources().getInteger(
R.integer.dock_enter_exit_duration);
mForceWindowDrawsStatusBarBackground = context.getResources().getBoolean(
R.bool.config_forceWindowDrawsStatusBarBackground)
&& context.getApplicationInfo().targetSdkVersion >= N;
mSemiTransparentStatusBarColor = context.getResources().getColor(
R.color.system_bar_background_semi_transparent, null /* theme */);
updateAvailableWidth();
//前面不是有一個(gè)在發(fā)現(xiàn)mDecorView不為Null時(shí)要賦予一個(gè)當(dāng)前window引用嗎司致?這里就是在初始化完成后再做的
setWindow(window);
updateLogTag(params);
mResizeShadowSize = context.getResources().getDimensionPixelSize(
R.dimen.resize_shadow_size);
initResizingPaints();
}
至此一個(gè)DecorView就初始化完成了捞奕,他實(shí)際上是一個(gè)FrameLayout。接下來看看這個(gè)mContentParent是如何通過DecorView來生成的:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//這里先拿到一些屬性
TypedArray a = getWindowStyle();
//…………
//這里開始先是對每一種屬性做判斷了弄抬,比如是否懸浮孔飒?是否無標(biāo)題逾柿?等等
//具體方法和我們寫自定義View時(shí)是一樣的,這里省略了
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
//………………
//這里開始取出部分app相關(guān)的信息,比如targetsdk
final Context context = getContext();
final int targetSdk = context.getApplicationInfo().targetSdkVersion;
//………………
WindowManager.LayoutParams params = getAttributes();
//這里是和高端設(shè)備相關(guān)的設(shè)置
// Non-floating windows on high end devices must put up decor beneath the system bars and
// therefore must know about visibility changes of those.
if (!mIsFloating && ActivityManager.isHighEndGfx()) {
if (!targetPreL && a.getBoolean(
R.styleable.Window_windowDrawsSystemBarBackgrounds,
false)) {
setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
}
if (mDecor.mForceWindowDrawsStatusBarBackground) {
params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
}
}
//………………
if (params.windowAnimations == 0) {
params.windowAnimations = a.getResourceId(
R.styleable.Window_windowAnimationStyle, 0);
}
// The rest are only done if this window is not embedded; otherwise,
// the values are inherited from our container.
if (getContainer() == null) {
if (mBackgroundDrawable == null) {
if (mBackgroundResource == 0) {
mBackgroundResource = a.getResourceId(
R.styleable.Window_windowBackground, 0);
}
if (mFrameResource == 0) {
mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
}
mBackgroundFallbackResource = a.getResourceId(
R.styleable.Window_windowBackgroundFallback, 0);
if (false) {
System.out.println("Background: "
+ Integer.toHexString(mBackgroundResource) + " Frame: "
+ Integer.toHexString(mFrameResource));
}
}
if (mLoadElevation) {
mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
}
mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
}
// Inflate the window decor.
//這里開始汪疮,就來真的了
//這個(gè)int值代表了要加載的布局的id
int layoutResource;
//所需的屬性
int features = getLocalFeatures();
//然后,根據(jù)屬性不同的需求,獲取不同的布局文件id
// 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 {
// Embedded, so no decoration is needed.
//記住這個(gè)布局文件id
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
//標(biāo)識著這個(gè)decorview開始改變了
mDecor.startChanging();
//將剛才那個(gè)布局文件猾漫,加載到decor中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//通過findviewbyid()的方式獲取這個(gè)contentParent庸蔼,記住這個(gè)ID_ANDROID_CONTENT
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//………………
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
//初始化背景和標(biāo)題等等一些屬性
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
//為decorview設(shè)置背景
mDecor.setWindowBackground(background);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
mDecor.setElevation(mElevation);
mDecor.setClipToOutline(mClipToOutline);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
//標(biāo)識著改變結(jié)束
mDecor.finishChanging();
//最后解总,返回這個(gè)contentParent
return contentParent;
}
總結(jié)一下,就是給這個(gè)framelayout————DecorView設(shè)置了一種布局掏膏,然后通過findviewbyid的方式獲取一個(gè)contentparent的劳翰。那么這兩者有什么關(guān)系呢?觀察到前面提到了兩個(gè)id馒疹,聯(lián)系就在這里佳簸!所以接下來看看具體設(shè)置布局的邏輯。
首先看看加載布局:
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mStackId = getStackId();
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();
}
看到layoutInflater就知道了颖变,這里果然是加載layoutResource指向的那個(gè)布局生均,這里加載后為一個(gè)叫做root的View,然后通過調(diào)用addView()方法————我們知道DecorView本身是一個(gè)FrameLayout————將root加載到自己這個(gè)FrameLayout中腥刹。
接下來看看layoutResource所引用的布局R.layout.screen_simple马胧,即screen_simple.xml的內(nèi)容:
<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>
嗯,一個(gè)LinearLayout衔峰,包含了一個(gè)ViewStub占位和一個(gè)FrameLayout佩脊。做一個(gè)猜測蛙粘,這就是我們Activity最普通的初始界面,即一個(gè)狀態(tài)欄+一個(gè)主界面威彰。然后發(fā)現(xiàn)下面那個(gè)FrameLayout的id是content出牧,再回到剛才方法中,通過findviewbyid初始化找到contentParent的時(shí)候用的id是哪個(gè)抱冷?
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
根據(jù)注釋崔列,我們知道了梢褐,這個(gè)id引用的view就是我們的主布局要加載的地方旺遮,也就是在剛才那個(gè)xml文件中的FrameLayout!
到此為止盈咳,一個(gè)installDecor()的過程基本完成了耿眉,來捋一捋。
首先要初始化一個(gè)叫做DecorView的FrameLayout鱼响,他是和當(dāng)前Window息息相關(guān)的鸣剪。我們知道一個(gè)Activity,他的顯示界面這個(gè)模塊是交給了Window管理丈积,而在Window中則是交給了DeocrView筐骇。這個(gè)DecorView會(huì)根據(jù)不同的需求(主題)來給自己填上一個(gè)不同的布局。然后在加載進(jìn)來的這個(gè)布局中江滨,有一個(gè)ViewGroup是專門用來顯示我們編寫的界面的铛纬,這個(gè)ViewGroup會(huì)通過findViewById()的形式在DecorView中找到,然后交給mContentParent唬滑,這樣我們要將自己寫的布局加載進(jìn)來的時(shí)候告唆,就是直接加載到mContentParent中就可以了。
回過頭來看看setContentView:
@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 {
//這里可以看到晶密,前面初始化結(jié)束后擒悬,果然是將我們自己寫的布局加載到了mContentParent中!
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
至此稻艰,Activity的setContent()流程就是走完了懂牧,大致知道了布局是怎么加載進(jìn)來的。接下來看看新的AppCompatActivity是如何加載布局的
AppCompatActivity
接下來再看看AppCompatActivity是如何加載布局的
先看AppCompatActivity.java的setContentView()方法:
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
這里通過getDelegate()方法獲取了一個(gè)對象的引用尊勿,再調(diào)用他的setContentView()方法归苍,相當(dāng)于做了一個(gè)代理。
那么現(xiàn)在問題拆分為兩步:
- 代理的對象是如何創(chuàng)建的
- 代理對象的setContentView()是如何執(zhí)行的
先看第一個(gè)問題:
/**
* @return The {@link AppCompatDelegate} being used by this Activity.
*/
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
這里用來做代理的运怖,是一個(gè)AppCompatDelegate對象拼弃,叫mDelegate,他是通過一個(gè)靜態(tài)方法create()創(chuàng)建的摇展,那么先看看這個(gè)類是什么:
<p>An {@link Activity} can only be linked with one {@link AppCompatDelegate} instance,
* therefore the instance returned from {@link #create(Activity, AppCompatCallback)} should be
* retained until the Activity is destroyed.</p>
再來看看他的create()方法:
/**
* Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code activity}.
*
* @param callback An optional callback for AppCompat specific events
*/
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return create(activity, activity.getWindow(), callback);
}
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
if (BuildCompat.isAtLeastN()) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (sdk >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV9(context, window, callback);
}
}
可以看到吻氧,這里最終是根據(jù)不同的sdk版本來創(chuàng)建不同的AppCompatDelegateImplxxx對象,分別點(diǎn)進(jìn)去看看后會(huì)發(fā)現(xiàn),最終都是到了AppCompatDelegateImplV9.java盯孙,然后:
class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
AppCompatDelegateImplV9(Context context, Window window, AppCompatCallback callback) {
super(context, window, callback);
}
所以最終是調(diào)用了父類的構(gòu)造器:
AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) {
mContext = context;
mWindow = window;
mAppCompatCallback = callback;
mOriginalWindowCallback = mWindow.getCallback();
if (mOriginalWindowCallback instanceof AppCompatWindowCallbackBase) {
throw new IllegalStateException(
"AppCompat has already installed itself into the Window");
}
mAppCompatWindowCallback = wrapWindowCallback(mOriginalWindowCallback);
// Now install the new callback
mWindow.setCallback(mAppCompatWindowCallback);
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
context, null, sWindowBackgroundStyleable);
final Drawable winBg = a.getDrawableIfKnown(0);
if (winBg != null) {
mWindow.setBackgroundDrawable(winBg);
}
a.recycle();
}
這樣就完成了鲁森。接下來看看setContentView()是如何執(zhí)行的。
進(jìn)入AppCompatDelegateImplV9.java的setContentView():
@Override
public void setContentView(int resId) {
//確保創(chuàng)建一個(gè)SubDecor
ensureSubDecor();
//通過findviewbyid的方式找到android.R.id.contentd代表的view振惰,作為一個(gè)contentParent
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
//清空
contentParent.removeAllViews();
//將我們自己的布局文件加載到這個(gè)contentParent中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
再看看這個(gè)SubDecor是什么:
// true if we have installed a window sub-decor layout.
private boolean mSubDecorInstalled;
private ViewGroup mSubDecor;
所以我們自己寫的布局文件最終是被加載到了一個(gè)id為content的ViewGroup上歌溉,而這個(gè)ViewGroup是通過subDecor來找到的,而這個(gè)SubDecor也是一個(gè)ViewGroup骑晶。那么重點(diǎn)就是ensureSubDecor()了痛垛,他的作用應(yīng)該就是初始化一個(gè)SubDecor了:
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
//創(chuàng)建一個(gè)SubDecor
mSubDecor = createSubDecor();
// If a title was set before we installed the decor, propagate it now
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
onTitleChanged(title);
}
applyFixedSizeWindow();
//做一個(gè)install?
onSubDecorInstalled(mSubDecor);
//標(biāo)識已經(jīng)installed
mSubDecorInstalled = true;
// 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 (!isDestroyed() && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}
現(xiàn)在就分為了兩步:
- mSubDecor是如何被創(chuàng)建的
- 創(chuàng)建成功之后做了什么
第一個(gè)問題桶蛔,看createSubDecor()方法:
private ViewGroup createSubDecor() {
//和自定義View時(shí)獲取屬性類似匙头,這兒是從AppCompatTheme獲取了屬性
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
//這里判斷如果沒有加這個(gè)屬性的話會(huì)拋出異常
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.");
}
//接下來就是普通的挨個(gè)遍歷屬性了
if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
}
mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();
// Now let's make sure that the Window has installed its decor by retrieving it
//這里通過Window對象(即PhoneWindow)調(diào)用了getDecorView()方法,猜測是獲取Decor
//這里是重點(diǎn)仔雷,待會(huì)兒分析
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
//創(chuàng)建了一個(gè)subDecor引用蹂析,還未實(shí)例化
ViewGroup subDecor = null;
//根據(jù)不同需求,讓subDecor 裝載不同的布局
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) {
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 (Build.VERSION.SDK_INT >= 21) {
// If we're running on L or above, we can rely on ViewCompat's
// setOnApplyWindowInsetsListener
ViewCompat.setOnApplyWindowInsetsListener(subDecor,
new OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
final int top = insets.getSystemWindowInsetTop();
final int newTop = updateStatusGuard(top);
if (top != newTop) {
insets = insets.replaceSystemWindowInsets(
insets.getSystemWindowInsetLeft(),
newTop,
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom());
}
// Now apply the insets on our view
return ViewCompat.onApplyWindowInsets(v, insets);
}
});
} else {
// Else, we need to use our own FitWindowsViewGroup handling
((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
new FitWindowsViewGroup.OnFitSystemWindowsListener() {
@Override
public void onFitSystemWindows(Rect insets) {
insets.top = updateStatusGuard(insets.top);
}
});
}
}
//到此為止碟婆,subDecor算是實(shí)例化完畢了
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);
//這里開始重點(diǎn)來了
//從subDecor中拿到了一個(gè)ContentFrameLayout,注意id為R.id.action_bar_activity_content
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//從window中拿到一個(gè)id為content的ViewGroup
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
// 這里电抚,依次從window中那個(gè)viewgroup中取出子View
//然后將他們放入那個(gè)從subDecor中拿到的Content中
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.
//全部挪完之后,給原來window中的那個(gè)ViewGroup把id值為NO_ID
windowContentView.setId(View.NO_ID);
//然后偷梁換柱竖共,把那個(gè)ContentFrameLayout的id設(shè)為了content
contentView.setId(android.R.id.content);
// The decorContent may have a foreground drawable set (windowContentOverlay).
// Remove this as we handle it ourselves
//把那個(gè)背景也去掉了
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}
// Now set the Window's content view with the decor
//貍貓換太子蝙叛,直接把subDecor給了Window
mWindow.setContentView(subDecor);
contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
@Override
public void onAttachedFromWindow() {}
@Override
public void onDetachedFromWindow() {
dismissPopups();
}
});
return subDecor;
}
再來看看那個(gè)重點(diǎn)標(biāo)記的方法:
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
哦?原來window的getDecorView()方法其實(shí)就是前面提到過的installDecor()方法誒肘迎!之前說過甥温,installDecor()方法是什么作用來著?
首先要初始化一個(gè)叫做DecorView的FrameLayout妓布,他是和當(dāng)前Window息息相關(guān)的姻蚓。我們知道一個(gè)Activity,他的顯示界面這個(gè)模塊是交給了Window管理匣沼,而在Window中則是交給了DeocrView狰挡。這個(gè)DecorView會(huì)根據(jù)不同的需求(主題)來給自己填上一個(gè)不同的布局。然后在加載進(jìn)來的這個(gè)布局中释涛,有一個(gè)ViewGroup是專門用來顯示我們編寫的界面的加叁,這個(gè)ViewGroup會(huì)通過findViewById()的形式在DecorView中找到,然后交給mContentParent唇撬,這樣我們要將自己寫的布局加載進(jìn)來的時(shí)候它匕,就是直接加載到mContentParent中就可以了。
馬上接觸到真相了窖认,再隨便找個(gè)剛才所引用到的布局文件看看豫柬,比如R.layout.abc_screen_simple:
<android.support.v7.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">
<android.support.v7.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" />
</android.support.v7.widget.FitWindowsLinearLayout>
還有abc_screen_content_include.xml:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.v7.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è)id:action_bar_activity_content告希,而且他還是很個(gè)ContentFrameLayout 發(fā)現(xiàn)沒有?這里的SubDecorView和以前的DecorView邏輯是很像的烧给!都是以自己作為一個(gè)大的ViewGroup燕偶,里面放另一個(gè)小ViewGroup,在這個(gè)小ViewGroup中础嫡,還有一個(gè)ViewGroup作為根布局指么。
捋一捋剛才的流程:
- 首先創(chuàng)建了兩個(gè)DecorView,一個(gè)就是以前Activity直接用的那個(gè)DecorView榴鼎,另一個(gè)叫做SubDecorView
- 將舊DecorView的content內(nèi)容交給SubDecorView的content
- 將SubDecorView作為一個(gè)整體伯诬,交給DecorView
總之,就是一個(gè)替換的過程檬贰。
再回到前面看看:
@Override
public void setContentView(int resId) {
//這里做了剛才所說的一切姑廉,現(xiàn)在是兩個(gè)DecorView嵌套起來了
ensureSubDecor();
//id為content的ViewGroup現(xiàn)在的內(nèi)容其實(shí)就是以前的DecorView用的那個(gè)
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
//清空
contentParent.removeAllViews();
//將我們自己的布局文件加載到這個(gè)contentParent中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
至此缺亮,Activity的AppCompatActivity的setContent()的流程都分析完了翁涤,總結(jié)一下:
- 一個(gè)Activity,有很多功能萌踱,其中的“展示東西給別人看”這個(gè)功能葵礼,是交給自己的一個(gè)Window對象來管理的。
- Window包含一個(gè)ViewGroup并鸵,作為根ViewGroup
- 根ViewGroup鸳粉,根據(jù)不同的需求(即主題定義等)會(huì)加載不同的布局文件
- 以最基本的一種布局來講,他包含一個(gè)title和一個(gè)content
- 我們setContent()時(shí)傳入的布局文件id所指向的那個(gè)布局文件园担,會(huì)被加載到這個(gè)content中
- Activity和AppCompatActivity在這里的區(qū)別在于届谈,Activity單純的用一個(gè)DecorView,AppCompatActivity則是在原來的基礎(chǔ)上弯汰,加了一個(gè)SubDeocrView艰山,將舊的DecorView的內(nèi)容放到SubDecorView的content中,然后將SubDecorView作為整體放入舊的DecorView的content中咏闪,也就是說曙搬,一個(gè)DecorView包裹著一個(gè)SubDecorView