Jank — είναι ένας όρος που υποδηλώνει αισθητά τραυλίσματα ή “κολλήματα” στην κίνηση της διεπαφής, που προκαλούνται από την παράλειψη μεμονωμένων καρέ. Σε εφαρμογές κινητών, το Jank εμφανίζεται όταν ο χρόνος απόδοσης ενός καρέ υπερβαίνει τον προϋπολογισμό που κατανέμεται από τον ρυθμό ανανέωσης της οθόνης. Σύμφωνα με το Android Developers, 2025, το Jank είναι η κύρια αιτία της υποκειμενικής αίσθησης “καθυστέρησης” — η εφαρμογή μπορεί να είναι λειτουργικά τέλεια, αλλά ο χρήστης την αντιλαμβάνεται ως αργή λόγω ασταθούς FPS.
Κύρια σημεία
Jank — είναι ένας όρος από τον τομέα των γραφικών υπολογιστή που υποδηλώνει ένα οπτικό ελάττωμα κατά το οποίο η κίνηση γίνεται με τραντάγματα αντί για ομαλή ολίσθηση. Στην ανάπτυξη κινητών, το Jank μετράται ως ο αριθμός των παραλειπόμενων καρέ (skipped frames) ανά μονάδα χρόνου. Εάν το σύστημα δεν προλάβει να προετοιμάσει ένα καρέ μέχρι τη στιγμή VSync, η οθόνη επαναλαμβάνει το προηγούμενο καρέ — δημιουργείται μια παύση 16.6 ms στα 60 Hz. Ένα παραλειπόμενο καρέ μπορεί να είναι απαρατήρητο, αλλά μια σειρά 3–5 συνεχόμενων παραλειπόμενων καρέ δημιουργεί μια αίσθηση “καθυστέρησης” 50–80 ms, την οποία ο χρήστης αντιλαμβάνεται καθαρά.
Το Jank είναι ιδιαίτερα κρίσιμο για κινήσεις που πρέπει να λειτουργούν με σταθερή ταχύτητα: κύλιση λίστας, κίνηση ανοίγματος μενού, εφέ παράλλαξης, μεταβάσεις μεταξύ οθονών. Σύμφωνα με έρευνα UX της Google (2024), μια εφαρμογή με δείκτη Jank άνω του 3% των συνεδριών κύλισης λαμβάνει 22% περισσότερες κριτικές ενός αστεριού από μια εφαρμογή με δείκτη κάτω του 0.5%. Το εργαλείο Android Vitals παρακολουθεί αυτόματα το Jank και το ταξινομεί ανάλογα με τη σοβαρότητα: moderate, severe και critical.
Οι αιτίες του Jank χωρίζονται σε διάφορες κατηγορίες. Πρώτη — Layout Jank: προκαλείται από συχνές κλήσεις requestLayout() λόγω αλλαγής μεγεθών View, κινήσεων LayoutTransition ή δυναμικής φόρτωσης περιεχομένου. Κάθε κλήση requestLayout ενεργοποιεί Measure + Layout για ολόκληρο το υποδέντρο View, το οποίο μπορεί να διαρκέσει 5–30 ms. Δεύτερη — Draw Jank: σχετίζεται με overdraw και χρήση βαρέων drawable. Τρίτη — Thread Jank: μπλοκάρισμα του main thread λόγω σύγχρονων λειτουργιών — φόρτωση αρχείων, εργασία με βάση δεδομένων στο κύριο νήμα, αποκωδικοποίηση Bitmap.
Τέταρτη κατηγορία — GC Jank: garbage collection στο ART/Dalvik ή Swift ARC. Όταν συσσωρεύονται πολλά αντικείμενα στο heap, το GC ενεργοποιεί μια παύση Stop-The-World διάρκειας 5–15 ms. Στο Android, οι παύσεις GC εμφανίζονται συχνότερα σε συχνές δεσμεύσεις μνήμης σε βρόχους: δημιουργία αντικειμένων σε onDraw(), δεσμεύσεις σε προσαρμογείς, αχρησιμοποίητες εκφράσεις lambda. Πέμπτη — IPC Jank: επικοινωνία μεταξύ διεργασιών (ContentProvider, Binder) στο κύριο νήμα. Έκτη — Rendering Jank: αργή απόδοση GPU λόγω μη βέλτιστων shader ή υφών μεγάλου μεγέθους.
| Τύπος Jank | Αιτία | Τυπική διάρκειαΕργαλείο αναζήτησης | |
|---|---|---|---|
| Layout | requestLayout, relayout | 5–30 ms | Perfetto, Systrace |
| Draw | Overdraw, βαρύ drawable | 3–20 ms | GPU Profiling |
| Thread | Μπλοκάρισμα main thread | 10–200 ms | Android Studio Profiler |
| GC | Garbage Collection | 5–15 ms | Memory Profiler |
| Rendering | Φόρτωση GPU | 10–50 ms | GPU Tracer, Xcode GPU |
Στο Android, η διάγνωση του Jank ξεκινά με το σύστημα ιχνηλάτησης Perfetto. Το Perfetto καταγράφει τη δραστηριότητα όλων των νημάτων, CPU, GPU και του χρονοπρογραμματιστή. Ένας σαφής δείκτης Jank είναι οι γραμμές Choreographer.doFrame και Choreographer.doCallbacks: εάν το διάστημα μεταξύ δύο διαδοχικών κλήσεων doFrame υπερβαίνει τα 16.6 ms, το καρέ παραλείπεται. Το Perfetto δείχνει την ακριβή αιτία — ποιο system call, lock ή GC προκάλεσε την καθυστέρηση. Στο Android Studio Profiler, παρόμοια λειτουργία είναι διαθέσιμη μέσω του CPU Profiler.
Για αυτόματη ανίχνευση Jank σε παραγωγή χρησιμοποιείται το FrameMetricsAggregator — ένα API που συλλέγει στατιστικά για κάθε καρέ και τα συγκεντρώνει ανά συνεδρία. Στο Android 12+ εμφανίστηκε το PerformanceHintManager — ένα API για υποδείξεις στο σύστημα σχετικά με τον στοχευόμενο ρυθμό καρέ. Εάν η εφαρμογή υποδείξει ότι λειτουργεί σε σενάριο 120 FPS, το σύστημα μπορεί να αυξήσει τη συχνότητα CPU/GPU για την πρόληψη του Jank. Για απλή καταγραφή όλων των παραλειπόμενων καρέ, αρκεί η εγγραφή στο Choreographer.FrameCallback.
Ο κώδικας Kotlin εγγράφεται στο Choreographer.FrameCallback και καταγράφει κάθε παραλειπόμενο καρέ με αναφορά της διάρκειας καθυστέρησης. Το καλείται σε κάθε VSync.
class JankDetector {
private val frameBudget = 16_666_666L
private var previousFrameTime = 0L
private val callback =
Choreographer.FrameCallback { currentTime ->
if (previousFrameTime != 0L) {
val frameDuration =
currentTime - previousFrameTime
val skippedFrames =
(frameDuration / frameBudget) - 1
if (skippedFrames > 0) {
Log.w("Jank",
"Παραλείφθηκαν $skippedFrames καρέ")
}
}
previousFrameTime = currentTime
Choreographer.getInstance()
.postFrameCallback(this)
}
fun start() {
Choreographer.getInstance()
.postFrameCallback(callback)
}
}
Στο iOS, η διάγνωση του Jank πραγματοποιείται μέσω του Instruments με το πρότυπο Core Animation. Το Instruments δείχνει το FPS σε πραγματικό χρόνο, τον αριθμό των offscreen-render και τα hit-test. Οι κύριοι δείκτες Jank στο iOS: κόκκινες στήλες στη χρονική κλίμακα Core Animation (υπέρβαση προϋπολογισμού καρέ), υψηλός δείκτης Renderer (σημαίνει offscreen rendering) και χαμηλό FPS. Για παρακολούθηση παραγωγής, το MetricKit συλλέγει αναφορές με τη μετρική MXAnimatoryMetric, η οποία περιλαμβάνει μέσο FPS, P50 και P95 χρόνο καρέ.
Η εγγενής διάγνωση Jank στο iOS περιλαμβάνει το CADisplayLink με έλεγχο timestamp και targetTimestamp. Εάν το τρέχον timestamp υστερεί σημαντικά από το targetTimestamp, ένα ή περισσότερα καρέ έχουν παραλειφθεί. Η Apple συνιστά επίσης τη χρήση os_signpost για προσαρμοσμένη προφίλ: τοποθετήστε signpost-interval στην αρχή και στο τέλος της απόδοσης καρέ και ελέγξτε στο Instruments ποια διαστήματα υπερβαίνουν τα 16.6 ms. Στο SwiftUI, για διάγνωση Jank χρησιμοποιείται το UIView.invalidateIntrinsicContentSize — η συχνή κλήση αυτής της μεθόδου υποδηλώνει ασταθές Layout.
Ο κώδικας Swift ανιχνεύει παραλειπόμενα καρέ μέσω CADisplayLink. Εάν η διαφορά μεταξύ timestamp και targetTimestamp υπερβαίνει τα 16.6 ms — το Jank καταγράφεται.
class JankMonitor {
private var displayLink: CADisplayLink?
private var totalJank = 0
func start() {
displayLink = CADisplayLink(
target: self,
selector: #selector(detectJank)
)
displayLink?.add(to: .current,
forMode: .common)
}
@objc
private func detectJank() {
guard let link = displayLink else { return }
let delay = link.targetTimestamp
- link.timestamp
if delay > 0.0167 {
totalJank += 1
}
}
}
Για την προφίλ του Jank χρησιμοποιούνται τόσο τα ενσωματωμένα εργαλεία του λειτουργικού συστήματος όσο και SDK τρίτων. Στο Android, το βασικό εργαλείο είναι το Perfetto (αντικατέστησε το Systrace). Το Perfetto επιτρέπει την εγγραφή ιχνηλατήσεων έως 30 δευτερόλεπτα και την ανάλυσή τους μέσω της διεπαφής ιστού ui.perfetto.dev. Δείχνει την ακριβή χρονογραμμή με τη δραστηριότητα του Choreographer, των νημάτων απόδοσης (RenderThread) και της GPU. Για λεπτομερή ανάλυση προβλημάτων GPU χρησιμοποιείται το AGI (Android GPU Inspector), το οποίο δείχνει όχι μόνο τον χρόνο καρέ αλλά και τη φόρτωση συγκεκριμένων μπλοκ GPU — shader, ραστεροποιητή, μπλοκ υφής.
Στο iOS, το αντίστοιχο είναι το Instruments με τα πρότυπα Core Animation, Metal System Trace και GPU Driver. Το Core Animation δείχνει FPS και χρόνο καρέ, το Metal System Trace — εργασία GPU με λεπτομέρεια έως κάθε draw call. Για προφίλ σε πραγματικές συσκευές υπό φόρτο χρησιμοποιούνται τα Firebase Performance (συλλέγει μετρική Screen Rendering) και Sentry(καταγράφει stack trace στο Jank). Το νέο API Android 15 Performance Hint επιτρέπει στον προγραμματιστή να υποδείξει στο σύστημα ποια καρέ είναι σημαντικά και να λαμβάνει προειδοποιήσεις από το σύστημα όταν πλησιάζει το Jank.
Ο κώδικας Kotlin χρησιμοποιεί το FrameMetricsAggregator για συλλογή στατιστικών καρέ ανά συνεδρία. Μετά τη διακοπή του aggregator, εμφανίζεται ο αριθμός των παραλειπόμενων καρέ.
class JankAggregator(private val activity: Activity) {
private val aggregator = FrameMetricsAggregator()
fun startCollection() {
aggregator.add(activity.window)
}
fun stopAndReport() {
aggregator.remove()
val result = aggregator.getMetrics()
val totalFrames = result
?.get(FrameMetrics.TOTAL_DURATION)
?.size ?: 0
val jankFrames = result
?.get(FrameMetrics.TOTAL_DURATION)
?.count { it > 16_666_666L} ?: 0
Log.d("JankReport",
"Αναλογία Jank: ${jankFrames * 100 / totalFrames}%")
}
}
Η διόρθωση του Jank απαιτεί συνδυασμό μεθόδων ανάλογα με τον τύπο του. Για Layout Jank: αντικατάσταση βαθιών ιεραρχιών με ConstraintLayout/Compose/SwiftUI, χρήση merge-tags, αποφυγή requestLayout σε κινήσεις. Για Draw Jank: χρήση Debug GPU Overdraw για εύρεση 4x+ overdraw, αντικατάσταση βαρέων drawable με διανυσματικά (VectorDrawable/PDF), προσεκτική χρήση hardware layers — επιταχύνουν την απόδοση αλλά καταναλώνουν περισσότερη μνήμη GPU. Για Thread Jank: μεταφορά όλων των λειτουργιών I/O, εργασίας με βάση δεδομένων και αποκωδικοποίησης Bitmap σε νήματα παρασκηνίου, χρήση Kotlin Coroutines με σωστό Dispatcher ή RxJava με Schedulers.io().
Για GC Jank: ελαχιστοποίηση δεσμεύσεων σε onDraw() και getView(), χρήση object pool (ObjectPool), αντικατάσταση for-each με indexed for, προσεκτική χρήση immutable data class σε Kotlin με copy() — το copy δημιουργεί νέο αντικείμενο. Για IPC Jank: τεμπέλικη αρχικοποίηση ContentProvider μέσω App Startup, μεταφορά κλήσεων Binder σε νήμα παρασκηνίου. Για Rendering Jank: μείωση μεγέθους υφών στη μέγιστη ανάλυση οθόνης, χρήση συμπίεσης ASTC ή ETC2, αποφυγή περιττής shader compilation (μεταγλώττιση shader εκ των προτέρων). Ολοκληρωμένη λύση — τακτική εκτέλεση προφίλ Perfetto/Instruments στο CI και παρακολούθηση παλινδρομήσεων Jank.
Ο κώδικας Kotlin παρουσιάζει ασύγχρονη φόρτωση δεδομένων στην οθόνη μετά το reportFullyDrawn, ώστε η βαριά εργασία να μην μπλοκάρει το πρώτο καρέ. Το callback καλείται αφού ο χρήστης δει τη διεπαφή.
class JankSafeLoader {
suspend fun loadAfterFirstFrame(
activity: Activity
) {
// εγγυόμαστε ότι το πρώτο καρέ έχει ήδη αποδοθεί
if (Build.VERSION.SDK_INT >= 29) {
activity.reportFullyDrawn()
}
// βαριά φόρτωση — μετά το πρώτο καρέ
withContext(Dispatchers.IO) {
val data = fetchHeavyData()
withContext(Dispatchers.Main) {
updateUI(data)
}
}
}
}
Συχνές ερωτήσεις
Jank — είναι παραλειπόμενα καρέ απόδοσης που εκδηλώνονται ως αισθητά τραυλίσματα ή τραντάγματα της κίνησης. Συμβαίνει όταν ο χρόνος προετοιμασίας καρέ υπερβαίνει τον χρονικό προϋπολογισμό (16.6 ms για 60 FPS).
Layout Jank (συχνές requestLayout), Draw Jank (overdraw), Thread Jank (μπλοκάρισμα main thread), GC Jank (garbage collection), IPC Jank (κλήσεις Binder) και Rendering Jank (βαριά shader).
Χρησιμοποιήστε το Perfetto για ιχνηλάτηση συστήματος, GPU Profiling για ανάλυση φάσεων καρέ και FrameMetricsAggregator για παρακολούθηση παραγωγής. Στο Android Studio — CPU Profiler με Deep Java Trace.
Μέσω Instruments με το πρότυπο Core Animation ή Metal System Trace. Για παραγωγή — MetricKit με MXAnimatoryMetric. Προγραμματιστικά — CADisplayLink με έλεγχο διαφοράς timestamp και targetTimestamp.
Σύμφωνα με την Google, δείκτης Jank άνω του 3% των συνεδριών κύλισης (3 στα 100 scroll περιέχουν τραύλισμα) οδηγεί σε αύξηση των αρνητικών κριτικών κατά 22%. Στόχος — λιγότερο από 0.5% των συνεδριών κύλισης.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης