3.View的绘制流程

news2025/1/14 19:47:02

View是在什么时候显示在屏幕上面的?(如:MainActivity的布局文件activity_main.xml)

  • setContentView最终的结果是将解析的xml文件中的View添加到DecorView中.

那么这个DecorView是什么时候添加到Window(PhoneWindow)的呢?

  • DecorView是在ActivityThread.java的handleResumeActivity()方法中,performResumeActivity()方法后面添加到PhoneWindow中的,具体的添加代码如下:
    wm.addView(decor, l);
    参数分析:
    wmWindowManagerImpl,为什么不是ViewManager呢?
    因为在中途设置了windowManager的值为WindowManagerImpl,代码如下:
    //Activity.java中的attach方法里
    //setWindowManager方法中将一个创建的WindowManagerImpl赋值给了WindowManager
    mWindow.setWindowManager(...);
    mWindowManager = mWindow.getWindowManager();
    
    //Window.java中实现上面setWindowManager(...)方法的代码实现
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) {
        //省略部分代码        
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    
    decorDeorView,lWindowManager.LayoutParams

总结: 也就是说,View显示在屏幕上,其实是在onResume生命周期方法后面,通过WindowManagerDecorView显示在屏幕上的.

View被显示到屏幕上Window的过程?

  • 调用wm.addView(decor, l);方法去添加decorView;
  • 而创建的wm其实是WindowManagerImpl的实例,而WindowManagerImpl这个类中的addView方法;
  • 调用的mGlobal.addView方法,其实是调用的WindowManagerGlobal中的addView方法
  • 调用WindowManagerGlobal中的addView方法后,接着就会调用到ViewRootImpl中的addView方法

总结:
DecorView展示到屏幕PhoneWindow上,实际上是调用的WindowManagerGlobal.java中的addView方法,然而实际的调度是分配给一个个的ViewRootImpl去完成setView方法

分析WindowManagerGlobal.java中的addView方法

  • 创建ViewRootImpl对象实例root
  • 将数据缓存到集合,数据指DecorViewViewRootImplWindowManager.LayoutParams
    • mViews.add(view); //这的mViews集合中缓存的是DecorView
    • mRoots.add(root);//这的mRoots集合中缓存的是ViewRootImpl
    • mParams.add(wparams);//这的mParams集合中缓存的是WindowManager.LayoutParams
  • 调用root.setView(view, wparams, panelParentView, userId);方法

过程中涉及到三个类:

  • WindowManangerImpl
    • 确定View属于哪个屏幕/父窗口
  • WindowMangerGlobal
    • 管理整个App进程的所有的窗口信息,也就是说一个进程对应一个WindowManagerGlobal
  • ViewRootImpl
    • WindowManagerGlobal的实际操作类,操作对应的窗口
    • ViewRootImpl构造函数中的一些变量分析
      • mThread = Thread.currentThread(); 存储创建View的线程,一般是在主线程中创建View
      • mDirty = new Rect(); 脏数据,存储如TextView文字发生改变的信息
      • mAttachInfo = new View.AttachInfo(…) 保存当前窗口的一些信息

分析ViewRootImpl中的setView方法

  • 调用requestLayout()方法请求遍历,里面的流程如下
    • scheduleTraversals();
    • mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    • doTraversal();
    • performTraversals(); 这个方法中就是在绘制View
  • 调用mWindowSession.addToDisplayAsUser
    • 将窗口添加到WMS上面
  • 调用view.assignParent(this);
    • 分配父容器,通过view.getParent方法可以拿到根ViewRootImpl实例root

分析ViewRootImpl.java绘制View的方法performTraversals()

  • measureHierarchy() 预测量,里面最多可执行3次测量操作
  • relayoutWindow() 布局窗口,在WMS中处理mWindowSession.relayout
  • performMeasure() 控件树测量
  • performLayout() 布局
  • performDraw() 绘制

分析measureHierarchy()方法中的3次预测量performMeasure()

  • 期望的窗体宽度desiredWindowWidth大于baseSize进行第一次测量
  • 测量的状态太小,MEASURED_STATE_TOO_SMALL值为1时,调整baseSize大小后进行第二次测量,调整方式如下:
    baseSize = (baseSize+desiredWindowWidth)/2;
  • 如果goodMeasure值为false,进行第三次测量

总结:
所以View的绘制流程中最多可能涉及到4次测量,3次预测量;如果在预测量后,窗体大小可能还会发生变化,windowSizeMayChangetrue时,还需1次测量

setContentViewLayoutInflaterinflat方法,root参数为null时,布局文件中的根控件rootView的宽高属性失效问题?
代码举例:
LayoutInflater.from(this).inflate(R.layout.merge_layout,null,false);
因为在root参数为null的时候,inflat()方法源码中,不会对资源文件.xml的根控件设置LayoutParam,也就是说布局文件最外层控件没有LayoutParam值,所以我们布局文件中的根控件的宽高是不起作用的,从而导致了上面的问题.

面试题:UI刷新只能在主线程中进行吗?
不是的
因为在View绘制的过程中,会调用checkThread()方法,检查创建创建ViewRootImpl的线程和当前线程是否为同一个线程,如果创建ViewRootImpl的线程和当前线程不是同一个线程(创建ViewRootImpl的线程,存放在ViewRootImpl中的变量是mThread),则会报如下错误:
"Only the original thread that created a view hierarchy can touch its views."
如何在子线程中刷新UI呢?

  1. ViewRootImpl还没有创建的时候去刷新UI,这个时候就不会调用ViewRootImpl里面的checkThread方法
  2. 在子线程中创建ViewRootImpl,然后就可以在子线程中刷新UI了;创建WindowManager,然后将我们的布局文件的ViewWindowManager.LayoutParam设置进去,调用WindowManager.addView方法,就可以刷新UI了.

View绘制流程中几个方法分析

  • onMeasure
    • 作用:测量到控件的宽高
    • 流程:ViewRootImpl.performMeasure()->(DecorView)mView.measure()->View.measure()->onMeasure
    • 重点:MeasureSpec测量模式,高2位是测量模式getMode(),低30位是测量的值getSize();测量模式包括了:UNSPECIFIED(wrap_content)、EXACTLY(100dp)和AT_MOST(match_parent)
    • 扩展:View在测量的时候需要加上自己的padding,而ViewGroup在测量的时候需要加上自己的margin
  • onLayout
    • 作用:确定测量的控件布局在屏幕上的坐标位置(left、top、right和bottom),到了这一步onLayout我们才能在Activity中获取到View的宽和高,通过view.getMeasureHeight
    • 流程:ViewRootImpl.performLayout()->(DecorView)host.layout->View.layout()->onLayout
  • onDraw
    • 流程:ViewRootImpl.performDraw()->draw()
      • ->scrollToRectOrFocus()作用举例:输入框获取焦点时,页面整体往上滚动达到输入法在输入框下面的目的
      • ->硬件加速绘制 mAttachInfo.mThreadedRenderer.draw() 硬件加速绘制效果会更好
      • ->软件绘制 drawSoftware()
      • 流程:(DecorView)mView.draw()->View.draw()->View.onDraw()//绘制当前控件
      • ->View.dispatchDraw()//绘制当前控件的子控件

ViewRootImpl流程图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fSsNvpaD-1684050356871)(evernotecid://3A22127D-0425-400F-9B33-054BC6B3328D/appyinxiangcom/40619816/ENResource/p19)]

ViewGroup为什么不执行onDraw()方法?

  • View.draw(canvas); 这里的View是DecorView,这个方法中会同时执行onDraw和dispatchDraw方法
    • -> onDraw(canvas); 注意:这个地方执行的是一个参数的onDraw方法
    • -> dispatchDraw(canvas); 接下来我们来分析这个方法
  • ViewGrpup.dispatchDraw(Canvas canvas) 注意:ViewGroup中只有dispatchDraw方法,没有onDraw方法
    • -> ViewGrpup.drawChild()
    • -> child.draw(canvas, this, drawingTime);注意:ViewGroup.java中调用的是View中的三个参数的draw方法
    • -> View.draw(Canvas canvas, ViewGroup parent, long drawingTime)
    • -> updateDisplayListIfDirty();分析这个方法中执行dispatchDraw和draw方法的逻辑,默认情况下会执行if逻辑判断的代码,从而导致了ViewGroup中不会执行draw方法而只执行dispatchDraw方法
      if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
          //默认情况下进入这个逻辑判断,执行dispatchDraw方法,这是一个递归的过程,会一层一层去遍历去绘制子View
          dispatchDraw(canvas);
          if (mOverlay != null && !mOverlay.isEmpty()) {
              mOverlay.getOverlayView().draw(canvas);
          }
      } else {
          //这里draw方法中就会执行onDraw和dispatchDraw方法,但是默认情况下不会走到这个逻辑判断中来
          draw(canvas);
      }
      

