金三银四,Android View的绘制流程看这篇就够了

View是如何显示出来的

  1. 调用Activity的attach方法,该方法会创建PhoneWindow对象
  2. 在onCreate的setContentView方法中,先会通过PhoneWindow的getDecorView方法获取DecorView,如果没有则创建DecorView对象
  3. 获取DecorView后,创建subDecor,里面包括标题和内容两部分,其中内容设置id为android.R.id.content。DecorView将添加subDecor
  4. 通过LayoutInflater方法加载我们设置的xml布局,移除全部id为android.R.id.content的ViewGroup中的所有子View,最后调用addView添加我们的View
  5. 在 handleResumeActivity 方法调用完 activity 的 onResume 方法后,会通过PhoneWindow获取WindowManagerImpl来调用addView方法,其内部会调用WindowManagerGlobal.addView方法,最后调到ViewRootImpl的 setView 方法。
  6. 在setView方法中调用 requestLayout 方法,其内部先会检查线程,然后调用scheduleTraversals方法
  7. scheduleTraversals方法是通过Handle实现,目的是确保每一次绘制都在vsync脉冲信号发出时调用 doTraversal 方法
  8. doTraversal 方法会调用 performTraversals,先调用DecorView的dispatchAttachedToWindow方法,分发onAttachedToWindow 事件;然后执行 measure、layout、draw 流程
  9. 在setView方法中将当前PhoneWindow添加到WindowManagerService(WMS)上

了解上面的流程,一些常见的面试题就很容易回答了。常见面试题如下:

面试题1:在onResume中是否可以获取View的宽高

第一次启动activity时不能在onResume中获取。根据上面View的显示流程,view的measure是在onResume方法后执行的。如果是从其他的activity回到当前activity而执行的onResume方法,那么就能够获取到View的宽高

面试题2:为什么子线程不能更新UI?

可以在子线程更新UI。根据上面view的显示流程,Android是在ViewRootImpl的requestLayout方法中检查线程的,如果在ViewRootImpl创建之前更新UI是可以的。之所以google要求在主线程更新UI,是因为如果允许多线程更新UI,但是UI的接口没有加锁的,一旦多线程抢占了资源,那么界面更新将会混乱。如果加了锁则会影响提高界面的流畅性。

面试题3:Activity,Window,View三者的联系和区别

  • Activity是安卓四大组件之一,负责界面、交互和业务逻辑处理。其内部包含一个PhoneWindow,负责处理View的相关逻辑

  • Window目前实现类只有PhoneWindow,Window是View的载体,负责管理View

  • View则是放在Window容器的元素

面试题4:View的绘制流程是从Activity的哪个生命周期方法开始执行的

根据上面view的显示流程,可以知道view的绘制流程是在activity的onResume方法后执行的

View的绘制流程

  1. View的绘制从 ViewRootImpl 类的 performTraversals() 方法开始
  2. 调用 measureHierarchy 方法开始预测量阶段
  3. 在预测量阶段,会根据WindowManager.LayoutParams和屏幕宽高来确定DecorView的MeasureSpec。其中WindowManager.LayoutParams的width、height默认为MATCH_PARENT;当为弹窗时,才会设置为WRAP_CONTENT
  4. 执行 performMeasure 方法开始预测量,内部调用 DecorView 的 measure,然后调用它的 onMeasure 方法,由于DecorView是FrameLayout,其内部会先调用 measureChildWidthMargins 方法,它会根据当前View的MeasureSpec和子View的LayoutParams(xml解析时被创建)来计算出子View的MeasureSpec。如果是child是View,则会先后调用它的measure、onMeasure,最后调用setMeasureDimension 方法保存自己的尺寸;如果child是ViewGroup,则先测量子View的尺寸,最后调用setMeasureDimension 方法保存自己的尺寸。
  5. 测量阶段,重复步骤4
  6. 调用 performLayout 方法,内部调用 DecorView 的 layout方法,内部先调用 setFrame 方法设置自己的大小和位置,然后调用 onLayout 方法,其内部调用子View的layout方法完成布局。
  7. 调用 performDraw 方法,内部调用 DecorView 的 draw方法,draw方法内部会先调drawBackground(Canvas)方法绘制背景;后调用onDraw(Canvas)绘制主体;再调用 dispatchDraw(Canvas) 方法绘制子View;最后调用 onDrawForeground(Canvas) 方法绘制滑动条和前景。

面试题5:View绘制流程中,onMeasure方法将执行几次

最少执行2次,最多执行4次。如果界面为弹窗,那么在activity的setContentView方法中会设置WindowManager.LayoutParams的width为WRAP_CONTENT,这时在预测量阶段会有协商的过程,这时最多会测量3次。默认情况下预测量阶段只会测量一次。因此onMeasure方法最少执行2次,最多执行4次。

面试题6:getMeasuredWidth和getWidth的区别

getMeasureWidth方法实现是返回 MeasureSpec的大小,因此它在setMeasureDemention后设置了MeasureSpec后才有值;而getWidth()实现是(mRight - mLeft),它是在layout中setFrame()方法中赋值。

面试题7:ViewGroup的onDraw什么情况下执行

ViewGroup的onDraw()方法默认是不执行的,若要ViewGroup的onDraw()执行,只需要setWillNotDraw(false)、设置背景、设置前景、设置焦点高亮,4个选项其中一项满足即可。

面试题8:说一说你对MeasureSpec的理解

MeasureSpec 代表一个 32 位 int 值,高 2 位代表测量模式 SpecMode,低 30 位代表规格大小 SpecSize,MeasureSpec 通过把 SpecMode 和 SpecSize 打包成一个 int 值避免过多的对象内存分配

SpecMode有三种模式

  • UNSPECIFIED:不指定测量模式, 父视图没有限制子视图的大小,子视图可以是任何想要的尺寸,通常用于系统内部,应用开发中很少用到
  • EXACTLY:精确测量模式,对应于match_parent或具体数值,这种模式下View的测量值就是SpecSize的值
  • AT_MOST:最大值测量模式,对于wrap_content,这种模式下子View的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。

对于普通的View,MeasureSpec的计算规则如下。它的MeasureSpec是由父视图的MeasureSpec和其自身的LayoutParams共同决定的。特殊的是,对于DecorView而言,它的MeasureSpec由屏幕宽高和WindowManger.LayoutParams来决定的。

面试题9:invalidate、postInvalidate、requestLayout 的区别

  • invalidate():在UI线程调用。view的invalidate会导致当前view被重绘,由于mLayoutRequested为false,不会导致onMeasure和onLayout被调用,而OnDraw会被调用
  • postInvalidate():可以在子线程调用,内部是通过 ViewRootImpl 的handler切换到UI线程,最终执行 invalidate()
  • requestLayout():view的requestLayout会直接递归调用父窗口的requestLayout,直到ViewRootImpl,然后触发peformTraversals,由于mLayoutRequested为true,会导致onMeasure和onLayout被调用,不一定会触发OnDraw;requestLayout触发onDraw可能是因为在在layout过程中发现l,t,r,b和以前不一样,那就会触发一次invalidate,所以触发了onDraw

参考

相关推荐
工业甲酰苯胺1 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做3431 小时前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee2 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯3 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
uzong4 小时前
7 年 Java 后端,面试过程踩过的坑,我就不藏着了
java·后端·面试
Jinkey4 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!6 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟7 小时前
Android音频采集
android·音视频
小白也想学C8 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程8 小时前
初级数据结构——树
android·java·数据结构