Jank — اصطلاحی است که به لرزشهای قابل توجه یا «تاخیر» در انیمیشن رابط کاربری اشاره دارد که در اثر افت فریمهای جداگانه ایجاد میشود. در برنامههای موبایل، Jank زمانی رخ میدهد که زمان رندر یک فریم از بودجه تخصیصیافته توسط نرخ تازهسازی نمایشگر فراتر رود. طبق Android Developers, 2025، Jank علت اصلی احساس ذهنی «کندی» است — برنامه ممکن است از نظر عملکردی عالی باشد، اما کاربر به دلیل FPS ناپایدار آن را کند درک میکند.
نکات اصلی
Jank — اصطلاحی در حوزه گرافیک کامپیوتری است که به نقص بصری اشاره دارد که در آن انیمیشن به جای حرکت نرم، به صورت ناگهانی حرکت میکند. در توسعه موبایل، Jank به عنوان تعداد فریمهای افت کرده (skipped frames) در واحد زمان اندازهگیری میشود. اگر سیستم نتواند فریم را تا لحظه VSync آماده کند، نمایشگر فریم قبلی را تکرار میکند — مکثی به مدت 16.6 میلیثانیه در 60 هرتز ایجاد میشود. یک فریم افت کرده ممکن است نامحسوس باشد، اما یک سری 3–5 فریم افت کرده پشت سر هم احساس «کندی» به مدت 50–80 میلیثانیه ایجاد میکند که کاربر به وضوح آن را تشخیص میدهد.
Jank به ویژه برای انیمیشنهایی که باید با سرعت ثابت کار کنند حیاتی است: اسکرول لیست، انیمیشن باز شدن منو، افکتهای پارالاکس، انتقال بین صفحهها. طبق تحقیق UX گوگل (2024)، برنامهای با شاخص Jank بیش از 3% جلسات اسکرول، 22% نظرات یک ستاره بیشتری نسبت به برنامهای با شاخص کمتر از 0.5% دریافت میکند. ابزار Android Vitals به طور خودکار Jank را ردیابی و بر اساس severity طبقهبندی میکند: moderate، severe و critical.
علل Jank به چند دسته تقسیم میشوند. دسته اول — Layout Jank: ناشی از requestLayout() مکرر به دلیل تغییر اندازه View، انیمیشنهای LayoutTransition یا بارگذاری دینامیک محتوا. هر فراخوانی requestLayout باعث Measure + Layout برای کل زیردرخت View میشود که ممکن است 5–30 میلیثانیه طول بکشد. دسته دوم — Draw Jank: مرتبط با overdraw و استفاده از drawableهای سنگین. دسته سوم — Thread Jank: مسدود شدن main thread به دلیل عملیات همزمان — بارگذاری فایلها، کار با پایگاه داده روی نخ اصلی، رمزگشایی Bitmap.
دسته چهارم — GC Jank: garbage collection در ART/Dalvik یا Swift ARC. وقتی اشیاء زیادی در heap جمع میشوند، GC یک توقف Stop-The-World به مدت 5–15 میلیثانیه راهاندازی میکند. در Android، توقفهای GC بیشتر در تخصیصهای مکرر در حلقهها رخ میدهد: ایجاد اشیاء در onDraw()، تخصیص در آداپتورها، عبارات lambda استفاده نشده. دسته پنجم — IPC Jank: ارتباط بین فرآیندی (ContentProvider، Binder) روی نخ اصلی. دسته ششم — Rendering Jank: رندر کند GPU به دلیل شیدرهای غیربهینه یا بافتهای با اندازه بزرگ.
| نوع 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 با trace سیستمی Perfetto آغاز میشود. Perfetto فعالیت همه نخها، CPU، GPU و زمانبند را ثبت میکند. نشانگر واضح Jank خطوط Choreographer.doFrame و Choreographer.doCallbacks هستند: اگر فاصله بین دو فراخوانی متوالی doFrame از 16.6 میلیثانیه بیشتر باشد، فریم افت کرده است. 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 مشترک میشود و هر فریم افت کرده را با ذکر مدت تاخیر لاگ میکند. Callback در هر 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 و 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 میلیثانیه تجاوز میکنند. در SwiftUI برای تشخیص Jank از UIView.invalidateIntrinsicContentSize استفاده میشود — فراخوانی مکرر این روش نشاندهنده Layout ناپایدار است.
کد Swift فریمهای افت کرده را از طریق CADisplayLink تشخیص میدهد. اگر اختلاف بین timestamp و targetTimestamp از 16.6 میلیثانیه بیشتر باشد — 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 امکان ضبط traceهای تا 30 ثانیه و تحلیل آنها از طریق رابط وب ui.perfetto.dev را فراهم میکند. مقیاس زمانی دقیق با کار Choreographer، نخهای رندر (RenderThread) و GPU را نشان میدهد. برای تحلیل دقیق مشکلات GPU از AGI (Android GPU Inspector) استفاده میشود که نه تنها زمان فریم، بلکه بار بلوکهای خاص GPU — شیدرها، رسترایزر، بلوک بافت را نشان میدهد.
در 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 برای یافتن overdraw 4x+، جایگزینی drawableهای سنگین با برداری (VectorDrawable/PDF)، استفاده محتاطانه از hardware layers — آنها رندر را تسریع میبخشند اما حافظه GPU بیشتری مصرف میکنند. برای Thread Jank: انتقال همه عملیات I/O، کار با پایگاه داده و رمزگشایی Bitmap به نخهای پسزمینه، استفاده از Kotlin Coroutines با Dispatcher مناسب یا RxJava با Schedulers.io().
برای GC Jank: به حداقل رساندن تخصیص در onDraw() و getView()، استفاده از ObjectPool، جایگزینی for-each با for ایندکسدار، استفاده محتاطانه از immutable data class در Kotlin با copy() — copy یک شیء جدید ایجاد میکند. برای IPC Jank: مقداردهی اولیه تنبل ContentProvider از طریق App Startup، انتقال فراخوانیهای Binder به نخ پسزمینه. برای Rendering Jank: کاهش اندازه بافتها تا حداکثر وضوح صفحه، استفاده از فشردهسازی ASTC یا ETC2، اجتناب از shader compilation غیرضروری (کامپایل شیدرها از قبل). راهحل جامع — اجرای منظم پروفایلینگ 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 میلیثانیه برای 60 FPS) فراتر رود.
Layout Jank (requestLayout مکرر)، Draw Jank (overdraw)، Thread Jank (مسدود شدن main thread)، GC Jank (garbage collection)، IPC Jank (فراخوانیهای Binder) و Rendering Jank (شیدرهای سنگین).
از Perfetto برای trace سیستم، 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 اسکرول حاوی لرزش) منجر به افزایش 22% نظرات منفی میشود. شاخص هدف — کمتر از 0.5% جلسات اسکرول.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید