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是一样的。
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 的一个子类,它担任办理运用程序窗口的外观和行为。
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的用户界面布局。
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。
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
Window的setContentView
办法内部,进一步骤用了PhoneWindow
的setContentView
实现。在这个办法中,会创立或找到DecorView,然后将解析的视图树增加到DecorView中。
@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中。
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()
办法中
@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
目标。
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()
办法
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: 每日一算法,由浅入深,欢迎参加一起共勉。