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, που για ένα μεγάλο γράφημα σημαίνει επανασχεδίαση χιλιάδων pixel αντί για μερικές δεκάδες. 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() — κάντε cache το Bitmap, αποφύγετε δεσμεύσεις μνήμης και χρησιμοποιήστε Hardware Acceleration για σχεδίαση GPU.
Ναι, μετά την αλλαγή ιδιοτήτων Paint (χρώμα, πάχος, στυλ) πρέπει να καλείται invalidate(), επειδή το View δεν παρακολουθεί αυτόματα αλλαγές στα αντικείμενα Paint. Το σύστημα δεν γνωρίζει ότι το Paint άλλαξε και δεν θα καλέσει onDraw() χωρίς ρητό αίτημα.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης