稀土掘金 稀土掘金

View工作原理 | ViewRoot和DecorView

前言

说到View的原理,我相信很多开发者都能脱口而出什么onMeasure、onLayout、onDraw等回调方法,但是这个View的绘制流程是哪里开始的、是谁负责绘制View的这些问题可能没有仔细思考过,在说具体的原理之前,必须要搞清楚一个基础知识,即ViewRoot和DecorView,这样我们才可以知其然知其所以然。

正文

为什么要理解除了View绘制流程之外的一些知识呢?因为这些知识可以让我们更加清晰地认识Android体系架构,让我们更容易把知识点给串联起来。

以Activity能展示出界面来看,这里面涉及了Activity的启动流程,其中的重点是Window机制。关于Window机制,在Android中非常重要,可以仔细阅读下面文章:

juejin.cn/post/711900…

这里我们回顾一些文章中重要结论:

  1. Android中所有视图包括Activity、Dialog和Toast都是通过Window为载体来显示,而Window是一个抽象概念,这里我们可以把一个View树看成是一个Window
  2. Window的真实创建、操作的类是WindowManagerService,而和WMS进行IPC的类就是ViewRootImpl类,所以ViewRootImpl是Window和View之间的桥梁

上面结论只是很少一部分本篇文章用得到的结论,我们下面就来看看ViewRootImpl的工作流程。

整体架构

关于Activity、PhoneWindow、DecorView这3者关系如下图:

image.png

这个示意图其实有点问题,看过上面链接中的文章会知道,PhoneWindow其实是一个Window工具类,它并不是一个真正的Window。

但是这里不影响我们理解Activity的整体架构。

ViewRootImpl

下图是前面文章中的图:

image.png

通过阅读Activity的Window的创建过程,我们会知道一个View树即DecorView会对应一个Window,同时也对应一个ViewRootImpl,而ViewRootImpl一边负责绘制View,一边通过Session和WMS进行IPC通信。这里除了绘制的部分,其他内容在上面Android Window详解中都有介绍,本篇文章就来看看ViewRootImpl的绘制。

ViewRootImpl绘制过程

Activity的启动流程中,会调用ActivityThread类中的handleResumeActivity方法,即Activity会回调onResume方法,在该方法中:

public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, String reason) {
    ...
    if (r.window == null && !a.mFinished && willBeVisible) {
        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;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;

        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            } else {
                ...
            }
        }
        ...
      
    }
    ...
}

我们会发现它会取出Activity的DecorView,然后通过WindowManager.addView方法把DecorView这个View树配合LayoutParams给添加到Window中,方法如下:

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
    ...
    ViewRootImpl root;
    View panelParentView = null;
    ...
        root = new ViewRootImpl(view.getContext(), display);
        ...
        try {
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {
            ...
        }
    }
}

这时WindowManager逻辑实现类WindowManagerGlobal中的代码,我们发现在这个时候会创建出我们期望的ViewRootImpl类,同时调用其setView方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    synchronized (this) {
            ...
            //绘制View树
            requestLayout()
            ...
            try {
                //IPC通信
                res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), userId,
                        mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
                        mTempControls);
               ...
    }
}

上述代码比较多,但是核心就是requestLayout方法与和WMS进行IPC通信的部分,这里也更验证了我们结论:ViewRootImpl是View和Window的桥梁,一边和WMS通信,一边绘制我们的View树。

requestLayout

该方法我相信很多开发者再熟悉不过了,我们可以通过任意一个View来调用该方法,而该方法是定义在ViewParent接口中的,ViewRootImpl真是实现该接口的地方。

我们来看一下该方法定义:

/**
 * Called when something has changed which has invalidated the layout of a
 * child of this view parent. This will schedule a layout pass of the view
 * tree.
 */
public void requestLayout();

当一个View树的子View的布局失效时调用,该方法会对整个View树的布局做重新处理

所以该方法的调用时机我们也要把握清楚,当自定义View时,其布局改变时,我们才有必要调用该方法进行重新绘制。

话不多说,我们看看ViewRootImpl中该方法的实现:

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

这里会调用scheduleTraversals()方法,在该方法中会调用performTravsals,该方法特别长,不过思路很清晰,它会调用3个方法:

image.png

如上图所示,performTravsals会依次调用performMeasureperfromLayoutperformDraw三个方法,这3个方法分别完成顶级View即DecorView,也就是一个FrameLayout的测量、布局和绘制三大流程。

  • performMeasure中会调用View(DecorView)的measure方法,在measure方法中又会调用onMeasure方法,在onMeasure(这时已经是ViewGroup,即FrameLayout重写)方法中会对所有子元素进行measure过程,这时measure流程就从父容器传递到了子元素。

这里要明白ViewGroup也是View,所以必须当DecorView是一个ViewGoup时,必须要重写onMeasure,然后在onMeasure进行子View的measure,这样只有当子View全部measure完时,才能确定ViewGroup的测量宽高。

  • performLayout类似,它也是先调用DecorView的layout方法,在View的layout方法中会调用onLayout方法,这时需要DecorView(它是ViewGroup)重写onLayout方法,同样在ViewGoup的onLayout中进行子View的布局,这样只有子View全部layout完,才能确定ViewGoup的位置。

  • performDraw有一点不一样,它也是先调用View的draw方法,但是在draw方法中,会分为如下几个步骤:

  1. 绘制背景,调用drawBackground方法;
  2. 绘制View自己内容,调用onDraw方法;
  3. 绘制子View,调用dispatchDraw方法;
  4. 绘制前景图或者滚动条,调用onDrawForeground方法;

当ViewRootImpl调用DecorView的draw方法时,它会通过onDraw先绘制自己,然后DecorView因为是ViewGoup,它会重写dispatchDraw方法,在该方法中进行子View的绘制即可。

这里是否有点奇怪,为什么绘制的流程API设计和测量和布局不一样呢?

原因是因为测量和布局必须要等待子View全部测量和布局完,才能在onMeasure和onLayout中设置值;但是绘制就不一样了,在进行绘制的时候,每个View的大小和位置都确定了,我就可以按照绘制背景、绘制内容、绘制子View和绘制前景这个一般的顺序来完成,至于为什么要这么多步骤,因为绘制是有遮盖效果的,所以这样绘制让内容更丰富。

通过查看了不少ViewGoup实现类,比如LinearLayout和RecyclerView等,它的dispatchDraw都是使用ViewGoup的默认实现,这说明其实该步骤我们不必太多干涉,我们在自定义ViewGoup时其实只需要安排好子View的位置和大小即可

总结

在正式开启View工作原理前,这篇文章还是非常重要的,可以让我们在心里有个大致的思路,至少知道了View的绘制流程从哪里开始,调用了哪些方法以及如何传递。至于每个流程的细节,我们后面文章再仔细分析。

笔者能力有限,欢迎大家评论、讨论。

玻璃钢生产厂家昆明水果玻璃钢雕塑生产厂家辽阳人物玻璃钢雕塑生产厂家玻璃钢雕塑样本哪里有玻璃钢雕塑加工厂玻璃钢仿生雕塑山城玻璃钢雕塑定制公园水景玻璃钢景观雕塑加工绥化玻璃钢雕塑厂家茂名玻璃钢人物雕塑价格合理玻璃钢装饰雕塑售价晋城玻璃钢雕塑制作厂家青岛商场美陈销售企业广州玻璃钢雕塑租赁宜宾玻璃钢花盆花器厦门商场开业美陈吉林户外玻璃钢雕塑市场四川玻璃钢仿真水果雕塑定制中式玻璃钢雕塑一般多少钱玻璃钢雕塑动物雕塑鹤壁玻璃钢成品雕塑河南节庆商场美陈报价青岛玻璃钢雕塑订购玻璃钢雕塑生产厂家树脂玻璃钢花盆厂家松原商场美陈兰州玻璃钢景观雕塑订制伊春卡通玻璃钢雕塑定做惠州玻璃钢仿真植物水果雕塑宜宾玻璃钢仿铜雕塑厂夏季商场中厅美陈香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化