分析源码,搞清楚Activity的setContent()背后的逻辑
Activity 的setContent()流程
以前的Activity,都是直接继承Activity.java,而现在的Activity则基本都是继承AppCompatActivity.java,自然setContent()是不一样的,那么先捋一捋旧的
Activity.java
先从Activity.java开始看起。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看到,getWindow()方法获取了一个自己Activity持有的Window对象的引用,再调用这个对象的setContent(),之后做一个初始化流程。Window类是一个抽象类:
/**
* 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.
*/
看注释,这大概是一个Activity所呈现界面的顶层Window。他的实现类只有一个,是PhoneWindow。那么就来看看这个PhoneWindow类的setContentView()方法实现:
@Override
public void setContentView(int layoutResID) {
//首先这里有一个ContentParent,如果为空则做一个初始化
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 {
/*
不需要动画,直接开始加载布局,这里是将layoutResID布局加载到了mContentParent上
而layoutResID是我们交给setContent()的那个布局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;
可以看到,这个mContentParent其实就是一个ViewGroup
所以在setContent()中主要做了两件事:
- 初始化一个Window持有的ContentParent(即ViewGroup)对象
- 将布局文件加载到ContentParent上
那么现在看看这个所谓的初始化过程做了什么,即installDecor():
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {//第一步,发现mDecor没有初始化
//生成一个mDecor对象,并对其初始化
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
//让mDecor获取一个当前window的引用
mDecor.setWindow(this);
}
if (mContentParent == null) {//第二步,发现mContentParent没有初始化
//用前面的mDecor生成一个mContentParent对象并初始化
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
//在mDecor中找一下是否有一个DecorContentParent
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
//有?对这个做初始化
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 {//没有?那么从这里开始
//获取一个作为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);
}
}
}
//对这个mDecor设置背景(回调)
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
//之后就是一些无关紧要的东西了
}
再看看这个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就是一个FrameLayout了。
那么这个初始化过程就分为了两步:
- 初始化mDecor(一个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());
}
先是用想办法和获取一个context,然后再调用新的构造器,这里的featureId传进来的是-1。然后看构造器:
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();
//前面不是有一个在发现mDecorView不为Null时要赋予一个当前window引用吗?这里就是在初始化完成后再做的
setWindow(window);
updateLogTag(params);
mResizeShadowSize = context.getResources().getDimensionPixelSize(
R.dimen.resize_shadow_size);
initResizingPaints();
}
至此一个DecorView就初始化完成了,他实际上是一个FrameLayout。接下来看看这个mContentParent是如何通过DecorView来生成的:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//这里先拿到一些属性
TypedArray a = getWindowStyle();
//…………
//这里开始先是对每一种属性做判断了,比如是否悬浮?是否无标题?等等
//具体方法和我们写自定义View时是一样的,这里省略了
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相关的信息,比如targetsdk
final Context context = getContext();
final int targetSdk = context.getApplicationInfo().targetSdkVersion;
//………………
WindowManager.LayoutParams params = getAttributes();
//这里是和高端设备相关的设置
// 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.
//这里开始,就来真的了
//这个int值代表了要加载的布局的id
int layoutResource;
//所需的属性
int features = getLocalFeatures();
//然后,根据属性不同的需求,获取不同的布局文件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.
//记住这个布局文件id
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
//标识着这个decorview开始改变了
mDecor.startChanging();
//将刚才那个布局文件,加载到decor中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//通过findviewbyid()的方式获取这个contentParent,记住这个ID_ANDROID_CONTENT
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//………………
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
//初始化背景和标题等等一些属性
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
//为decorview设置背景
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);
}
//标识着改变结束
mDecor.finishChanging();
//最后,返回这个contentParent
return contentParent;
}
总结一下,就是给这个framelayout————DecorView设置了一种布局,然后通过findviewbyid的方式获取一个contentparent的。那么这两者有什么关系呢?观察到前面提到了两个id,联系就在这里!所以接下来看看具体设置布局的逻辑。
首先看看加载布局:
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指向的那个布局,这里加载后为一个叫做root的View,然后通过调用addView()方法————我们知道DecorView本身是一个FrameLayout————将root加载到自己这个FrameLayout中。
接下来看看layoutResource所引用的布局R.layout.screen_simple,即screen_simple.xml的内容:
<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>
嗯,一个LinearLayout,包含了一个ViewStub占位和一个FrameLayout。做一个猜测,这就是我们Activity最普通的初始界面,即一个状态栏+一个主界面。然后发现下面那个FrameLayout的id是content,再回到刚才方法中,通过findviewbyid初始化找到contentParent的时候用的id是哪个?
/**
* 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;
根据注释,我们知道了,这个id引用的view就是我们的主布局要加载的地方,也就是在刚才那个xml文件中的FrameLayout!
到此为止,一个installDecor()的过程基本完成了,来捋一捋。
首先要初始化一个叫做DecorView的FrameLayout,他是和当前Window息息相关的。我们知道一个Activity,他的显示界面这个模块是交给了Window管理,而在Window中则是交给了DeocrView。这个DecorView会根据不同的需求(主题)来给自己填上一个不同的布局。然后在加载进来的这个布局中,有一个ViewGroup是专门用来显示我们编写的界面的,这个ViewGroup会通过findViewById()的形式在DecorView中找到,然后交给mContentParent,这样我们要将自己写的布局加载进来的时候,就是直接加载到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 {
//这里可以看到,前面初始化结束后,果然是将我们自己写的布局加载到了mContentParent中!
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
至此,Activity的setContent()流程就是走完了,大致知道了布局是怎么加载进来的。接下来看看新的AppCompatActivity是如何加载布局的
AppCompatActivity
接下来再看看AppCompatActivity是如何加载布局的
先看AppCompatActivity.java的setContentView()方法:
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
这里通过getDelegate()方法获取了一个对象的引用,再调用他的setContentView()方法,相当于做了一个代理。
那么现在问题拆分为两步:
- 代理的对象是如何创建的
- 代理对象的setContentView()是如何执行的
先看第一个问题:
/**
* @return The {@link AppCompatDelegate} being used by this Activity.
*/
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
这里用来做代理的,是一个AppCompatDelegate对象,叫mDelegate,他是通过一个静态方法create()创建的,那么先看看这个类是什么:
<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);
}
}
可以看到,这里最终是根据不同的sdk版本来创建不同的AppCompatDelegateImplxxx对象,分别点进去看看后会发现,最终都是到了AppCompatDelegateImplV9.java,然后:
class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
AppCompatDelegateImplV9(Context context, Window window, AppCompatCallback callback) {
super(context, window, callback);
}
所以最终是调用了父类的构造器:
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()是如何执行的。
进入AppCompatDelegateImplV9.java的setContentView():
@Override
public void setContentView(int resId) {
//确保创建一个SubDecor
ensureSubDecor();
//通过findviewbyid的方式找到android.R.id.contentd代表的view,作为一个contentParent
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
//清空
contentParent.removeAllViews();
//将我们自己的布局文件加载到这个contentParent中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
再看看这个SubDecor是什么:
// true if we have installed a window sub-decor layout.
private boolean mSubDecorInstalled;
private ViewGroup mSubDecor;
所以我们自己写的布局文件最终是被加载到了一个id为content的ViewGroup上,而这个ViewGroup是通过subDecor来找到的,而这个SubDecor也是一个ViewGroup。那么重点就是ensureSubDecor()了,他的作用应该就是初始化一个SubDecor了:
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
//创建一个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();
//做一个install?
onSubDecorInstalled(mSubDecor);
//标识已经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);
}
}
}
现在就分为了两步:
- mSubDecor是如何被创建的
- 创建成功之后做了什么
第一个问题,看createSubDecor()方法:
private ViewGroup createSubDecor() {
//和自定义View时获取属性类似,这儿是从AppCompatTheme获取了属性
TypedArray a = mContext.obtainStyledAttributes(R.styleable.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.");
}
//接下来就是普通的挨个遍历属性了
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)调用了getDecorView()方法,猜测是获取Decor
//这里是重点,待会儿分析
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
//创建了一个subDecor引用,还未实例化
ViewGroup subDecor = null;
//根据不同需求,让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算是实例化完毕了
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);
//这里开始重点来了
//从subDecor中拿到了一个ContentFrameLayout,注意id为R.id.action_bar_activity_content
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//从window中拿到一个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中那个viewgroup中取出子View
//然后将他们放入那个从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中的那个ViewGroup把id值为NO_ID
windowContentView.setId(View.NO_ID);
//然后偷梁换柱,把那个ContentFrameLayout的id设为了content
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
//狸猫换太子,直接把subDecor给了Window
mWindow.setContentView(subDecor);
contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
@Override
public void onAttachedFromWindow() {}
@Override
public void onDetachedFromWindow() {
dismissPopups();
}
});
return subDecor;
}
再来看看那个重点标记的方法:
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
哦?原来window的getDecorView()方法其实就是前面提到过的installDecor()方法诶!之前说过,installDecor()方法是什么作用来着?
首先要初始化一个叫做DecorView的FrameLayout,他是和当前Window息息相关的。我们知道一个Activity,他的显示界面这个模块是交给了Window管理,而在Window中则是交给了DeocrView。这个DecorView会根据不同的需求(主题)来给自己填上一个不同的布局。然后在加载进来的这个布局中,有一个ViewGroup是专门用来显示我们编写的界面的,这个ViewGroup会通过findViewById()的形式在DecorView中找到,然后交给mContentParent,这样我们要将自己写的布局加载进来的时候,就是直接加载到mContentParent中就可以了。
马上接触到真相了,再随便找个刚才所引用到的布局文件看看,比如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>
看看那个id:action_bar_activity_content,而且他还是很个ContentFrameLayout 发现没有?这里的SubDecorView和以前的DecorView逻辑是很像的!都是以自己作为一个大的ViewGroup,里面放另一个小ViewGroup,在这个小ViewGroup中,还有一个ViewGroup作为根布局。
捋一捋刚才的流程:
- 首先创建了两个DecorView,一个就是以前Activity直接用的那个DecorView,另一个叫做SubDecorView
- 将旧DecorView的content内容交给SubDecorView的content
- 将SubDecorView作为一个整体,交给DecorView
总之,就是一个替换的过程。
再回到前面看看:
@Override
public void setContentView(int resId) {
//这里做了刚才所说的一切,现在是两个DecorView嵌套起来了
ensureSubDecor();
//id为content的ViewGroup现在的内容其实就是以前的DecorView用的那个
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
//清空
contentParent.removeAllViews();
//将我们自己的布局文件加载到这个contentParent中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
至此,Activity的AppCompatActivity的setContent()的流程都分析完了,总结一下:
- 一个Activity,有很多功能,其中的“展示东西给别人看”这个功能,是交给自己的一个Window对象来管理的。
- Window包含一个ViewGroup,作为根ViewGroup
- 根ViewGroup,根据不同的需求(即主题定义等)会加载不同的布局文件
- 以最基本的一种布局来讲,他包含一个title和一个content
- 我们setContent()时传入的布局文件id所指向的那个布局文件,会被加载到这个content中
- Activity和AppCompatActivity在这里的区别在于,Activity单纯的用一个DecorView,AppCompatActivity则是在原来的基础上,加了一个SubDeocrView,将旧的DecorView的内容放到SubDecorView的content中,然后将SubDecorView作为整体放入旧的DecorView的content中,也就是说,一个DecorView包裹着一个SubDecorView
网友评论