总结:
ViewGroup之所以不会执行onDraw方法,是因为源码中只有dispatchDraw方法,查看该方法的代码逻辑,默认他会走dispatchDraw方法逻辑,而不会走draw方法逻辑(这个方法会同时执行onDraw和dispatch方法),所以ViewGroup不会执行onDraw方法.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/524716.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2-Zookeeper单机版安装

2-Zookeeper单机版安装 本文介绍的是 Linux 系统下 Zookeeper 安装方式 ① 下载 进入官网 https://zookeeper.apache.org/ 点击下载按钮 进入下载页 https://zookeeper.apache.org/releases.html 后选择 最新的稳定版本,如下: 3.7.1 为最新的稳定版本…

号称分割一切的图片分割模型开源了——Segment Anything Meta SAM

头条号:人工智能研究所 微信号:启示AI科技 微信小程序:AI人工智能工具 以前,要解决任何类型的分割问题,有两类方法。第一种是交互式分割,允许分割任何类别的对象,但需要人通过迭代细化掩码来指导。第二种,自动分割,允许分割提前定义的特定对象类别(例如,猫或椅子),…

【计算机系统】指令

leaq指令 一元指令 二元指令 例子 指令addq 指令subq 指令incq 指令subq 移位指令 移位指令用途 特殊运算指令

