总体而言,Android 的绘制基本可以分为
ViewrootImpl
,在这姑且先将它理解为 Activity
按顺序讲的话就得先讲 View 是怎么绘制到屏幕上的,或者 Activity 怎么加载布局的
在整个 Activity
的生命周期中,setContentView
是在 onCreate
中调用的,它实现了对资源文件的解析,完成了 xml
文件到 View
的转化。
那么 View 真正开始绘制是在哪个生命周期呢?
答案是 onResume
结束后
onResume
之后,从 Activity 中的 Window 实例中获取 Decorview
activity
中 windowmanager
的 addView
方法,将 decorView
传入到 ViewRootImpl
的 setView
方法中ViewRootImpl.setView()
来完成 View
的绘制问题又来了,什么是
ViewRootImpl
、decorView
呢?setView 到底有什么魔法,为什么他就能完成 View 的绘制工作呢?
从结构上来看,ViewRootImpl
和 ViewGroup
其实是一种东西
/**
* The top of a view hierarchy, implementing the needed protocol between View and the WindowManager.
* This is for the most part an internal implementation detail of {@link WindowManagerImpl}.
* {@hide}
*/
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
...
}
//A ViewGroup is a special view that can contain other views (called children.)
//The view group is the base class for layouts and views containers.
//This class also defines the LayoutParams class which serves as the base class for layouts parameters.
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
...
}
相同点
ViewParent
。ViewParent
是一个接口,定义了一些父 View
的基本行为,比如 requestlayout
,getparent
等。不同点
ViewRootImpl
并不会像 ViewGroup
一样被真正绘制在屏幕上Activity
中,它是专门用来绘制 DecorView
的,核心方法是 setView
Activity
并不负责视图控制,它只是控制生命周期和处理事件。真正控制视图的是 Window
。一个 Activity
包含了一个 Window
,Window
才是真正代表一个窗口。Activity
就像一个控制器,统筹视图的添加与显示,以及通过其他回调方法,来与 Window
、以及 View
进行交互。Window
是一个抽象类,实际在 Activity
中持有的是其子类 PhoneWindow
。PhoneWindow
中有个内部类DecorView
,通过创建 DecorView
来加载 Activity
中设置的布局 R.layout.activity_main
Window
是视图的承载器,内部持有一个 DecorView
,而这个 DecorView
才是 view
的根布局。Window
通过 WindowManager
将 DecorView
加载其中,并将 DecorView
交给 ViewRootimpl
,进行视图绘制以及其他交互/**
* 顶级窗口外观和行为策略的抽象基类。An instance of this class 应用作 top-level view 添加到窗口管理器。
* 它提供了标准的 UI 策略,例如背景、标题区域、默认键处理等。
*
* 这个抽象类的唯一现有实现是 PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
...
/**
* Retrieve the top-level window decor view (containing the standard
* window frame/decorations and the client's content inside of that), which
* can be added as a window to the window manager.
*
* Note that calling this function for the first time "locks in"
* various window characteristics as described in
* {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.
*
* @return Returns the top-level window decor view.
*/
public abstract @NonNull View getDecorView();
}
DecorView
是 FrameLayout
的子类,它可以被认为是 Android
视图树的根节点视图。
DecorView
作为顶级 View
,一般情况下它内部包含一个竖直方向的 LinearLayout
,在这个 LinearLayout
里面有上下三个部分,上面是个 ViewStub
,延迟加载的视图(应该是设置ActionBar
,根据 Theme
设置),中间的是标题栏(根据Theme设置,有的布局没有),下面的是内容栏。
具体情况和Android版本及主体有关,以其中一个布局为例,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub
android:id="@+id/action_mode_bar_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:theme="?attr/actionBarTheme" />
<FrameLayout
style="?android:attr/windowTitleBackgroundStyle"
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize">
<TextView
android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical" />
FrameLayout>
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foreground="?android:attr/windowContentOverlay"
android:foregroundGravity="fill_horizontal|top" />
LinearLayout>
在 Activity
中通过 setContentView
所设置的布局文件其实就是被加到内容栏之中的,成为其唯一子 View
,就是上面的 id
为 content
的 FrameLayout
中,在代码中可以通过 content
来得到对应加载的布局。
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
ViewGroup rootView = (ViewGroup) content.getChildAt(0);
window
是 activity
的一个成员变量,window
和 View
是“显示器”和“显示内容”的关系
Activity
的顶级 View
– DecorView
显示器与现实内容
的区别类似的问题,其实问的基本是一个东西。
activity
的 setContentView
方法实际上是就是交给 phonewindow
去做的。window
和 View
的关系可以类比为显示器和显示的内容。
上面分析到这:
onResume
之后,从 Activity 中的 Window 实例中获取 Decorview
activity
中 windowmanager
的 addView
方法,将 decorView
传入到 ViewRootImpl
的 setView
方法中ViewRootImpl.setView()
来完成 View
的绘制问题又来了,setView 到底有什么魔法,为什么他就能完成 View 的绘制工作呢?
简单来说 setView
做了三件事
看到这里,ViewRootImpl
的绘制基本就完成了。其实这也是面试官希望听到的内容。考察的是面试者对 View
绘制体系的理解。
后续 ViewGroup
和 View
的绘制其实是 performTraversals
对整个 ViewTree
的绘制。他们的关系可以用下面这张图表示
View.post 会判断当前 View 是否已经被添加到 window 上。如果添加了则立即执行 runnable,如果没有被添加则先放到一个队列中存储起来,等添加到 window 上时再执行。
而 View 被测量完成后才会 attachToWindow。所以当 post 的 runnable 执行时,View 已经绘制完成了。
前面我们说到,ViewRootImpl 作为顶级 View 负责 View 的绘制。所以简单来说,requestlayout 和 invaliate 最终都会向上回溯调用到 ViewRootImpl 的 postTranversals 方法来绘制 View。
不同的是 requestlayout 会绘制 View 的 measure,layout 和 draw 过程。invaliate 因为只添加了绘制 draw 的标志位,只会绘制 draw 过程。