لگ در برنامه موبایل تأخیر قابل توجهی بین عمل کاربر و واکنش رابط است که به دلیل بار بیش از حد رشته اصلی، نشت حافظه یا عملیات غیربهینه ورودی-خروجی رخ میدهد. برخلاف باگهای مرتبط با خطاهای منطقی، لگ یک مشکل عملکرد است: برنامه به درستی اما کند کار میکند. طبق گزارش AppDynamics Mobile App Performance 2024، 62٪ کاربران برنامه را حذف میکنند اگر بیش از 3 ثانیه کند کار کند. تشخیص لگها نیاز به پروفایلسازی CPU، حافظه و شبکه با استفاده از Android Studio Profiler و Xcode Instruments دارد.
نکات اصلی
لگ (از انگلیسی lag) در برنامه موبایل تأخیر قابل احساس ذهنی بین عمل کاربر (لمس، سوایپ، وارد کردن متن) و واکنش رابط است. از نظر فنی لگ به عنوان زمان بین رویداد ورودی و رندر کامل فریم اندازهگیری میشود: آستانه راحت — تا 100 میلیثانیه، قابل توجه — از 200 میلیثانیه، بحرانی — بیش از 500 میلیثانیه.
در اصطلاح کاربران «لگ میزند» و «کند کار میکند» اغلب به عنوان مترادف استفاده میشوند، اما از نظر فنی لگ یک تأخیر ثابت است (مثلاً 300 میلیثانیه در هر کلیک)، و «کند کار میکند» کاهش سرعت نامتناوب است: برنامه گاهی روان کار میکند، گاهی یک ثانیه قفل میکند. باگ بر خلاف لگ به سرعت مربوط نیست، بلکه به صحت نمایش مربوط میشود.
Google Play و App Store شاخصهای عملکرد را در رتبهبندی برنامهها در نظر میگیرند. نرخ ANR، تعداد jank و زمان راهاندازی بر دیده شدن در جستجو و تبدیل نصب تأثیر میگذارد. برنامه با لگهای مداوم تا 40٪ از کاربران را پس از اولین راهاندازی از دست میدهد.
لگها زمانی رخ میدهند که رشته اصلی UI نمیتواند فریمها را با نرخ 60 FPS (16.6 میلیثانیه به ازای هر فریم) یا 120 FPS (8.3 میلیثانیه) پردازش کند. منابع اصلی تأخیر را بررسی میکنیم.
هر عملیات همزمان در رشته UI — خواندن از SharedPreferences، کار با پایگاه داده از طریق Room بدون suspend، رمزگشایی تصویر به Bitmap — رندر فریم را مسدود میکند. در Android این منجر به جا افتادن فریمها (jank) میشود، در iOS — به تأخیر رندر Core Animation.
وقتی Garbage Collector در Android یا ARC در iOS حافظه را آزاد میکند، همه رشتهها متوقف میشوند. توقفهای مکرر GC هنگام ایجاد تعداد زیادی اشیاء موقت رخ میدهد — مثلاً در هر فراخوانی آداپتور لیست یک نمونه جدید ViewHolder ایجاد میشود. این خود را به صورت پیمایش تند نشان میدهد.
ConstraintLayoutهای تو در تو، LinearLayoutهای متعدد، Viewهای همپوشان — هر تو رفتگی زمان measure و layout pass را افزایش میدهد. Xcode نشان میدهد که سلسله مراتب عمیق لایهها (بیش از 10 سطح) باعث کاهش 20-30٪ FPS میشود.
برای شناسایی علل لگ از پروفایلرهای تعبیه شده در IDE و ابزارهای نظارت سیستمی استفاده میشود. هر ابزار وظیفه خود را حل میکند.
CPU Profiler نشان میدهد کدام متدها زمان پردازنده را میگیرند و در کدام رشتهها اجرا میشوند. اگر متدی با محاسبات سنگین در main thread اجرا شود — این ریشه مشکل است. ضبط ردیابی با sample Java Method فعال شده امکان مشاهده پشته فراخوانی را در هر لحظه و یافتن «نقاط داغ» فراهم میکند.
ابزار مشابه برای iOS — Time Profiler — هر میلیثانیه نمونههایی از پشته جمعآوری میکند و نشان میدهد هر متد چند درصد از زمان CPU را میگیرد. ترکیب با پرچم Main Thread Only فقط عملیات روی رشته اصلی را فیلتر میکند که مستقیماً منابع لگ را نشان میدهد.
درخواستهای کند شبکه حتی اگر رشته UI مسدود نشده باشد، احساس لگ ایجاد میکنند. Network Profiler در Android Studio و Network Link Conditioner در Xcode امکان شبیهسازی اتصال کند و بررسی رفتار برنامه در شرایط واقعی را فراهم میکنند. پاسخهای تکهای بدون پیشرفت و بارهای JSON بزرگ — منابع معمول لگهای ظاهری.
مثال پروفایلسازی درخواست شبکه با OkHttp با اندازهگیری زمان:
class TimingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val start = System.nanoTime()
val response = chain.proceed(chain.request())
val duration = (System.nanoTime() - start) / 1_000_000
Log.d("زمانبندی", "درخواست $duration میلیثانیه طول کشید")
return response
}
}
رفع لگ نیاز به کار سیستماتیک دارد: از بهینهسازی یک متد واحد تا تغییرات معماری. مؤثرترین تکنیکها را بررسی میکنیم.
Kotlin Coroutines با dispatcher Dispatchers.IO برای درخواستهای شبکه و Dispatchers.Default برای محاسبات تضمین میکنند که رشته اصلی برای UI آزاد میماند. در iOS Grand Central Dispatch با queue .global(qos: .userInitiated) برای وظایف پسزمینه و .main برای بهروزرسانی UI — رویکرد استاندارد. از عملیات sync بین صفها خودداری کنید.
RecyclerView در Android و UICollectionView در iOS نیاز به پیکربندی صحیح دارند: ViewHolder با حداقل ایجاد اشیاء در onBindViewHolder، DiffUtil برای محاسبه تغییرات، prefetching برای بارگذاری زودهنگام دادهها. در iOS از diffable data source برای بهروزرسانیهای متحرک بدون مدیریت دستی استفاده کنید.
بارگذاری همان تصویر در هر پیمایش — لگ تضمینی. Coil (Android) و Kingfisher (iOS) تصاویر را در حافظه و دیسک کش میکنند و نمایش فوری را در درخواست مجدد تضمین میکنند. برای دادهها از Room با لایه کش بر اساس Flow یا Combine استفاده کنید.
مثال تنظیم کش تصاویر با Coil در Android:
val imageLoader = ImageLoader(context) {
memoryCachePolicy(CachePolicy.ENABLED)
diskCachePolicy(CachePolicy.ENABLED)
crossfade(true)
size(512, 512)
}
// بارگذاری با کش خودکار فعال
imageView.load("https://example.com/image.jpg") {
placeholder(R.drawable.placeholder)
error(R.drawable.error)
}
پیشگیری از لگ ارزانتر از رفع آن در تولید است. اقدامات پیشگیرانه در سطح ابزارها و معماری در فرآیند توسعه گنجانده میشوند.
StrictMode — ابزار داخلی Android که عملیات تصادفی ورودی-خروجی و فراخوانیهای شبکه در رشته اصلی را در مرحله توسعه شناسایی میکند. آن را در Application.onCreate با سیاست penaltyDeath برای نقضهای بحرانی فعال کنید. این تنها راه تضمین این است که توسعهدهنده مشکل را قبل از commit ببیند.
مشابه برای iOS — Main Thread Checker در Xcode که بخشی از Runtime Sanitization است. این به طور خودکار بررسی میکند که همه فراخوانیهای UIKit و AppKit از رشته اصلی انجام میشوند. آن را در طرح ساخت Debug فعال کنید و به هشدارهای صفر در CI برسید.
به خط لوله CI اجرای Macrobenchmark (Android) و XCTMetrics (iOS) را برای اندازهگیری زمان راهاندازی، FPS پیمایش و استفاده از حافظه اضافه کنید. آستانهها را تعیین کنید: اگر commit جدید زمان راهاندازی را بیش از 5٪ افزایش دهد — ساخت ناموفق است.
سؤالات متداول
لگ یک احساس ذهنی تأخیر است که حتی با FPS بالا ممکن است رخ دهد اگر تأخیر ناشی از زمان پردازش ورودی باشد نه رندر. FPS پایین (کمتر از 30 فریم/ثانیه) — یکی از علل لگ است، اما نه تنها علت.
از Frame Timing API در Android (Choreographer) و CADisplayLink در iOS برای اندازهگیری زمان بین فریمها استفاده کنید. Google Play Vitals نرخ jank را در شرایط واقعی نشان میدهد. برای اندازهگیریهای دقیق از Macrobenchmark با سناریوهای پیمایش استفاده کنید.
دستگاههای قدیمی هستههای CPU کمتر، RAM کمتر و حافظه کندتری دارند. عملیاتی که در یک پرچمدار 5 میلیثانیه طول میکشد، در یک دستگاه اقتصادی ممکن است 50 میلیثانیه طول بکشد. عملکرد را در دستگاههای پایین رده تست کنید و برای کامپایل AOT Baseline Profiles تنظیم کنید.
بله، این یکی از مؤثرترین روشها است. تصاویر با وضوح بالا حافظه و زمان CPU زیادی برای رمزگشایی میگیرند. از downscale به اندازه View، فرمتهای WebP (Android) و HEIC (iOS) و همچنین کش از طریق Coil یا Kingfisher استفاده کنید.
SwiftUI به طور خودکار بهروزرسانیها را از طریق diffing بهینه میکند که خطر لگ را در هنگام تغییر داده کاهش میدهد. با این حال سلسله مراتب پیچیده و بازسازی مکرر body میتواند باعث کاهش FPS شود. UIKit کنترل بیشتری بر عملکرد میدهد اما نیاز به بهینهسازی دستی دارد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.