invalidate() — е метод на класа View в Android, който маркира изгледа като изискващ повторно рисуване. Извикването на invalidate() води до прерисуване на изгледа в най-близкия цикъл на обновяване на екрана, което го прави основния механизъм за обновяване на визуалното състояние на потребителски компоненти. Според Android Developers Documentation (2025), invalidate() се използва в 90% от потребителските View за синхронизиране на промени в данните с показването на екрана. Методът работи асинхронно — той само задава флага dirty и веднага връща управлението.
Основни точки
invalidate() — е метод на класа android.view.View, който уведомява системата Android, че визуалното представяне на изгледа е остаряло. След извикването на метода, системата маркира изгледа като dirty и планира неговото прерисуване в най-близкия цикъл на обновяване на екрана (обикновено 16 ms за 60 FPS).
Методът 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 — фазите measure и layout не се изпълняват, освен ако не е извикан requestLayout(). Това е ключовата разлика: 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 нишката |
| Производителност | Висока | Малък overhead върху 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 (например движение на курсора, промяна на индикатор) няма смисъл да се прерисува целия изглед.
Системата предава указания dirty правоъгълник на onDraw() чрез canvas.clipBounds. Вътре в 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) — задължителна техника за редактори, платна за рисуване и анимирани компоненти.
Една от честите грешки е извикването на requestLayout() там, където е достатъчен invalidate(), и обратно. Разликата е фундаментална: invalidate() засяга само фазата draw, докато requestLayout() стартира пълния цикъл measure → layout → draw.
| Аспект | invalidate() | requestLayout() |
|---|---|---|
| Фази на цикъла | Само draw | measure + layout + draw |
| Кога да се използва | Променя се само рисуването (цвят, текст, графика) | Променя се размерът или позицията на изгледа |
| Производителност | Леко — само прерисуване | Тежко — преизчисляване на йерархията |
| Влияние върху йерархията | Само текущия изглед | Може да засегне родителски контейнери |
Ако променяте текст в TextView — достатъчен е invalidate(), тъй като размерът на изгледа не се променя. Ако текстът може да премине на нов ред и да увеличи височината — е необходим requestLayout(). Android Lint помага за проследяване на такива грешки чрез правила за производителност.
Прекомерните извиквания на invalidate() — една от основните причини за ниска производителност на потребителски View в Android. Нека разгледаме техники за оптимизация.
Ако данните се обновяват с висока честота (сензори, анимация, видео), не извиквайте 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) за точкови обновявания, избягвайте извикването на invalidate() от onDraw() (безкраен цикъл) и винаги профилирайте чрез GPU Profile Rendering на устройството. Това ще покаже точното време за рисуване на всеки кадър и ще помогне за идентифициране на проблемни места.
Често задавани въпроси
Не, извикването на invalidate() вътре в onDraw() създава безкраен цикъл на прерисуване: onDraw() извиква invalidate(), който отново стартира onDraw(). Това води до 100% натоварване на CPU и загуба на кадри. Използвайте анимации чрез ValueAnimator или Choreographer.
invalidate() работи само в UI нишката и обновява dirty flag незабавно. postInvalidate() изпраща заявка чрез Handler до UI нишката и може да бъде извикан от всяка фонова нишка. Ако сте в UI нишката — използвайте invalidate() за минимална латентност.
Да, вътре в TextView методът setText() извиква invalidate() след обновяване на текста. Ако текстът е променил размерите на изгледа, допълнително се извиква requestLayout(). Разработчикът не трябва ръчно да извиква invalidate() при работа със стандартни Widget-и.
Всяко извикване на invalidate() планира прерисуване в следващия Vsync (на всеки 16 ms). Ако onDraw() отнема повече от 16 ms, настъпва загуба на кадри. Оптимизирайте onDraw() — кеширайте Bitmap, избягвайте алокации и използвайте Hardware Acceleration за GPU рисуване.
Да, след промяна на свойствата на Paint (цвят, дебелина, стил) трябва да се извика invalidate(), тъй като View не проследява автоматично промените в обектите Paint. Системата не знае, че Paint се е променил, и няма да извика onDraw() без изрична заявка.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също