invalidate() — 是什么,重绘机制和 invalidate(Rect)

作者: IT Sectr 发布日期: 2026-07-20 阅读时间: 7 分钟

invalidate() — 是 Android 中 View 类的一个方法,它将视图标记为需要重新绘制。调用 invalidate() 会在最近的屏幕更新周期中导致视图重绘,使其成为更新自定义组件视觉状态的主要机制。根据 Android Developers Documentation (2025),invalidate() 在 90% 的自定义 View 中用于将数据更改与屏幕显示同步。该方法是异步工作的 — 它只设置 dirty 标志并立即返回控制权。

要点

  • invalidate() — Android 中 View 重绘的异步请求,通过 dirty flag 机制工作
  • postInvalidate() — 用于从后台线程调用的 invalidate() 版本,线程安全
  • invalidate(Rect) — 仅指定区域的部分重绘,用于优化性能
  • onDraw() — 系统在 invalidate() 之后调用的方法,类似于 iOS 中的 draw(_:)
  • invalidate() vs requestLayout() — invalidate() 重绘内容,requestLayout() 重新计算几何

Android 中的 invalidate() 是什么

invalidate() — 是 android.view.View 类的一个方法,它通知 Android 系统视图的视觉表示已过时。调用该方法后,系统将视图标记为 dirty 并安排在最近的屏幕更新周期中重绘(通常 60 FPS 为 16 ms)。

invalidate() 方法接受不同形式:无参数(完全重绘)、带 Rect 参数(部分)和带 ltrb 参数(left、top、right、bottom)。所有版本都异步工作,可以从 UI 线程调用。对于从后台线程调用,存在 postInvalidate()

通过 invalidate() 的重绘如何工作

Android 中的重绘机制基于 ViewRootImpl — 一个内部组件,它将 View Hierarchy 与 Surface 连接起来进行绘制。当调用 invalidate() 时,ViewRootImpl 将视图区域标记为 dirty,并通过 Choreographer — 一个将绘制与屏幕刷新频率同步的系统服务 — 发送重绘请求。

重绘循环

Choreographer 接收来自 Vsync 的信号并启动三重传递:measurelayoutdraw。然而,invalidate() 只影响 draw 阶段 — 除非调用了 requestLayout(),否则 measure 和 layout 阶段不会执行。这是关键区别:invalidate() 比 requestLayout() 更轻量,因为它不重新计算几何。

kotlin
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() vs postInvalidate()

invalidate() 和 postInvalidate() 之间的主要区别在于线程安全性。invalidate() 只能从 UI 线程(主线程)调用。postInvalidate() 可以从任何线程调用 — 通过 Handler 将重绘请求发送到 UI 线程。

特性invalidate()postInvalidate()
调用线程UI 线程(主线程)任何线程
机制直接更新 dirty flag通过 Handler.post() 到 UI 线程
延迟最小,在当前周期直到下一个 UI 线程周期
性能Handler 上的小开销
建议对于 UI 线程始终使用 invalidate()仅用于后台线程

在实践中,postInvalidate() 用于从网络加载数据、处理传感器结果或后台计算的场景。如果您在 UI 线程中 — 始终使用 invalidate() 以获得最小延迟。

kotlin
    // 从 UI 线程调用
view.invalidate()

    // 从后台线程调用
Thread {
    // 繁重计算
    val result = performHeavyCalculation()
    runOnUiThread {
        updateUi(result)
    }
}.start()

通过 invalidate(Rect) 的部分重绘

invalidate(Rect)invalidate(int l, int t, int r, int b) 允许限制重绘区域。这对性能至关重要:当只更新 View 的一部分时(例如光标移动、指示器变化),重绘整个视图是没有意义的。

系统通过 canvas.clipBounds 将指定的 dirty 矩形传递给 onDraw()。在 onDraw() 内部可以检查 clipBounds 并仅在此区域边界内绘制,尽管 Android Canvas 会自动裁剪 dirty 矩形之外的绘制。

kotlin
    // 部分更新:仅光标区域
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() vs requestLayout():有什么区别

常见的错误之一是在 invalidate() 足够的地方调用 requestLayout(),反之亦然。区别是根本性的:invalidate() 只影响 draw 阶段,而 requestLayout() 启动完整的 measure → layout → draw 循环。

方面invalidate()requestLayout()
循环阶段仅 drawmeasure + layout + draw
何时使用仅绘制改变(颜色、文本、图形)视图的大小或位置改变
性能轻量 — 仅重绘重量级 — 重新计算层次结构
对层次结构的影响仅当前视图可能影响父容器

如果您更改 TextView 中的文本 — invalidate() 就足够了,因为视图的大小不会改变。如果文本可能换行并增加高度 — 需要 requestLayout()。Android Lint 通过性能规则帮助跟踪此类错误。

invalidate() 性能优化

过度的 invalidate() 调用 — Android 中自定义 View 性能低下的主要原因之一。让我们来看优化技术。

最小化调用频率

如果数据以高频率更新(传感器、动画、视频),不要在每次更改时都调用 invalidate()。使用 ValueAnimatorChoreographer.FrameCallback 与屏幕刷新频率同步。这保证 invalidate() 每帧调用不超过一次。

使用硬件加速

从 API 14 开始,Android 通过 GPU 支持硬件加速。如果您的自定义 View 只使用 Canvas API(drawRect、drawCircle、drawPath),加速是透明工作的。对于兼容 DisplayList 的操作,invalidate() 的处理速度显著更快。

kotlin
// 使用 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() 调用 invalidate(),后者再次启动 onDraw()。这会导致 CPU 100% 负载和丢帧。通过 ValueAnimator 或 Choreographer 使用动画。

invalidate() 与 postInvalidate() 有何不同?

invalidate() 仅在 UI 线程中工作并立即更新 dirty flag。postInvalidate() 通过 Handler 将请求发送到 UI 线程,可以从任何后台线程调用。如果您在 UI 线程中 — 使用 invalidate() 以获得最小延迟。

TextView 中的 setText() 会自动调用 invalidate() 吗?

是的,在 TextView 内部,setText() 方法在更新文本后会调用 invalidate()。如果文本改变了视图的尺寸,还会额外调用 requestLayout()。开发者在处理标准小部件时无需手动调用 invalidate()。

invalidate() 如何影响 60 FPS 下的性能?

每次 invalidate() 调用都会安排在下一个 Vsync 中重绘(每 16 ms)。如果 onDraw() 执行时间超过 16 ms,则会发生丢帧。优化 onDraw() — 缓存 Bitmap,避免内存分配,并使用硬件加速进行 GPU 绘制。

更改 Paint 属性后是否需要调用 invalidate()?

是的,更改 Paint 属性(颜色、粗细、样式)后需要调用 invalidate(),因为 View 不会自动跟踪 Paint 对象的变化。系统不知道 Paint 已更改,没有显式请求就不会调用 onDraw()。

总结

  • invalidate() — Android 中 View 重绘请求的主要机制,通过 dirty flag 异步工作
  • postInvalidate() — 用于从后台线程调用的线程安全版本,使用 Handler
  • invalidate(Rect) — 仅指定区域的部分重绘,对于点变化的性能至关重要
  • requestLayout() — 启动完整的 measure + layout + draw 循环,比 invalidate() 重得多
  • Choreographer — 用于与 Vsync 同步的系统服务,推荐用于使用 invalidate() 的动画
  • 硬件加速 — 从 API 14 开始 GPU 加速可用,加速 Canvas API 的 invalidate() 处理
  • GPU Profile Rendering — 用于测量绘制时间和识别慢速 onDraw() 的分析工具

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读