LitCTF2023 郑州轻工业大学首届网络安全赛 WP 部分

LitCTF2023 郑州轻工业大学首届网络安全赛 WP 部分 前言:Web:我Flag呢?导弹迷踪:Follow me and hack me:PHP是世界上最好的语言!!作业管理系统:Vim yyds:Ping&#xff1a…

Java基础-面向对象总结(2)

这篇文章主要讲解 Java中的 变量方法代码块访问修饰限定符Java 是值传递,还是引用传递?类和对象的生命周期..... 希望给您带来帮助 目录 变量 成员变量与局部变量的区别 静态变量和实例变量的区别?静态方法、实例方法呢? 可以…

数据分析06——Pandas中的数据抽取

1、前言: 在Pandas中进行数据抽取主要有两种方法,一种是loc方法,一种是iloc方法;在获取数据时可以获取的数据有三种形式,一种是Series类型,一种是DataFrame类型,还有一种是直接获取数据值&…

Nginx make报错处理

文章目录 make报错:fatal error:sys/sysctl.h:No such file or directory问题处理 make 报错:error: this statement may fall through [-Werrorimplicit-fallthrough]问题处理 make报错:error: struct crypt_data has no member named curre…

DCGAN--Keras实现

文章目录 一、Keras与tf.keras?二、keras中Model的使用三、使用Keras来实现DCGan1、导入必要的包2.指定模型输入维度:图像尺寸和噪声向量 的长度3、构建生成器4、构造鉴别器5、构建并编译DCGan6、对模型进行训练7、显示生成图像8、运行模型 总结 一、Ker…

