1、 Window
window 位于 android.view.Window.java。
我们来看下源码里面的说明
/**
* 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.
*/
public abstract class Window {
...
@Nullable
public View findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
/**
* Convenience for * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
* to set the screen content from a layout resource. The resource will be * inflated, adding all top-level views to the screen. * * @param layoutResID Resource ID to be inflated.
* @see #setContentView(View, android.view.ViewGroup.LayoutParams)
*/
public abstract void setContentView(@LayoutRes int layoutResID);
...
}
该类是一个抽象类,提供了绘制窗口的一组通用API。可以将之理解为一个载体,各种View在这个载体上显示。每一个Activity都包含一个Window对象
2、 PhoneWindow
PhoneWindow位于com.android.internal.policy.PhoneWindow.java。
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private final static String TAG = "PhoneWindow";
...
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
// 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.
private ViewGroup mContentParent;
private ViewGroup mContentRoot;
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
private void installDecor() {
//mDecor是DecorView,第一次mDecor=null,所以调用generateDecor
if (mDecor == null) {
mDecor = generateDecor();
...
}
//第一次mContentParent也等于null
if (mContentParent == null) {
//可以看到把DecorView传入进去了
mContentParent = generateLayout(mDecor);
}
}
...
}
该类继承于Window类,是Window类的具体实现,我们可以通过该类具体去绘制窗口。该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。 PhoneWindow类是把一个DecorView(FrameLayout)对象进行一定的包装,并提供一组通用的窗口操作接口。
3、 DecorView
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
/* package */int mDefaultOpacity = PixelFormat.OPAQUE;
/** The feature ID of the panel, or -1 if this is the application's DecorView */
private final int mFeatureId;
private final Rect mDrawingBounds = new Rect();
private final Rect mBackgroundPadding = new Rect();
private final Rect mFramePadding = new Rect();
private final Rect mFrameOffsets = new Rect();
....
}
继承自FrameLayout,该类就是对普通的FrameLayout进行功能的扩展,更确切点可以说是修饰(Decor的英文全称是Decoration,即“修饰”的意思),比如说添加TitleBar(标题栏)。最重要的一点是,它是所有应用窗口的根View 。
4、 AppCompatActivity.setContentView源码流程
compileSdkVersion 27 本版本以下源码是基于sdk 27 分析的,不同版本有不同
点击 AppCompatActivity.setContentView();方法
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
/**
* @return The {@link AppCompatDelegate} being used by this Activity.
*/
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
点击AppCompatDelegate.create
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return create(activity, activity.getWindow(), callback);
}
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
if (Build.VERSION.SDK_INT >= 24) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (Build.VERSION.SDK_INT >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else {
return new AppCompatDelegateImplV14(context, window, callback);
}
}
重点来了
AppCompatDelegateImplN extends AppCompatDelegateImplV23
AppCompatDelegateImplV23 extends AppCompatDelegateImplV14
AppCompatDelegateImplV14 extends AppCompatDelegateImplV9
AppCompatDelegateImplV9 的部分源码
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mOriginalWindowCallback.onContentChanged();
}
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
我们自己的view 被添加到content 里面了,
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
//这个mSubDecor其实就ViewGroup,调用createSubDecor()后,此时存放我们的布局的容器已经准备好了
mSubDecor = createSubDecor();//核心代码!
.....
}
调用了createSubDecor(),看字面意思创建了一个SubDecor,看似跟DecorView有联系。我们看下里面做了什么操作
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
a.recycle();
//还记得我们使用AppCompatActivity如果不设置AppCompat主题报的错误吗?就是在这里抛出来的
throw new IllegalStateException(
"You need to use a Theme.AppCompat theme (or descendant) with this activity.");
}
//初始化相关特征标志
if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
//一般我们的主题默认都是NoTitle
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();
//重点!在这里就创建DecorView,至于DecorView到底是什么以及如何创建的,稍后会讲到
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
//可以看到其实就是个ViewGroup,我们接着往下看,跟DecorView到底有啥关系
ViewGroup subDecor = null;
if (!mWindowNoTitle) {
//上面说了主题默认都是NoTitle,所以不会走里面的方法
if (mIsFloating) {
// If we're floating, inflate the dialog title decor
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
...
} else if (mHasActionBar) {
TypedValue outValue = new TypedValue();
mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
...
// 重点---------------->1
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
/**
* 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 {
//我们进入else
if (mOverlayActionMode) {
//调用了requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY)会走进来
// 重点---------------->2
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_screen_simple_overlay_action_mode, null);
} else {
//ok,所以如果这些我们都没设置,默认就走到这里来了,在这里映射出了subDecor,稍后我们来看下这个布局是啥
// 重点---------------->3
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);
//这个contentView很重要,是我们布局的父容器,你可以把它直接当成FrameLayout
// 重点---------------->4
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//看过相关知识的同学应该知道android.R.id.content这个Id在以前是我们布局的父容器的Id
// 重点---------------->5
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);
}
//注意!原来windowContentView的Id是android.R.id.content,现在设置成NO_ID
// 重点---------------->6
windowContentView.setId(View.NO_ID);
//在之前这个id是我们的父容器,现在将contentView设置成android.R.id.content,那么可以初步判定,这个contentView将会是我的父容器
// 重点---------------->7
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里面,这个Window是个抽象类,其实现类是PhoneWindow,稍后会讲到
mWindow.setContentView(subDecor);
....
return subDecor;
}
我们来看下这个abc_screen_simple.xml的布局到底是什么样子的
<android.support.v7.internal.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.internal.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.internal.widget.FitWindowsLinearLayout
abc_screen_content_include.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.v7.internal.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>
原来这个subDecor就是FitWindowsLinearLayout
非常重要的两行代码
mWindow.getDecorView();
mWindow.setContentView(subDecor);
而 mWindow 是 activity.getWindow() 获取到的
我们打开 activity源码
mWindow = new PhoneWindow(this, window, activityConfigCallback);
发现 这里的window 就是PhoneWindow
在generateDecor()做了什么?其实返回了一个DecorView对象。
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());
}
我们在来看看generateLayout()做了什么。
protected ViewGroup generateLayout(DecorView decor) {
TypedArray a = getWindowStyle();
//设置一堆标志位...
...
if (!mForcedStatusBarColor) {
//获取主题状态栏的颜色
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
}
if (!mForcedNavigationBarColor) {
//获取底部NavigationBar颜色
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
}
//获取主题一些资源
...
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
...我们设置不同的主题以及样式,会采用不同的布局文件...
} else {
//记住这个布局,之后我们会来验证下布局的结构
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
//要开始更改mDecor啦~
mDecor.startChanging();
//注意,此时把screen_simple放到了DecorView里面
// 重点---------1
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//这里的ID_ANDROID_CONTENT就是R.id.content;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
//这里的getContainer()返回的是个Window类,也就是父Window,一般为空
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
//设置背景
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();
return contentParent;
}
我们来看看这个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" />
<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 的onResourcesLoaded
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();
}
此时 screen_simple.xml 添加到 DecorView 里面了
再来看看 mWindow.setContentView(subDecor);
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
上面说到了 subDecor就是FitWindowsLinearLayout
此时将 FitWindowsLinearLayout 添加到mContentParent了
我们看看 mContentParent 是个什么东西
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
protected ViewGroup generateLayout(DecorView decor) {
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
可以发现contentParent 就是
screen_simple.xml 重的Framelayout
5校验、
ViewGroup decor = (ViewGroup) getWindow().getDecorView();
View decorChild = decor.findViewById(android.R.id.content);
while (decorChild.getParent() != decor){
decorChild = (View) decorChild.getParent();
AppLog.e(decorChild.getClass());
}
AppLog: class android.support.v7.widget.FitWindowsLinearLayout
AppLog: class android.widget.FrameLayout
AppLog: class android.widget.LinearLayout
在Android studio的Tools–>Layout Inspector–>选中连接的手机–>打开你要看的应用,然后在屏幕上就会显示当前页面的布局结构图View Tree
6、 Activity
AppLog: class android.widget.LinearLayout