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 플래그 메커니즘을 통해 작동
  • postInvalidate() — 백그라운드 스레드에서 호출하기 위한 invalidate() 버전, 스레드 안전
  • invalidate(Rect) — 성능 최적화를 위해 지정된 영역만 부분 다시 그리기
  • onDraw() — invalidate() 후 시스템이 호출하는 메서드, iOS의 draw(_:)와 유사
  • invalidate() vs requestLayout() — invalidate()는 콘텐츠를 다시 그리고, requestLayout()은 지오메트리를 다시 계산

Android에서 invalidate()란

invalidate()는 android.view.View 클래스의 메서드로, 뷰의 시각적 표현이 오래되었음을 Android 시스템에 알립니다. 메서드를 호출한 후 시스템은 뷰를 dirty로 표시하고 다음 화면 갱신 주기(일반적으로 60 FPS의 경우 16ms)에서 다시 그리기를 예약합니다.

invalidate() 메서드는 다양한 형태로 제공됩니다: 매개변수 없음(전체 다시 그리기), Rect 매개변수(부분), ltrb 매개변수(left, top, right, bottom) 포함. 모든 버전은 비동기적으로 작동하며 UI 스레드에서 호출해야 합니다. 백그라운드 스레드에서 호출하기 위한 postInvalidate()가 있습니다.

invalidate()를 통한 다시 그리기 작동 방식

Android의 다시 그리기 메커니즘은 ViewRootImpl을 기반으로 합니다 — View Hierarchy를 그리기 위한 Surface에 연결하는 내부 컴포넌트입니다. invalidate()가 호출되면 ViewRootImpl은 뷰 영역을 dirty로 표시하고 Choreographer를 통해 다시 그리기 요청을 보냅니다 — 화면 갱신률과 그리기를 동기화하는 시스템 서비스입니다.

다시 그리기 주기

Choreographer는 Vsync에서 신호를 받아 measure, layout, draw의 세 단계 프로세스를 시작합니다. 그러나 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()는 Path를 통해 선을 그리기 위한 Canvas를 받습니다.

invalidate() vs postInvalidate()

invalidate()와 postInvalidate()의 주요 차이점은 스레드 안전성에 있습니다. invalidate()는 UI 스레드(메인 스레드)에서만 호출해야 합니다. postInvalidate()는 모든 스레드에서 호출할 수 있으며 Handler를 통해 UI 스레드에 다시 그리기 요청을 보냅니다.

특성invalidate()postInvalidate()
호출 스레드UI 스레드(메인 스레드)모든 스레드
메커니즘직접 dirty 플래그 업데이트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의 일부만 변경될 때(예: 커서 이동, 표시기 변경) 전체 뷰를 다시 그릴 필요가 없습니다.

시스템은 지정된 dirty 사각형을 onDraw()canvas.clipBounds를 통해 전달합니다. 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()
주기 단계draw만measure + layout + draw
사용 시기렌더링만 변경(색상, 텍스트, 그래픽)View 크기 또는 위치 변경
성능가벼움 — 다시 그리기만무거움 — 계층 구조 재계산
계층 구조 영향현재 View만부모 컨테이너에 영향 가능

TextView에서 텍스트를 변경하는 경우 View 크기가 변하지 않으므로 invalidate()로 충분합니다. 텍스트가 새 줄로 넘어가 높이가 증가할 수 있는 경우 requestLayout()이 필요합니다. Android Lint는 성능 규칙을 통해 이러한 오류를 추적하는 데 도움을 줍니다.

invalidate() 성능 최적화

과도한 invalidate() 호출은 Android에서 사용자 정의 View 성능 저하의 주요 원인 중 하나입니다. 최적화 기술을 살펴보겠습니다.

호출 빈도 최소화

데이터가 높은 빈도(센서, 애니메이션, 비디오)로 업데이트되는 경우 모든 변경 사항에 대해 invalidate()를 호출하지 마세요. 화면 갱신률과 동기화하려면 ValueAnimator 또는 Choreographer.FrameCallback을 사용하세요. 이렇게 하면 invalidate()가 프레임당 한 번 이상 호출되지 않습니다.

하드웨어 가속 사용

API 14부터 Android는 GPU를 통한 하드웨어 가속을 지원합니다. 사용자 정의 View가 Canvas API(drawRect, drawCircle, drawPath)만 사용하는 경우 가속이 투명하게 작동합니다. DisplayList 호환 작업의 경우 invalidate() 처리가 훨씬 빨라집니다.

kotlin
// Vsync 동기화를 위해 Choreographer 사용
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 플래그를 즉시 업데이트합니다. postInvalidate()는 Handler를 통해 UI 스레드에 요청을 보내며 모든 백그라운드 스레드에서 호출할 수 있습니다. UI 스레드에 있는 경우 최소 지연 시간을 위해 invalidate()를 사용하세요.

TextView의 setText()가 자동으로 invalidate()를 호출하나요?

네, 내부적으로 TextView의 setText() 메서드는 텍스트 업데이트 후 invalidate()를 호출합니다. 텍스트가 View의 크기를 변경하는 경우 requestLayout()도 호출됩니다. 표준 위젯으로 작업할 때 개발자가 수동으로 invalidate()를 호출할 필요는 없습니다.

60 FPS에서 invalidate()가 성능에 어떤 영향을 미치나요?

각 invalidate() 호출은 다음 Vsync(16ms마다)에서 다시 그리기를 예약합니다. onDraw()가 16ms보다 오래 걸리면 프레임 드롭이 발생합니다. onDraw()를 최적화하세요 — Bitmap을 캐시하고, 할당을 피하며, GPU 렌더링을 위해 하드웨어 가속을 사용하세요.

Paint 속성 변경 후 invalidate()를 호출해야 하나요?

네, Paint 속성(색상, 두께, 스타일)을 변경한 후에는 invalidate()를 호출해야 합니다. View가 Paint 객체의 변경을 자동으로 추적하지 않기 때문입니다. 시스템은 Paint가 변경되었는지 알 수 없으며 명시적 요청 없이는 onDraw()를 호출하지 않습니다.

요약

  • invalidate() — Android에서 View 다시 그리기를 요청하는 기본 메커니즘, dirty 플래그를 통해 비동기적으로 작동
  • postInvalidate() — 백그라운드 스레드 호출용 스레드 안전 버전, Handler 사용
  • invalidate(Rect) — 지정된 영역만 부분 다시 그리기, 대상 변경의 성능에 중요
  • requestLayout() — 전체 measure + layout + draw 주기 트리거, invalidate()보다 훨씬 무거움
  • Choreographer — Vsync 동기화를 위한 시스템 서비스, invalidate()와 함께 애니메이션에 권장
  • Hardware Acceleration — API 14부터 사용 가능한 GPU 가속, Canvas API의 invalidate() 처리 속도 향상
  • GPU Profile Rendering — 렌더링 시간 측정 및 느린 onDraw() 메서드 식별을 위한 프로파일링 도구

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기