用通俗易懂的方式拆解这篇Android View绘制流程的文章,并补充关键细节:
一、View的诞生故事(比喻版)
想象你要装修房子:
-
开发商交房(Activity创建)时,先安装门窗框架(PhoneWindow和DecorView)
-
你设计户型图(setContentView)时:
- 开发商提供毛坯房(DecorView)
- 划分出客厅区(标题栏)和装修区(android.R.id.content)
-
正式使用前要经过三道工序:
- 量房阶段(measure):确定每个房间尺寸
- 摆家具阶段(layout):确定每个家具位置
- 粉刷阶段(draw):墙面装饰上色
二、核心角色关系
- Activity是房东:负责整体事务协调
- Window是物业管家:管理房屋结构
- View是装修工人:具体执行每个细节
- ViewRootImpl是工程监理:协调测量、施工顺序
- WMS是城市规划局:最终审批房屋建设
三、绘制流程关键细节
-
首次显示必经之路:
onResume执行后 → ViewRootImpl开始工作 → measure → layout → draw
这就是为什么首次onResume时拿不到View尺寸 -
测量阶段的秘密会议:
- 最少测量2次(常规布局)
- 最多4次(弹窗等特殊布局)
- 使用MeasureSpec作为"施工图纸",包含尺寸要求和限制模式
-
更新UI的三种姿势:
- invalidate():局部补漆(只重绘)
- requestLayout():重新量房+摆家具(触发measure+layout)
- postInvalidate():让工头代叫补漆工(子线程安全重绘)
四、高频面试题精要版
- 为什么onResume拿不到宽高?
就像装修还没开始量房,房东(Activity)当然不知道房间尺寸 - 子线程不能更新UI?
其实在监理(ViewRootImpl)上岗前可以修改,但就像没有安全措施的施工,容易引发事故(线程安全问题) - ViewGroup为何不触发onDraw?
默认认为它是"透明容器",若要绘制需要:
➢ 主动声明setWillNotDraw(false)
➢ 添加背景/前景等可视化元素 - getWidth和getMeasuredWidth区别?
前者是实际入住后的房间尺寸,后者是设计图纸上的理论尺寸
五、技术冷知识
-
神秘的VSYNC信号:
像装修队的开工哨声,保证每次绘制都等屏幕刷新信号,避免画面撕裂 -
DecorView的双层结构:
- 外层是系统装饰(状态栏/导航栏)
- 内层是开发者自定义内容
-
PhoneWindow的独特身份:
安卓系统中唯一的Window实现类,如同城市中所有建筑必须使用统一建筑标准
理解这些原理后,自定义View就像掌握了装修核心技术,能精准控制每个绘制细节,解决各种UI异常问题。实际开发中遇到布局问题时,可沿着"测量→布局→绘制"的线索逐步排查,如同检查装修工程的每个验收环节。