کند شدن — توصیف کاربر از وضعیتی است که برنامه موبایل کند و ناپایدار کار میکند: گاهی عادی پاسخ میدهد، گاهی ناگهان برای چند ثانیه قفل میکند. در زمینه فنی «کند شدن» به معنای ترکیبی از لگها و ریزهنگهایی است که توسط مکثهای مکرر GC، مسدود شدن رشته اصلی توسط عملیات همزمان و ساختارهای داده غیربهینه ایجاد میشود. طبق Android Performance Benchmarking Guide، کاهش زمان پاسخ از 300 ms به 100 ms retention کاربران را 25% افزایش میدهد. تشخیص کند شدن نیاز به ترکیب پروفایلسازی CPU و Memory با تحلیل دفعات جمعآوری زباله دارد.
نکات اصلی
کند شدن — یک اصطلاح غیررسمی است که کاربران با آن عملکرد ذهنی کند برنامه را توصیف میکنند. برخلاف لگ که به صورت تأخیر ثابت ظاهر میشود، کند شدن هنگهای نامنظم است: برنامه میتواند چند ثانیه عالی کار کند و سپس 1–3 ثانیه «فکر کند».
از دیدگاه پروفایلسازی، کند شدن به صورت یک سری فریمهای از دست رفته (jank) با تأخیرهای پیک بیش از 100 ms ظاهر میشود. در نمودار FPS این به صورت افتهای شدید دیده میشود: 60 → 20 → 55 → 10 فریم در ثانیه. برخلاف لگ با FPS یکنواخت پایین، کند شدن تغییرپذیری آشکاری دارد.
وقتی برنامه کند میشود، کاربر منطق کندی را درک نمیکند: صفحه میتواند نرم اسکرول شود و سپس ناگهان برای یک ثانیه متوقف شود. این باعث ناامیدی و کاهش اعتماد به برنامه میشود. طبق Google، 53% کاربران سایت یا برنامه را ترک میکنند اگر بارگذاری بیش از 3 ثانیه طول بکشد.
ماهیت نامنظم کند شدن نشان میدهد که مشکل ناشی از عوامل رویدادی است، نه بارگذاری دائمی. بیایید سناریوهای معمول را بررسی کنیم.
در Android در محیط ART، جمعآوری زباله همه رشتههای برنامه را متوقف میکند. اگر در کد اشیای موقت زیادی ایجاد شود — مثلاً در هر فراخوانی onBindViewHolder یک String جدید از طریق اتصال ایجاد شود — GC بیشتر اجرا میشود. مکث بسته به اندازه heap و نسل اشیا میتواند 5–50 ms طول بکشد. کاربر این را به عنوان «فکر کردن» ناگهانی احساس میکند.
Room در Android و Core Data در iOS از کوئریهای ناهمزمان پشتیبانی میکنند، اما توسعهدهندگان اغلب برای سادگی getValue() فراخوانی میکنند یا کوئری را از طریق runBlocking اجرا میکنند. SELECT سنگین با join روی جدول 10 000 ردیفی میتواند 200–500 ms طول بکشد و UI را کاملاً مسدود کند.
بارگذاری تصویر دوربین (12 مگاپیکسل، 4000x3000 px) بدون مقیاسبندی تا 200 ms برای دکدگذاری به Bitmap طول میکشد. اگر تصاویر به صورت ناهمزمان بارگذاری شوند اما بدون پول رشته با محدودیت، اجرای همزمان 5–6 دکدگذاری میتواند CPU را بیش از حد بارگذاری کرده و باعث کندیهای سرگردان شود.
تشخیص کندیهای نامنظم دشوارتر از تشخیص لگهای ثابت است، زیرا مشکل ممکن است در هر اجرا تکرار نشود. نیاز به جمعآوری آمار در یک دوره طولانی است.
Android Studio Memory Profiler نه تنها استفاده از حافظه، بلکه رویدادهای GC را نیز نشان میدهد: دفعات، نوع (Concurrent، Full)، مدت. اگر GC در حالت سکون بیش از 1 بار در 5 ثانیه رخ دهد — این نشانه تخصیص بیش از حد است. ثبت heap dump در لحظه کند شدن امکان دیدن اشیایی که حافظه را اشغال میکنند فراهم میکند.
در iOS از الگوی Allocations در Instruments برای ردیابی ایجاد و آزادسازی اشیا استفاده کنید. نسلها (Generations) را فعال کنید — آنها امکان گرفتن عکسهایی از heap بین عملیات و دیدن اشیایی که در حافظه باقی میمانند را فراهم میکنند. اشیای پایدار (Persistent objects) که آزاد نمیشوند — منبع انباشت حافظه و مکثهای بعدی هستند.
JankStats — کتابخانه Android که معیارهای فریمهای از دست رفته را در زمان واقعی جمعآوری میکند. هر jank را به سناریوی فعلی (مثلاً «اسکرول لیست»، «باز کردن صفحه») متصل میکند که این امکان را میدهد بفهمیم کند شدن دقیقاً در کدام عمل رخ میدهد.
مثال ادغام JankStats برای ردیابی هنگها در Android:
class MainActivity : AppCompatActivity() {
private lateinit var jankStats: JankStats
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
jankStats = JankStats.create(this.window.decorView) { frameData ->
if (frameData.isJank()) {
Log.w("Jank", "Duration=${frameData.durationMs}ms")
}
}
}
}
رفع کند شدن نیاز به کار هدفمند با هر علت دارد. راهحل جهانی وجود ندارد — نیاز به تحلیل پروفایلهای عملکرد خاص است.
اگر لیست شامل 1000+ عنصر است و همه یکباره بارگذاری میشوند — این کند شدن تضمینی است. Paging 3 در Android و NSFetchedResultsController در iOS دادهها را در حین اسکرول به صورت تکهای بارگذاری میکنند. کاربر فقط 10–20 عنصر اول را میبیند، بقیه در پسزمینه بارگذاری میشوند.
Room امکان پروفایلسازی کوئریها را از طریق Inspection Tool در Android Studio فراهم میکند: زمان اجرا، تعداد ردیفهای برگشتی و plan کوئری قابل مشاهده است. افزودن ایندکس به ستونهای WHERE و ORDER BY میتواند زمان کوئری را از 300 ms به 5 ms کاهش دهد. در iOS بررسی مشابه توسط Core Data Profiler در Instruments انجام میشود.
همگامسازیهای پسزمینه، بارگذاری فایلها، پردازش دادهها — همه اینها باید از طریق WorkManager (Android) یا Background Tasks (iOS) انجام شوند. اگر همگامسازی در رشته UI اجرا شود، برنامه در طول اجرا کند میشود. WorkManager اجرا را در رشته پسزمینه با در نظر گرفتن وضعیت باتری و شبکه تضمین میکند.
مثال همگامسازی پسزمینه از طریق WorkManager در Android:
class SyncWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
Log.d("Sync", "همگامسازی داده در رشته پسزمینه")
syncData()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
میتوان از کند شدن در مرحله نوشتن کد با پیروی از اصول کار مؤثر با حافظه و رشتهها جلوگیری کرد.
Baseline Profiles — فهرستی از کلاسها و متدهایی است که Android از قبل (AOT) کامپایل میکند، نه JIT. بدون پروفایل، هر صفحه جدید در اولین باز شدن کامپایل میشود و باعث تأخیر 100–500 ms میشود. Baseline Profile را برای صفحات کلیدی آماده کنید و تولید را در Gradle از طریق baseline-profile-gradle-plugin فعال کنید.
Hot path — کدی است که در هر فریم اجرا میشود: onBindViewHolder، draw، layoutSubviews. از ایجاد اشیا در این متدها خودداری کنید: از پول اشیا استفاده کنید، به جای اتصال از StringBuilder استفاده کنید، رشتههای فرمت شده و فرمتها را کش کنید. هر تخصیص اضافی GC بعدی را نزدیکتر میکند.
به pipeline CI اجرای Macrobenchmark با سناریوی اسکرول لیست و باز کردن صفحه اضافه کنید. آستانه تعیین کنید: صدک 99 زمان فریم نباید از 16 ms تجاوز کند. اگر آستانه افزایش شود — build تا بهینهسازی رد میشود.
سوالات متداول
لگ — تأخیر ثابت است (مثلاً 200 ms برای هر کلیک). کند شدن — نامنظم است: برنامه عادی کار میکند، سپس ناگهان 1–3 ثانیه کند میشود، سپس دوباره عادی. علت — عوامل رویدادی مانند مکثهای GC یا کوئریهای همزمان به پایگاه داده.
از Memory Profiler در Android Studio استفاده کنید: برگه Memory رویدادهای GC را با مدت نشان میدهد. برای نظارت تولیدی، Firebase Performance Monitoring را با تریسهای سفارشی متصل کنید. در iOS Malloc Debug را فعال کنید و نسلهای تخصیص را در Instruments علامتگذاری کنید.
به طور غیرمستقیم — بله. اگر پاسخ سرور با تأخیر میرسد و UI به طور همزمان منتظر آن است، برنامه قفل میکند. اگر کوئری ناهمزمان است اما پردازش پاسخ در رشته UI انجام میشود — این نیز باعث کند شدن میشود. راهحل — پردازش ناهمزمان با coroutine و نشانگرهای پیشرفت.
در صورت استفاده نادرست KMP میتواند اشیای wrapper اضافی برای قابلیت همکاری تولید کند. در iOS این دفعات تخصیص و در نتیجه مکثهای ARC را افزایش میدهد. از @ObjCName استفاده کنید، expect/actual را بهینه کنید و از فراخوانیهای مکرر کد مشترک از مسیرهای داغ UI خودداری کنید.
افزایش heap از طریق android:largeHeap="true" GC را به تأخیر میاندازد اما علت تخصیص را از بین نمیبرد. وقتی GC در نهایت اجرا شود، مکث طولانیتر خواهد بود زیرا باید اشیای بیشتری پیمایش شود. راهحل — کاهش تعداد تخصیصها، نه گسترش heap.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.