invalidate() — 是 Android 中 View 类的一个方法,它将视图标记为需要重新绘制。调用 invalidate() 会在最近的屏幕更新周期中导致视图重绘,使其成为更新自定义组件视觉状态的主要机制。根据 Android Developers Documentation (2025),invalidate() 在 90% 的自定义 View 中用于将数据更改与屏幕显示同步。该方法是异步工作的 — 它只设置 dirty 标志并立即返回控制权。
要点
invalidate() — 是 android.view.View 类的一个方法,它通知 Android 系统视图的视觉表示已过时。调用该方法后,系统将视图标记为 dirty 并安排在最近的屏幕更新周期中重绘(通常 60 FPS 为 16 ms)。
invalidate() 方法接受不同形式:无参数(完全重绘)、带 Rect 参数(部分)和带 ltrb 参数(left、top、right、bottom)。所有版本都异步工作,可以从 UI 线程调用。对于从后台线程调用,存在 postInvalidate()。
Android 中的重绘机制基于 ViewRootImpl — 一个内部组件,它将 View Hierarchy 与 Surface 连接起来进行绘制。当调用 invalidate() 时,ViewRootImpl 将视图区域标记为 dirty,并通过 Choreographer — 一个将绘制与屏幕刷新频率同步的系统服务 — 发送重绘请求。
Choreographer 接收来自 Vsync 的信号并启动三重传递:measure、layout、draw。然而,invalidate() 只影响 draw 阶段 — 除非调用了 requestLayout(),否则 measure 和 layout 阶段不会执行。这是关键区别:invalidate() 比 requestLayout() 更轻量,因为它不重新计算几何。
class CustomChartView(context: Context, attrs: AttributeSet?)
: View(context, attrs) {
private var dataPoints: List<Float> = emptyList()
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
fun updateData(newPoints: List<Float>) {
dataPoints = newPoints
invalidate() // 重绘请求
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.color = Color.BLUE
paint.strokeWidth = 4f
paint.style = Paint.Style.STROKE
// 绘制图表线
val path = Path()
dataPoints.forEachIndexed { index, value ->
val x = index * width / max(dataPoints.size - 1, 1)
val y = height - value * height
if (index == 0) path.moveTo(x, y)
else path.lineTo(x, y)
}
canvas.drawPath(path, paint)
}
}
在此示例中,用于绘制图表的自定义 View 在数据更新时调用 invalidate()。系统只重绘此 View,而不影响层次结构的其他元素。onDraw() 获取 Canvas 以通过 Path 绘制线条。
invalidate() 和 postInvalidate() 之间的主要区别在于线程安全性。invalidate() 只能从 UI 线程(主线程)调用。postInvalidate() 可以从任何线程调用 — 通过 Handler 将重绘请求发送到 UI 线程。
| 特性 | invalidate() | postInvalidate() |
|---|---|---|
| 调用线程 | UI 线程(主线程) | 任何线程 |
| 机制 | 直接更新 dirty flag | 通过 Handler.post() 到 UI 线程 |
| 延迟 | 最小,在当前周期 | 直到下一个 UI 线程周期 |
| 性能 | 高 | Handler 上的小开销 |
| 建议 | 对于 UI 线程始终使用 invalidate() | 仅用于后台线程 |
在实践中,postInvalidate() 用于从网络加载数据、处理传感器结果或后台计算的场景。如果您在 UI 线程中 — 始终使用 invalidate() 以获得最小延迟。
// 从 UI 线程调用
view.invalidate()
// 从后台线程调用
Thread {
// 繁重计算
val result = performHeavyCalculation()
runOnUiThread {
updateUi(result)
}
}.start()
invalidate(Rect) 和 invalidate(int l, int t, int r, int b) 允许限制重绘区域。这对性能至关重要:当只更新 View 的一部分时(例如光标移动、指示器变化),重绘整个视图是没有意义的。
系统通过 canvas.clipBounds 将指定的 dirty 矩形传递给 onDraw()。在 onDraw() 内部可以检查 clipBounds 并仅在此区域边界内绘制,尽管 Android Canvas 会自动裁剪 dirty 矩形之外的绘制。
// 部分更新:仅光标区域
private val cursorRect = Rect()
fun moveCursorTo(newX: Int, newY: Int) {
// 使旧位置失效
invalidate(cursorRect)
cursorRect.set(newX - 5, newY - 5,
newX + 5, newY + 5)
// 使新位置失效
invalidate(cursorRect)
}
如果没有部分重绘,每次光标移动都会重绘整个 View,对于一个大型图表来说,这意味着重绘数千像素而不是几十个。invalidate(Rect) — 对于编辑器、绘图画布和动画组件来说是强制技术。
常见的错误之一是在 invalidate() 足够的地方调用 requestLayout(),反之亦然。区别是根本性的:invalidate() 只影响 draw 阶段,而 requestLayout() 启动完整的 measure → layout → draw 循环。
| 方面 | invalidate() | requestLayout() |
|---|---|---|
| 循环阶段 | 仅 draw | measure + layout + draw |
| 何时使用 | 仅绘制改变(颜色、文本、图形) | 视图的大小或位置改变 |
| 性能 | 轻量 — 仅重绘 | 重量级 — 重新计算层次结构 |
| 对层次结构的影响 | 仅当前视图 | 可能影响父容器 |
如果您更改 TextView 中的文本 — invalidate() 就足够了,因为视图的大小不会改变。如果文本可能换行并增加高度 — 需要 requestLayout()。Android Lint 通过性能规则帮助跟踪此类错误。
过度的 invalidate() 调用 — Android 中自定义 View 性能低下的主要原因之一。让我们来看优化技术。
如果数据以高频率更新(传感器、动画、视频),不要在每次更改时都调用 invalidate()。使用 ValueAnimator 或 Choreographer.FrameCallback 与屏幕刷新频率同步。这保证 invalidate() 每帧调用不超过一次。
从 API 14 开始,Android 通过 GPU 支持硬件加速。如果您的自定义 View 只使用 Canvas API(drawRect、drawCircle、drawPath),加速是透明工作的。对于兼容 DisplayList 的操作,invalidate() 的处理速度显著更快。
// 使用 Choreographer 进行 Vsync 同步
private val frameCallback = Choreographer.FrameCallback { frameTimeNanos ->
updateAnimation(frameTimeNanos)
invalidate()
Choreographer.getInstance().postFrameCallback(this)
}
fun startAnimation() {
Choreographer.getInstance().postFrameCallback(frameCallback)
}
对于点更新使用 invalidate(Rect),避免从 onDraw() 调用 invalidate()(无限循环),并始终通过设备上的 GPU Profile Rendering 进行分析。这将显示每帧的精确绘制时间,并帮助识别问题区域。
常见问题
不可以,在 onDraw() 内部调用 invalidate() 会创建无限的重绘循环:onDraw() 调用 invalidate(),后者再次启动 onDraw()。这会导致 CPU 100% 负载和丢帧。通过 ValueAnimator 或 Choreographer 使用动画。
invalidate() 仅在 UI 线程中工作并立即更新 dirty flag。postInvalidate() 通过 Handler 将请求发送到 UI 线程,可以从任何后台线程调用。如果您在 UI 线程中 — 使用 invalidate() 以获得最小延迟。
是的,在 TextView 内部,setText() 方法在更新文本后会调用 invalidate()。如果文本改变了视图的尺寸,还会额外调用 requestLayout()。开发者在处理标准小部件时无需手动调用 invalidate()。
每次 invalidate() 调用都会安排在下一个 Vsync 中重绘(每 16 ms)。如果 onDraw() 执行时间超过 16 ms,则会发生丢帧。优化 onDraw() — 缓存 Bitmap,避免内存分配,并使用硬件加速进行 GPU 绘制。
是的,更改 Paint 属性(颜色、粗细、样式)后需要调用 invalidate(),因为 View 不会自动跟踪 Paint 对象的变化。系统不知道 Paint 已更改,没有显式请求就不会调用 onDraw()。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。