Android 窗口机制 SDK31源码分析 总目录
- 初识
- DecorView与SubDecor的创建加载
- Window与Window Manager的创建加载
- ViewRootImpl的创建以及视图真正加载
- ViewRootImpl的事件分发
- 一定要在主线程才可以更新UI吗?为什么?
- Activity的Token和Dialog的关系
- Toast机制 - 封装可以在任何线程调用的toast
- 总结
本篇文章介绍一下DecorView与SubDecor的创建初始化流程。
了解一组概念,Window、PhoneWindow、DecorView、SubDecor.
Window
从前面的介绍中我们知道,每个Activity都会持有一个Window实例,那么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.
The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.
public abstract class Window {
...
private WindowManager mWindowManager;
...
Convenience for setContentView(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.
Params:
layoutResID – Resource ID to be inflated.
See Also:
setContentView(View, ViewGroup.LayoutParams)
public abstract void setContentView(@LayoutRes int layoutResID);
...
}
顶级窗口外观和行为策略的抽象基类,这个类的实例应该作为顶级视图被添加到窗口管理器,它提供了标准的 UI 策略,例如背景、标题区域、默认键处理等。
此抽象类的唯一现有实现是 PhoneWindow,你应该在需要 Window 时对其进行实例化。
显而易见,Window是一个抽象基类,它提供一系列窗口的方法,比如设置布局、背景、标题等等,它唯一的子类即为PhoneWindow。
可以看到Window内部会持有WindowManager类型的全局变量mWindowManager
。
PhoneWindow
public class PhoneWindow extends Window implements MenuBuilder.Callback {
...
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
...
}
PhoneWindow作为Window的唯一实现类,内部持有了DecorView。
我们在Activity中,通过getWindow()
方法获取到的就是PhoneWindow的实例。
DecorView
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
...
private final int mFeatureId;
private final Rect mDrawingBounds = new Rect();
private final Rect mBackgroundPadding = new Rect();
...
}
显然DecorView是一个FrameLayout,它将作为最顶级的View,由PhoneWindow负责添加。
SubDecor
这个玩意儿是什么呢?如果你看大多数Android窗口分析的话,应该是没有这个东西的,但是现在自己去看代码的话应该能看到这个View。
//AppCompatDelegateImpl 里面创建SubDecor 的方法
private ViewGroup createSubDecor() {
}
因为现在的源码setContentView
的操作委托给了AppCompatDelegateImpl去操作,在这里面会创建一个SubDecor,类型是ViewGroup,添加到DecorView里面。所以如果是较新的Android版本的话,我们自己的布局其实是添加到SubDecor里面的。
那上面这些东西是如何进行关联呢?下面从AppCompatActivity
的setContentView
开始进行源码流程跟踪。setContentView
方法有几个重载方法,分别可以传入布局ID
或者View
或者布局ID+LayoutParams
,因为个人习惯使用ViewBinding,所以调用setContentView
的话传进去的直接是View了,所以我们就分析接收View的这个重载方法。
AppCompatActivity
public void setContentView(View view) {
getDelegate().setContentView(view);
}
调用了getDelegate().setContentView(view)
,而getDelegate()
获取的就是AppCompatDelegateImpl
,所以这里其实就是调用到了AppCompatDelegateImpl的setContentView(view)
方法。
AppCompatDelegateImpl
public void setContentView(View v) {
//构建SubDecor
ensureSubDecor();
//将我们传进入的View添加进SubDecor
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
//通知内容改变
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
这里首先调用ensureSubDecor
方法,构建SubDecor
。之后将我们传进来的View添加到SubDecor
内id为content的ViewGroup中。
所以我们看一下ensureSubDecor
方法,看一下SubDecor
是如何构建出来的。
AppCompatDelegateImpl
private void ensureSubDecor() {
...
//构建SubDecor 布局
mSubDecor = createSubDecor();
//设置标题到mSubDecor布局 getTitle() 方法获取到的就是在AndroidManifest.xml 中给Activity设置的label, 通过这里设置到了Android原生主题的标题
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);
}
}
//根据主题的宽高,比如windowFixedWidthMajor等,重新设置给mSubDecor布局容器。
applyFixedSizeWindow();
...
}
上面的注释应该写的比较明确了,通过createSubDecor
方法构建出了SubDecor,下面都是对SubDecor的一些设置,所以我们看一下createSubDecor
方法。
AppCompatDelegateImpl
private ViewGroup createSubDecor() {
...
//确保已经存在Window对象,否则抛异常
ensureWindow();
//安装DecorView
mWindow.getDecorView();
//根据各种有无工具栏等设置subDecor布局
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
... //列出其中一个布局,没有标题栏的
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
...
//获取subDecor内的容器
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//获取DecorView内的容器
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
...
//兼容:将原来的DecorView内部容器的id设置为NO_ID,将subDecor内部容器id设置为content。所以对于外层调用这来说通过content都可以获取到底层的容器。
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
...
}
//将subDecor添加到DecorView之中
mWindow.setContentView(subDecor);
...
return subDecor;
}
注释的已经很清楚了吧,我们挑重点来看。首先是调用了ensureWindow
方法,该方法主要判断Window对象是否存在,不存在则会抛出异常。接下来调用Window的getDecorView
方法,去获取DecorView。之后就是使用LayoutInflater
填充SubDecor,这里省略了很多判断布局,总之会根据主题设置有无标题等相关的进行布局填充,比如上述列出填充的布局为abc_screen_simple
,该布局没有标题,有一个id为action_bar_activity_content
的FramLayout,再下来将它的id更换为content
。之后将SubDecor通过Window添加到DecorView之中。
简单分析一下我们需要关注的重点:
- DecorView是如何安装的,看
getDecorView
方法。 - SubDecor是如何安装到DecorView的,看
setContentView
方法。
好,接下来看一下Window的getDecorView
方法。通过上面分析我们知道Window的唯一实现类为PhoneWindow,所以我们看PhoneWindow对应的方法。
PhoneWindow
public final @NonNull View getDecorView() {
...
installDecor();
...
return mDecor;
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//构建DecorView
mDecor = generateDecor(-1);
...
if (mContentParent == null) {
//填充DecorView布局
mContentParent = generateLayout(mDecor);
...
}
protected DecorView generateDecor(int featureId) {
...
//直接new
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
...
//设置flag相关,所以这儿就是为什么falg的设置要在调用setContentView之前
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);
}
...
if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
false)) {
setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
& (~getForcedWindowFlags()));
}
//和前面SubDecor一样,根据主题设置宽高等属性
...
if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedWidthMajor,
mFixedWidthMajor);
}
...
// DecorView 布局填充
int layoutResource;
...
//这里列出一个最简单的布局 一个title 一个id为content的Framelayout
layoutResource = R.layout.screen_simple;
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//ID_ANDROID_CONTENT 其实就是R.id.content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
注释应该也非常明显了,内部核心调用了installDecor
进行DecorView的构建,installDecor
内部调用了两个核心的方法,一个是generateDecor
;一个是generateLayout
。
根据上面所说,大家应该知道DecorView其实是一个FrameLayout,所以generateDecor
方法其实直接new了一个DecorView
返回了,并赋值给了mDecor变量。
而generateLayout
其实就是根据主题各种判断去选择系统的布局,最终加载到了DecorView之中。那么DecorView就创建完成了。
那么DecorView和SubDecor是如何关联的呢?还记得在createSubDecor
方法的最后调用了mWindow.setContentView(subDecor)
吗?接下来我们看看这个方法干了什么吧。
PhoneWindow
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//在getDecorView方法中已经给mContentParent赋值了,所以走这里
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
//将SubDecor添加进DecorView的布局之中
mContentParent.addView(view, params);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//回调通知表示完成界面改变
cb.onContentChanged();
}
...
}
显而易见,将SubDecor给添加到了DecorView之中。之后通知Activity
界面已经完成改变,因为Activity
实现了Window.Callback
接口,是一个空实现,所以可以通过重写该方法来监听布局内容的改变。
public void onContentChanged() {
}
所以总结一下,调用onCreate时,我们的Activity已经持有了Window对象,且通过调用setContentView
方法,我们会现在PhoneWindow中根据主题构建DecorView,之后因为compat
等原因,会构建SubDecor添加到DecorView之中,而我们设置的布局则会添加到SubDecor之中,并且被添加的布局id为content。比如通过getWindow().getDecorView()
获取到的对象,就是PhoneWindow中的DecorView。
那么Activity
是合适拥有了PhoneWindow呢?上面说到的CallBack是合适设置到PhoneWindow中呢?可以继续关注下一篇文章:Window与Window Manager的创建加载。
创作不易,如有帮助一键三连咯🙆♀️。欢迎技术探讨噢!