DecorView
是Android应用程序中所有视图的根视图。它是框架用来管理和显示应用程序界面的核心组件之一。理解DecorView
的创建流程对于理解Android视图系统的运作方式至关重要。
简介
DecorView的主要角色是作为顶层容器,承载着应用的视图结构。当在应用中使用setContentView方法加载布局时,实际上是将这个布局作为子视图添加到DecorView中。因此,DecorView定义了应用界面的边界,所有的视图都在这个边界内进行绘制和事件分发。
下面我们来说一下,DecorView与Window、Activity和ViewRootImpl之间的关系,这能够更好地帮助我们理解应用的视图层次结构。
与Window的关系
Window是Android中的一个抽象概念,代表着屏幕上的一块区域,可以用来显示视图。每个Activity都会被赋予一个Window,而这个Window则负责承载DecorView。简单来说,Window是一个显示DecorView的容器。在Android中,Window和View通过WindowManager服务来管理,WindowManager负责将Window(及其包含的DecorView)放置到屏幕上的正确位置。
与Activity的关系
Activity是Android应用中的一个基本组件,负责创建用户界面。每个Activity都会有一个与之关联的Window,而这个Window则承载着DecorView。在Activity的生命周期中,当调用setContentView
方法时,系统就会开始构建视图层次结构,将指定的布局文件加载到当前Activity的Window所关联的DecorView中。
与ViewRootImpl的关系
ViewRootImpl是Android UI系统的内部机制,作为桥梁连接Window和DecorView。它负责初始化视图层次结构的根,处理布局、绘制、事件分发等。当一个Activity的视图被设置或者窗口发生变化时,ViewRootImpl确保DecorView得到更新和重新绘制。ViewRootImpl是不对开发者公开的,但它在视图渲染和事件处理过程中起着关键作用。
创建流程
DecorView的创建通常在Activity的生命周期的onCreate方法中开始,具体是通过调用setContentView方法触发的。
当Activity的setContentView
方法被调用时,背后的LayoutInflater就开始发挥作用。这个方法接受一个布局资源ID,然后LayoutInflater负责找到对应的布局文件,解析它,并根据文件中的定义构建出一个完整的View树。这个View树随后被设置为Activity的内容视图,实质上是被添加到Activity所关联的Window的DecorView中。
类似于我们直接使用LayoutInflater加载获取到View是一样的。
kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用LayoutInflater加载布局
val inflater = LayoutInflater.from(this)
val view = inflater.inflate(R.layout.activity_main, null)
setContentView(view)
}
所以,DecorView的创建之前,需要经过Activity的启动。
创建PhoneWindow
在 Activity 的 attach() 方法中,会创建一个 PhoneWindow 对象。PhoneWindow 是 Window 的一个子类,它负责管理应用程序窗口的外观和行为。
javascript
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
IBinder shareableActivityToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mActivityInfo = info;
// 创建Window
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
....
}
在PhoneWindow中,会初始化DecorView,但它的触发逻辑是在调用setContentView
的时候
初始化DecorView
当Activity启动时,在onCreate
方法中通常会调用setContentView
方法来设置Activity的用户界面布局。
kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
这段代码中,setContentView
是Activity
类中的一个方法,它接受一个布局资源ID,用于指定Activity的布局。
在Activity的setContentView
方法内部,会进行以下几个关键步骤:
-
获取Window : 首先,
setContentView
通过getWindow()
方法获取当前Activity的Window对象。Window对象代表了Android窗口管理系统中的一个窗口。 -
布局解析: 使用LayoutInflater解析指定的布局资源ID。这个过程会根据布局文件中的定义,创建出对应的View对象,并按照布局文件的层次结构组装这些对象,形成一个完整的视图树。
-
设置内容视图 : 通过Window的
setContentView
方法,将解析好的视图树设置为Window的内容视图。这个视图树的根节点,就是我们所说的DecorView。
java
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
Window的setContentView
方法内部,进一步调用了PhoneWindow
的setContentView
实现。在这个方法中,会创建或找到DecorView,然后将解析的视图树添加到DecorView中。
java
@Override
public void setContentView(int layoutResID) {
// 确保DecorView已经被创建
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;
}
installDecor
方法负责初始化DecorView。如果DecorView还没有被创建,PhoneWindow
会创建一个新的DecorView实例,并将其设置为窗口的根视图。接着,解析的视图树(即Activity的布局)被添加到DecorView中。
java
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
...
} else {
...
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// 其他初始化代码...
}
}
通过这个流程,DecorView被创建并作为Window的内容视图。它不仅包含了Activity的布局,还可能包含窗口级别的UI元素,如状态栏和导航栏。
将DecorView添加到WindowManager中
WindowManager 是系统服务,它负责管理应用程序窗口的显示。它提供了一些用于管理窗口显示的方法,例如添加、删除、更新窗口等。
在 Activity 的 onResume() 方法之后,会将 DecorView 添加到 WindowManager 中。这将导致 DecorView 显示在屏幕上。
触发点是在,ActivityThread
中的handleResumeActivity()
方法中
ini
@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, boolean shouldSendCompatFakeFocus, String reason) {
...
// 执行Activity onResume
if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
...
if (r.window == null && !a.mFinished && willBeVisible) {
// PhoneWindow
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 添加到WindowManager中,并与wms建立双向通信
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
...
}
这就是为什么我们在onCreate
与onResume
的时候不能直接拿到View的宽高的原因。因为DecorView
添加是在onResume
之后。
绘制
一旦DecorView被创建并设置内容,ViewRootImpl
就负责将DecorView附加到窗口。ViewRootImpl
是一个系统内部使用的类,它连接窗口管理器(WindowManager)和DecorView,处理布局、绘制和事件分发。
在上面将DecorView添加到WindowManager中时,内部是交由WindowManagerGlobal
的addView
处理,在该方法中会创建ViewRootImpl
对象。
scss
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
// 创建ViewRootImpl
if (windowlessSession == null) {
root = new ViewRootImpl(view.getContext(), display);
} else {
root = new ViewRootImpl(view.getContext(), display,
windowlessSession, new WindowlessWindowLayout());
}
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
// 将DecorView交由ViewRootImpl,进行后续的绘制与事件分发等出来。
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
if (viewIndex >= 0) {
removeViewLocked(viewIndex, true);
}
throw e;
}
}
}
在这里我们就能发现,DecorView的绘制是由ViewRootImpl
触发的,而内部其实是调用了它的requestLayout()
方法
scss
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
...
requestLayout()
...
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 主线程判断
checkThread();
mLayoutRequested = true;
// 等待垂直刷新信号量的到来,触发分发绘制流程
scheduleTraversals();
}
}
在requestLayout()
方法中,做了经典的两件事情
- 验证是否是在主线程触发
- 等待刷新,触发后续的绘制流程
总结
最后,总结一下,整个流程主要可以归纳为四步:
- 在
Activity
的attach()
方法里面先创建PhoneWindow
并获取WindowManager
- 在
Activity
的onCreate()
方法里调用setContentView()
会通过调用用PhoneWindow
的installDecor()
来创建DecorView
。 - 在
Activity
的onResume()
方法之后,也就是handleResumeActivity()
方法中,会把DecorView
添加到WindowMangaer
中,并与wms
建立双向通信。最终交个ViewRootImpl
进行后续的绘制流程。 - 在
ViewRootImple
中,验证触发线程,并等到屏幕刷新信号来了,会调用到ViewRootImpl
的performTraversals()
来进行后续的绘制。
推荐
android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。
AwesomeGithub: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。
flutter_github: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。
android-api-analysis: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。
daily_algorithm: 每日一算法,由浅入深,欢迎加入一起共勉。