Android View 绘制流程

在这里插入图片描述

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
  • 调用 activitywindowmanageraddView 方法,将 decorView 传入到 ViewRootImplsetView 方法中
  • 通过 ViewRootImpl.setView() 来完成 View 的绘制

问题又来了,什么是 ViewRootImpldecorView 呢?setView 到底有什么魔法,为什么他就能完成 View 的绘制工作呢?

2.1 什么是 ViewRootImpl

从结构上来看,ViewRootImplViewGroup 其实是一种东西

/**
 * 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 的基本行为,比如 requestlayoutgetparent 等。
  • 不同点

    • ViewRootImpl 并不会像 ViewGroup 一样被真正绘制在屏幕上
    • Activity 中,它是专门用来绘制 DecorView 的,核心方法是 setView

2.2 什么是 Activity?从视图角度分析

  • Activity 并不负责视图控制,它只是控制生命周期和处理事件。真正控制视图的是 Window。一个 Activity 包含了一个 WindowWindow才是真正代表一个窗口。
  • Activity就像一个控制器,统筹视图的添加与显示,以及通过其他回调方法,来与 Window、以及 View 进行交互。

2.3 什么是Window?

  • Window 是一个抽象类,实际在 Activity 中持有的是其子类 PhoneWindowPhoneWindow 中有个内部类DecorView,通过创建 DecorView 来加载 Activity 中设置的布局 R.layout.activity_main
  • Window 是视图的承载器,内部持有一个 DecorView,而这个 DecorView才是 view 的根布局。
  • Window 通过 WindowManagerDecorView 加载其中,并将 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?

DecorViewFrameLayout 的子类,它可以被认为是 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,就是上面的 idcontentFrameLayout 中,在代码中可以通过 content 来得到对应加载的布局。

ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
ViewGroup rootView = (ViewGroup) content.getChildAt(0);

2.5 Activity,window,View 三者之间的关系是什么?

windowactivity 的一个成员变量,windowView 是“显示器”和“显示内容”的关系

  • Window 抽象类,PhoneWindow 唯一实现类,用于加载 Activity 的顶级 ViewDecorView
  • Window --> 顶级窗口外观和行为策略的抽象基类。
    • An instance of this class 应用作 top-level view 添加到窗口管理器。
    • 它提供了标准的 UI 策略,例如背景、标题区域、默认键处理等。
    • 相对 view 来说就是 显示器与现实内容 的区别
  • 一个 Activity 对应一个 Window 也就是 PhoneWindow,一个 PhoneWindow 持有一个 DecorView 的实例,DecorView 本身是一个 FrameLayout

2.6 Activity、PhoneWindow、DecorView 关系图

类似的问题,其实问的基本是一个东西。

activitysetContentView 方法实际上是就是交给 phonewindow 去做的。windowView 的关系可以类比为显示器和显示的内容。

在这里插入图片描述

3. 再来看看 普通的View 是怎么绘制的

在这里插入图片描述

上面分析到这:

  • onResume 之后,从 Activity 中的 Window 实例中获取 Decorview
  • 调用 activitywindowmanageraddView 方法,将 decorView 传入到 ViewRootImplsetView 方法中
  • 通过 ViewRootImpl.setView() 来完成 View 的绘制

问题又来了,setView 到底有什么魔法,为什么他就能完成 View 的绘制工作呢?

3.1 ViewRootImpl 是如何绘制 View 的

在这里插入图片描述

简单来说 setView 做了三件事

  • ① 检查绘制的线程是不是创建 View 的线程。这里可以引申出一个问题,View 的绘制必须在主线程吗?
  • ② 通过内存屏障保证绘制 View 的任务是最优先的
  • ③ 调用 performTraversals 完成 measure,layout,draw 的绘制

看到这里,ViewRootImpl 的绘制基本就完成了。其实这也是面试官希望听到的内容。考察的是面试者对 View 绘制体系的理解。

后续 ViewGroupView 的绘制其实是 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 过程。

参考链接

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值