金三银四,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

参考

相关推荐
雨白10 分钟前
OkHttpClient 核心配置详解
android·okhttp
淡淡的香烟12 分钟前
Android auncher3实现简单的负一屏功能
android
一块plus34 分钟前
创造 Solidity、提出 Web3 的他回来了!Gavin Wood 这次将带领波卡走向何处?
javascript·后端·面试
RabbitYao1 小时前
Android 项目 通过 AndroidStringsTool 更新多语言词条
android·python
RabbitYao1 小时前
使用 Gemini 及 Python 更新 Android 多语言 Excel 文件
android·python
Aphasia3111 小时前
性能优化之重绘和重排
前端·面试
纽马约1 小时前
Android RxJava的使用
android
掘金安东尼2 小时前
代理式AI,从被动响应到主动执行的技术演进
面试
没有了遇见2 小时前
Kotlin高级用法之<扩展函数/属性>
android·kotlin
安卓开发者2 小时前
Android中使用RxJava实现网络请求与缓存策略
android·网络·rxjava