力扣sql中等篇练习(二十)

力扣sql中等篇练习(二十) 1 寻找面试候选人 1.1 题目内容 1.1.1 基本题目信息1 1.1.2 基本题目信息2 1.1.3 示例输入输出 a 示例输入 b 示例输出 1.2 示例sql语句 # 分为以下两者情况,分别考虑,然后union进行处理(有可能同时满足,需要去进行去重) # ①该用户在 三场及更多…

软件测试八股文,软件测试常见面试合集【附答案】

PS:加上参考答案有几十万字,答案就没有全部放上来了,高清打印版本超过400多页,评论区留言直接获取 1、你的测试职业发展是什么? 2、你认为测试人员需要具备哪些素质 3、你为什么能够做测试这一行 4、测试的目的是什么? 5、测…

一图看懂 attrs 模块:一个在类定义时可替换 `__init__`, `__eq__`, `__repr__`等方法的样板,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创,转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 attrs 模块:一个在类定义时可替换 __init__, __eq__, __repr__等方法的样板,资料整理笔记(大全) 🧊摘要&#x1…

吴恩达|chatgpt 提示词工程师学习笔记。

目录 一、提示指南 写提示词的2大原则: 模型的限制 二、迭代 三、总结 四、推断 五、转换 六、扩展 七、对话机器人 吴恩达和openai团队共同开发了一款免费的课程,课程是教大家如何更有效地使用prompt来调用chatgpt,整个课程时长1个…

ctfshow周末大挑战2023/5/12

本周周末大挑战用到的函数讲解 parse_url() 作用:解析URL,返回其组成部分 语法: parse_url ( string $url [, int $component -1 ] ) 参数: url:要解析的 URL。无效字符将使用 _ 来替换。 component: …

Sentinel———隔离和降级

FeignClient整合Sentinel SpringCloud中,微服务调用都是通过Feign来实现的,因此做客户端保护必须整合Feign和Sentinel。 第一步 修改OrderService的application.yml文件,开启Feign的Sentinel功能(消费者服务) feig…

算法基础第二章

算法基础第二章 第二章:数据结构1、链表1.1、单链表(写邻接表:存储图和树)1.2、双链表(优化某些问题) 2、栈与队列2.1、栈2.1.1、数组模拟栈2.1.2、单调栈 2.2、队列2.2.1、数组模拟队列2.2.2、滑动窗口(单调队列的使用…

操作系统实验二 进程(线程)同步

前言 实验二相比实验一难度有所提升,首先得先掌握好相应的理论知识(读者-写者问题和消费者-生产者问题),才能在实验中得心应手。任务二的代码编写可以借鉴源码,所以我们要先读懂源码。 1.实验目的 掌握Linux环境下&a…

linux系统状态检测命令

1、ifconfig命令 用于获取网卡配置于状态状态的等信息: ens33:网卡名称 inet:ip地址 ether:网卡物理地址(mac地址) RX、TX:接收数据包与发送数据包的个数及累计流量 我们也可以直接通过网卡名称查对应信息: 2、查看系统版本的…

设计模式 - 工厂 Factory Method Pattern

文章参考来源 一、概念 创建简单的对象直接 new 一个就完事,但对于创建时需要各种配置的复杂对象例如手机,没有工厂的情况下,用户需要自己处理屏幕、摄像头、处理器等配置,这样用户和手机就耦合在一起了。 可以使代码结构清晰&a…

【人工智能】— 贝叶斯网络

【人工智能】— 贝叶斯网络 频率学派 vs. 贝叶斯学派贝叶斯学派Probability(概率):独立性/条件独立性:Probability Theory(概率论):Graphical models (概率图模型)什么是图模型(Grap…