1. 讲一讲 View 的绘制流程
- 简单的可以说,如 measure,layout,draw 分别对应测量,布局,绘制三个过程
- 深一些可以引申出 Handler,同步屏障,View 的事件传递,甚至 activity 的启动过程
总体而言,Android 的绘制基本可以分为
- 顶级 View 的绘制 --> 也就是
ViewrootImpl
,在这姑且先将它理解为Activity
- Viewgroup 的绘制
- View 的绘制
按顺序讲的话就得先讲 View 是怎么绘制到屏幕上的,或者 Activity 怎么加载布局的
2. Android 中 xml 布局怎么显示到屏幕上的?
在整个 Activity
的生命周期中,setContentView
是在 onCreate
中调用的,它实现了对资源文件的解析,完成了 xml
文件到 View
的转化。
那么 View 真正开始绘制是在哪个生命周期呢?
答案是 onResume
结束后
几个关键点:
onResume
之后,从 Activity 中的 Window 实例中获取Decorview
- 调用
activity
中windowmanager
的addView
方法,将decorView
传入到ViewRootImpl
的setView
方法中 - 通过
ViewRootImpl.setView()
来完成View
的绘制
问题又来了,什么是
ViewRootImpl
、decorView
呢?setView 到底有什么魔法,为什么他就能完成 View 的绘制工作呢?
2.1 什么是 ViewRootImpl
从结构上来看,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
2.2 什么是 Activity?从视图角度分析
Activity
并不负责视图控制,它只是控制生命周期和处理事件。真正控制视图的是Window
。一个Activity
包含了一个Window
,Window
才是真正代表一个窗口。Activity
就像一个控制器,统筹视图的添加与显示,以及通过其他回调方法,来与Window
、以及View
进行交互。
2.3 什么是Window?
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 策略,例如背景、标题区域、默认键处理等。
*
* <p>这个抽象类的唯一现有实现是 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.
*
* <p><em>Note that calling this function for the first time "locks in"
* various window characteristics as described in
* {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p>
*
* @return Returns the top-level window decor view.
*/
public abstract @NonNull View getDecorView();
}
2.4 什么是DecorView?
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">
<!-- Popout bar for action modes -->
<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);
2.5 Activity,window,View 三者之间的关系是什么?
window
是 activity
的一个成员变量,window
和 View
是“显示器”和“显示内容”的关系
- Window 抽象类,PhoneWindow 唯一实现类,用于加载
Activity
的顶级View
–DecorView
- Window --> 顶级窗口外观和行为策略的抽象基类。
- An instance of this class 应用作 top-level view 添加到窗口管理器。
- 它提供了标准的 UI 策略,例如背景、标题区域、默认键处理等。
- 相对 view 来说就是
显示器与现实内容
的区别
- 一个 Activity 对应一个 Window 也就是 PhoneWindow,一个 PhoneWindow 持有一个 DecorView 的实例,DecorView 本身是一个 FrameLayout
2.6 Activity、PhoneWindow、DecorView 关系图
类似的问题,其实问的基本是一个东西。
activity
的 setContentView
方法实际上是就是交给 phonewindow
去做的。window
和 View
的关系可以类比为显示器和显示的内容。
3. 再来看看 普通的View 是怎么绘制的
上面分析到这:
onResume
之后,从 Activity 中的 Window 实例中获取Decorview
- 调用
activity
中windowmanager
的addView
方法,将decorView
传入到ViewRootImpl
的setView
方法中 - 通过
ViewRootImpl.setView()
来完成View
的绘制
问题又来了,setView 到底有什么魔法,为什么他就能完成 View 的绘制工作呢?
3.1 ViewRootImpl 是如何绘制 View 的
简单来说 setView
做了三件事
- ① 检查绘制的线程是不是创建 View 的线程。这里可以引申出一个问题,View 的绘制必须在主线程吗?
- ② 通过内存屏障保证绘制 View 的任务是最优先的
- ③ 调用 performTraversals 完成 measure,layout,draw 的绘制
看到这里,ViewRootImpl
的绘制基本就完成了。其实这也是面试官希望听到的内容。考察的是面试者对 View
绘制体系的理解。
后续 ViewGroup
和 View
的绘制其实是 performTraversals
对整个 ViewTree
的绘制。他们的关系可以用下面这张图表示
4. 延申
4.1 为什么我在 onCreate 中调用 View.post 方法可以得到 View 的宽高呢
View.post 会判断当前 View 是否已经被添加到 window 上。如果添加了则立即执行 runnable,如果没有被添加则先放到一个队列中存储起来,等添加到 window 上时再执行。
而 View 被测量完成后才会 attachToWindow。所以当 post 的 runnable 执行时,View 已经绘制完成了。
4.2 invaliate 和 requestlayout 方法的区别
前面我们说到,ViewRootImpl 作为顶级 View 负责 View 的绘制。所以简单来说,requestlayout 和 invaliate 最终都会向上回溯调用到 ViewRootImpl 的 postTranversals 方法来绘制 View。
不同的是 requestlayout 会绘制 View 的 measure,layout 和 draw 过程。invaliate 因为只添加了绘制 draw 的标志位,只会绘制 draw 过程。