一、View绘制流程
Android绘制流程详尽版
AndroidView绘制精简版
ViewRootImpl和绘制流程
Android View绘制流程
主流程
Android 渲染流程是面试中的高频考点,核心围绕“应用层绘制 → 系统层合成 → 硬件显示”三个阶段展开,涉及多个关键组件和机制。以下是结构化的面试级解析:
一、核心目标
Android 渲染的核心是将应用的 UI 内容(文字、图像、布局等)高效转换为屏幕上的像素,确保流畅的视觉体验(理想帧率 60 FPS,即每帧渲染耗时 ≤ 16.6ms)。
二、完整渲染流程(分阶段解析)
1. 应用层:UI 绘制(Measure → Layout → Draw)
应用层通过 View
体系完成 UI 内容的计算与绘制,核心是 MLD 流程:
Measure(测量):
递归计算每个 View 的宽高(onMeasure()
),根据父 View 的约束确定子 View 的尺寸范围(如match_parent
/wrap_content
)。Layout(布局):
确定每个 View 在父容器中的位置(onLayout()
),通过坐标(left/top/right/bottom)定位。-
Draw(绘制):
将 View 的视觉内容绘制到画布(onDraw(Canvas)
),包括:- 绘制背景(
drawBackground()
); - 绘制自身内容(如
Canvas.drawText()
/drawBitmap()
); - 绘制子 View(递归调用子 View 的
draw()
); - 绘制装饰(如滚动条、前景)。
关键机制:
- Canvas:封装绘制指令的抽象类,提供绘制图形、文本、图像的 API。
- 硬件加速:Android 3.0+ 支持,将绘制指令转换为 GPU 可执行的操作(如 OpenGL ES 调用),替代 CPU 软件绘制,提升效率。
-
DisplayList:硬件加速下,绘制指令会被缓存为
DisplayList
(记录绘制操作序列),避免重复计算(如 View 未变化时直接复用)。
- 绘制背景(
2. 系统层:合成与提交(Surface → SurfaceFlinger)
应用层绘制完成后,内容需提交给系统服务处理:
-
Surface:每个窗口(如 Activity、Dialog)对应一个
Surface
,是应用与系统交互的“绘图表面”,内部包含图形缓冲区(GraphicBuffer
),用于存储渲染后的像素数据。 -
提交绘制结果:
应用通过Surface.lockCanvas()
获取缓冲区,绘制完成后调用Surface.unlockCanvasAndPost()
提交,缓冲区被传递给 SurfaceFlinger(系统核心服务)。 -
SurfaceFlinger 合成:
SurfaceFlinger 接收所有应用的Surface
(按 Z-order 排序,即窗口层级),将多个 Surface 的像素数据合成到一个屏幕缓冲区(如通过 GPU 的图层混合),最终输出到屏幕。
3. 硬件层:显示输出(VSync 同步)
-
VSync(垂直同步信号):
屏幕硬件每秒产生 60 次 VSync 信号(60 FPS 场景),触发渲染流程的同步:- 应用层通过
Choreographer
监听 VSync 信号,确保在信号到来时启动 MLD 流程,避免“画面撕裂”(前一帧未绘制完就显示下一帧)。
- 应用层通过
-
显示硬件:
合成后的屏幕缓冲区数据通过显示控制器(如 LCD 控制器)发送到屏幕,完成像素点亮。
三、关键组件与机制
组件/机制 | 作用 | 核心考点 |
---|---|---|
DisplayList | 缓存硬件加速下的绘制指令 | 减少重复绘制,提升渲染效率 |
Surface | 应用的绘图表面,存储像素数据 | 每个窗口一个 Surface,隔离绘制内容 |
SurfaceFlinger | 系统服务,负责多 Surface 合成 | 处理窗口层级、透明度、旋转等合成逻辑 |
VSync | 垂直同步信号,同步渲染节奏 | 避免画面撕裂,控制帧率 |
硬件加速 | 用 GPU 替代 CPU 处理绘制 | 支持复杂绘制,但部分操作不兼容(需禁用) |
四、常见问题与优化方向(面试拓展)
-
掉帧(FPS 不足 60)的原因:
- 应用层:MLD 流程耗时过长(如复杂布局、过度绘制、主线程阻塞);
- 系统层:SurfaceFlinger 合成压力大(如过多重叠窗口、大分辨率 Surface)。
-
优化手段:
- 减少过度绘制(通过 Layout Inspector 检测,移除不必要的背景);
- 简化布局(使用
ConstraintLayout
减少层级,避免嵌套过深); - 开启硬件加速(默认开启,注意兼容问题);
- 避免主线程耗时操作(将网络/IO 移到子线程);
- 复用
DisplayList
(减少 View 频繁重绘)。
总结
Android 渲染流程是“应用层绘制(MLD)→ 系统层合成(SurfaceFlinger)→ 硬件显示(VSync 同步)”的完整链路,核心是通过 硬件加速、DisplayList 缓存、VSync 同步 等机制确保高效渲染。面试中需重点掌握各阶段的核心步骤、关键组件作用,以及性能优化思路。
其他:
1.getWidth()和getMeasuredWidth()的区别
getMeasuredWidth()、getMeasuredHeight()必须在onMeasure之后使用才有效)
getMeasuredWidth() 的取值最终来源于 setMeasuredDimension() 方法调用时传递的参数;
getWidth()与getHeight()方法必须在layout(int l, int t, int r, int b)执行之后才有效
getWidth()返回的是,mRight - mLeft,mRight、mLeft 变量分别表示 View 相对父容器的左右边缘位置;
2.View.post(runnable)原理
利用Handler通信机制,发送一个Runnable到MessageQueue中,当View布局处理完成时,自动发送消息,通知UI进程。借此机制,巧妙获取View的高宽属性,代码简洁,相比ViewTreeObserver监听处理,还不需要手动移除观察者监听事件。
3.requestLayout()的作用
requestLayout()也可以达到重绘view的目的,但是与invalidate和postInvalidate不同,它会先调用onLayout()重新排版,再调用ondraw()方法。
当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view(父类的视图)重新调用他的onMeasure、onLayout来重新设置自己位置。特别是当view的LayoutParams发生改变,并且它的值还没能应用到view上时,这时候适合调用这个方法requestLayout()
4.Window&View
Window&View
①.window是什么?
是所有Viewde直接管理者,任何视图都通过window呈现。Activity的setContentView底层都通过Window完成。
-window是一个抽象类,唯一实现类是PhoneWindow;Window并不是实际存在的,而是以View的形式存在。
-创建Window需要通过WindowManager创建
-实际使用中无法直接访问Window,必须通过WindowManager
-Window具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是通过IPC完成(PC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程,这里指的是Binder)
-Window和View通过ViewRootImpl建立联系
-WMS把所有的用户消息发给View/ViewGroup,但是在View/ViewGroup处理消息的过程中,有一些操作是公共的, Window把这些公共行为抽象出来, 这就是Window。
②.Window类型
FrameWork定义了三种窗口类型,三种类型定义在WindowManager,通过LayoutParams.type设置。
-应用窗口,对应一个Activity,每个Activity都包含一个Window对象。加载Activity由AmS完成,创建一个应用窗口只能在Activity内部完成(层级1~99)。
-子窗口,必须依附于任何类型的父窗口(层级1000~1999)。
-系统窗口,不需要对应任何Activity,如:状态栏,导航栏,普通应用程序不能创建系统窗口,必须要有系统应用权限.(层级2000~2999)。
int值越大层位置越靠上
如下图:
3.WindowManger的功能
主要就三个:添加、更改、删除View
WindowManger是一个接口,ViewManager是其父接口,WindowMangerImpl是其实现类。
4.Activity的setContentView流程
Activity#setContentView()实际通过getWindow().setContentView()交由PhoneWindow处理。
PhoneWindow中主要做两件事:
①当mDecor为空的时候,通过installDecor()初始化DecorView(mDecor)【DecoreView本质就是一个FrameLayout,是Activity中的顶级View】
②generateLayout()实际就是获取布局的父容器(mContentParent), 然后通过inflate将我们的setContentView传入的View或者layout布局文件填充到这个mContentParent,而这个父容器mContentParent实际上就是DecorView的id为R.id.content的子容器。
5.Activity、Window、DecorView、View之间的关系
DecorView 把屏幕划分为了两个区域:一个是 TitleView,也就是ActionBar或者TitleBar,一个是 ContentView,而我们平时在 Xml 文件中写的布局正好是展示在 ContentView 中。
6.DecorView何时被WindowManager添加到Window中?
ActivityThread的handleResumeActivity方法中,首先会调用Activity的onResume方法,接着调用Activity的makeVisible()方法
makeVisible()中通过WindowManager.addView()完成了DecorView的添加和显示两个过程
7.Dialog的创建过程:
//TODO
8.Window常见FLAG属性
这些属性有几个还有很重要的,比如:
FLAG_KEEP_SCREEN_ON: 屏幕常亮,可以在播放视频的时候设置这个参数
FLAG_FULLSCREEN: 沉浸式全屏
FLAG_SECURE:禁止截屏
FLAG_SHOW_WHEN_LOCKED:窗口在锁屏窗口上显示
activity和window的关系,dialog的window用的是谁的
一、Activity 与 Window 的关系
-
Activity 依赖 Window 展示界面:每个
Activity
在初始化时(attach()
方法)会创建一个PhoneWindow
(Window
的唯一实现),并通过它承载 UI 布局(如setContentView
实际由PhoneWindow
完成)。 -
Window 是显示与事件载体:
PhoneWindow
包含根视图DecorView
,负责 UI 渲染和事件传递(通过Callback
接口将事件交给Activity
处理)。 -
生命周期绑定:
Activity
的生命周期(如onResume
/onDestroy
)会驱动Window
的创建、显示和销毁。
二、Dialog 的 Window 用的是谁的?
Dialog
的 Window
是独立创建的(自己实例化 PhoneWindow
),但必须依赖宿主 Activity
的窗口令牌(Token)。它与宿主 Activity
的 Window
是不同对象,仅通过宿主的上下文关联,确保显示层级和权限正确(如依附于宿主窗口之上)。
synchornized的实现原理
Java 中 synchronized
是实现线程同步的核心关键字,其底层基于 JVM 的对象监视器(Monitor) 机制,经历了 JDK 1.6 的重大优化(从重量级锁升级为分级锁)。面试中需重点掌握其实现原理、锁升级过程及核心机制:
一、核心原理:基于对象监视器(Monitor)
synchronized
的同步语义依赖于 对象监视器(Monitor,也称为管程),每个 Java 对象都天生携带一个 Monitor(由 JVM 实现),其工作原理可概括为:
- 当线程进入
synchronized
代码块时,需获取对象的 Monitor 所有权; - 同一时刻只有一个线程能持有 Monitor(互斥性);
- 其他线程尝试获取时会进入阻塞队列,直到当前线程释放 Monitor。
二、锁的实现形式
synchronized
可修饰方法和代码块,底层通过不同方式关联 Monitor:
-
修饰普通方法:锁对象是 当前实例(this),通过方法常量池中的
ACC_SYNCHRONIZED
标志位实现。 -
修饰静态方法:锁对象是 当前类的 Class 对象,同样通过
ACC_SYNCHRONIZED
标志位。 -
修饰代码块:通过
monitorenter
和monitorexit
指令实现,锁对象为代码块括号中指定的对象(如synchronized(obj) { ... }
中的obj
)。
三、JDK 1.6 后的锁升级:从低效到高效
JDK 1.6 前,synchronized
是重量级锁(直接调用操作系统互斥量,开销大)。优化后引入分级锁机制,根据竞争强度自动升级,减少性能损耗:
- 无锁状态:对象刚创建,未被任何线程锁定。
-
偏向锁:
- 当只有一个线程访问时,锁会“偏向”该线程,记录线程 ID(通过对象头的 Mark Word 存储)。
- 后续该线程进入时无需竞争,直接获取锁(避免 CAS 操作开销)。
- 若有其他线程竞争,偏向锁会升级为轻量级锁。
-
轻量级锁:
- 多线程交替访问时,通过 CAS 操作尝试获取锁(将对象头的 Mark Word 替换为线程栈中的锁记录指针)。
- 若 CAS 成功,线程获取锁;若失败(存在竞争),自旋几次后升级为重量级锁。
-
重量级锁:
- 多线程激烈竞争时,依赖操作系统的互斥量(Mutex)实现,未获取锁的线程进入内核态阻塞(开销最大)。
四、关键数据结构:对象头(Mark Word)
锁的状态存储在对象头的 Mark Word 中(32 位 JVM 占 4 字节,64 位占 8 字节),不同锁状态对应不同存储结构:
- 无锁/偏向锁:存储对象哈希码、分代年龄、偏向线程 ID 等。
- 轻量级锁:存储指向线程栈中锁记录的指针。
- 重量级锁:存储指向 Monitor 的指针。
五、核心优势与面试考点
- 原子性保证:通过 Monitor 实现临界区资源的独占访问。
- 可见性保证:释放锁时会将工作内存数据刷新到主内存,获取锁时会从主内存加载最新数据。
- 有序性保证:通过“happens-before”规则限制指令重排序。
高频问题:
- 问:
synchronized
与volatile
的区别?
答:synchronized
保证原子性、可见性、有序性;volatile
仅保证可见性和有序性,不保证原子性。 - 问:锁升级的意义?
答:减少无竞争/低竞争场景下的性能开销(偏向锁→轻量级锁避免内核态切换,重量级锁应对高竞争)。
总结
synchronized
底层基于对象 Monitor 实现,JDK 1.6 后通过锁升级(偏向锁→轻量级锁→重量级锁) 优化性能,核心是根据竞争强度动态调整实现方式,平衡同步安全性与效率。面试中需结合对象头、Monitor 机制和锁升级过程阐述,体现对底层原理的理解。
Android事件分发机制
https://cloud.tencent.com/developer/article/1601306