مدیریت بهینه حافظه رم و پیشگیری از نشت حافظه (Memory Leak)، یکی از اساسی ترین شاخص های سنجش کیفیت، پایداری و مهندسی پیشرفته در سیستم عامل اندروید به شمار می رود.
نشت حافظه زمانی رخ می دهد که یک شیء پس از اتمام چرخه حیات و عدم نیاز برنامه، به دلیل نگه داری ناخواسته مرجع (Reference) توسط اشیاء ریشه ای سیستم، توسط زباله روب یا همان Garbage Collector آزاد نمی شود.
تجمع تدریجی این اشیاء رها نشده در فضای رم (Heap)، در نهایت باعث کاهش چشمگیر سرعت پردازش، لکنت فریم های انیمیشن و در نهایت پرتاب خطای فاجعه بار OutOfMemoryError می گردد.
بر اساس گزارش رسمی تیم مهندسی اندروید در شرکت گوگل، بیش از ۳۸٪ از کرش های غیرمنتظره اپلیکیشن های تجاری ناشی از خطای کمبود حافظه (OutOfMemoryError) و انسداد زباله روب است.
شناسایی دقیق عامل نشت حافظه در پروژه های بزرگ، مستلزم تسلط بر ابزار های پیشرفته ای نظیر Android Studio Profiler و کتابخانه خودکار LeakCanary است.
در این راهنمای مرجع و تخصصی در حوزه برنامه نویسی اندروید، کالبدشکافی عمیق سازوکار زباله روب ART، نحوه تحلیل فایل های Heap Dump، پیاده سازی کامل LeakCanary و الگو های رفع نشت در جت پک کامپوز و کاتلین را بررسی می نماییم.
دستاوردهای کلیدی و آموزه های فنی این راهنما
- کالبدشکافی زنجیره مراجع GC Roots: درک نحوه نگه داری ناخواسته اشیاء منهدم شده توسط ریشه های سیستم عامل اندروید.
- شناسایی خودکار با LeakCanary: نصب و تحلیل آنی فایل های لاگ نشت حافظه بدون نیاز به نوشتن حتی یک خط کد اضافی.
- تحلیل حرفه ای با Memory Profiler: ثبت و مقایسه اسنپ شات های Heap Dump و تفکیک اندازه Shallow Size از Retained Size.
- پاکسازی نشت های رایج در کاتلین: اصلاح مراجع استاتیک، لیسنرهای ثبت نشده و کارهای لغو نشده کوروتین.
- مدیریت حافظه در Jetpack Compose: پیشگیری از نشت کلوژرها با متد rememberUpdatedState و کنترل افکت های جانبی.
۱. کالبدشکافی نشت حافظه و زنجیره مراجع GC Roots در اکوسیستم اندروید
برای ریشه یابی نشت حافظه، ابتدا باید درک عمیقی از مدل مدیریت حافظه در ماشین مجازی اندروید (ART - Android Runtime) داشته باشیم.
محیط اجرای اندروید حافظه را به دو بخش کلی تقسیم می کند: حافظه استک (Stack) برای متغیرهای محلی و فراخوانی متدها، و حافظه هیپ (Heap) برای نمونه سازی تمامی اشیاء داینامیک.
زباله روب (Garbage Collector) به صورت دوره ای در پس زمینه اجرا می شود تا اشیایی را که دیگر برای برنامه در دسترس نیستند نابود کرده و فضای رم را آزاد سازد.
مبنای سنجش زباله روب، الگوریتم پیمایش گراف دسترس پذیری از طریق نقاط ریشه (GC Roots) است.
نقاط ریشه یا GC Roots شامل تریدهای فعال، متغیرهای استاتیک کلاس ها، اشیاء زنده در حافظه Native و نمونه های فعال JNI هستند.
اگر یک شیء منهدم شده نظیر اکتیویتی حتی از طریق یک مسیر باریک به یکی از این ریشه ها متصل بماند، زباله روب قادر به آزادسازی آن نخواهد بود.
از آن جا که هر اکتیویتی حاوی رفرنس مستقیم به کل درخت ویوها (View Hierarchy)، تصاویر بیت مپ و کانتکست برنامه است، نشت یک اکتیویتی می تواند چند ده مگابایت رم را مسدود کند.
سیستم مدیریت حافظه در ماشین مجازی ART از الگوریتم چندنسلی (Generational GC) بهره می برد. اشیاء تازه ساخته شده در فضای نسل جوان (Young Generation) قرار می گیرند و در صورت بقا پس از چند چرخه زباله روبی، به نسل پیر (Old Generation) منتقل می شوند.
زمانی که یک اکتیویتی نشت می کند، زباله روب مجبور می شود فرآیند Full GC را در سراسر فضای نسل پیر اجرا کند که این عملیات سنگین باعث توقف های چند ده میلی ثانیه ای پردازنده و افت فریم شدید در انیمیشن ها می گردد.
۲. ماتریس مقایسه ابزار های عیب یابی حافظه: LeakCanary در برابر Profiler
برای مقابله با نشت های حافظه، توسعه دهندگان دو ابزار بنیادین در اختیار دارند: ابزار اتوماتیک LeakCanary و ابزار تحلیلی Android Studio Profiler.
جدول زیر نقاط قوت، کاربردها و تفاوت های ساختاری این دو فناوری را در سال ۲۰۲۶ مقایسه می کند:
| شاخص ارزیابی فنی | کتابخانه خودکار LeakCanary | ابزار Android Studio Memory Profiler |
|---|---|---|
| نحوه کشف و رصد | کاملاً خودکار در پس زمینه با ObjectWatcher | دستی از طریق اتصال کابل یا وای فای به IDE |
| تاثیر بر عملکرد اجرا | بسیار ناچیز در حین اجرا (توقف کوتاه هنگام Dump) | مصرف منابع سیستم برای ثبت تایم لاین بلادرنگ |
| محیط استفاده بهینه | نسخه های Debug و تست های روزمره تیمی | تحلیل عمیق، مقایسه هیپ دپ ها و بنچمارک دقیق |
| نحوه نمایش خروجی | نوتیفیکیشن گوشی با گراف ساده ریشه نشت (LeakTrace) | نمودار درختی جامع کلاس ها، بایت ها و متدهای کالبک |
| نیاز به کد نویسی | صفر خط کد (نصب فقط از طریق دپندنسی گریدل) | تنظیم فلگ های دیباگ و فعال سازی پروفایلر در بیلد |
| تشخیص اشیاء کاتلین و کامپوز | پشتیبانی هوشمند از Fragment, ViewModel و View | پشتیبانی سطح پایین از اشیاء Native و Java/Kotlin |
رویکرد استاندارد مهندسی در شرکت های طراز اول، استفاده همزمان از هر دو ابزار است: LeakCanary برای کشف زودهنگام باگ ها و Profiler برای آنالیز ریشه ای.
۳. مفاهیم بنیادی تحلیل حافظه: تفاوت Shallow Size و Retained Size
هنگام بررسی گزارش های حافظه، با دو مفهوم کلیدی مواجه می شوید که درک نادرست آن ها باعث سردرگمی در رفع باگ می گردد:
- اندازه سطحی (Shallow Size): مقدار حافظه ای است که صرفاً خود شیء برای نگه داری فیلدها و ساختار داخلی خود مصرف می کند (بدون احتساب اشیایی که به آن ها ارجاع داده است). برای مثال، یک نمونه ساده از اکتیویتی ممکن است تنها چند صد بایت Shallow Size داشته باشد.
- اندازه نگه داری شده (Retained Size): کل مقدار حافظه ای است که در صورت نابود شدن این شیء، به صورت خودکار توسط زباله روب آزاد خواهد شد. این عدد شامل تمام اشیاء فرزند، ویوها، تکسچرها و بیت مپ هایی است که منحصراً توسط این شیء زنده مانده اند.
در هنگام رفع نشت حافظه، همواره باید تمرکز اصلی خود را بر روی اشیایی با بالاترین Retained Size قرار دهید، زیرا آزاد کردن آن ها بیشترین فضای رم را احیا می نماید.
تفاوت این دو پارامتر در اشیاء تصویری نمود بارزتری دارد؛ یک شیء Bitmap در کاتلین ممکن است اندازه سطحی ناچیزی در حدود ۳۲ بایت داشته باشد، اما اندازه نگه داری شده آن در حافظه پیکسلی به بیش از ۲۰ مگابایت برسد.
۴. آموزش عملی Android Studio Memory Profiler و استخراج Heap Dump
ابزار Memory Profiler درون محیط اندروید استودیو، خط زمانی زنده ای از نحوه مصرف رم بر تفکیک بایت های کد، تصاویر و داده های Native ارائه می دهد.
برای شروع عیب یابی، ابتدا اپلیکیشن را در حالت دیباگ اجرا کرده و از زبانه پایین IDE، تب Profiler را باز نمایید:
مراحل گام به گام ضبط و کشف نشت حافظه در پروفایلر به شرح زیر است:
- تحریک سناریوی نشت: صفحه مورد نظر را باز کنید، چندین بار گوشی را بچرخانید (Rotation) و سپس با زدن دکمه Back از صفحه خارج شوید.
- فراخوانی اجباری زباله روب: بر روی آیکون سطل زباله (Initiate GC) در بالای پروفایلر کلیک کنید تا مطمئن شوید اشیاء موقت پاکسازی شده اند.
- ثبت اسنپ شات هیپ (Capture Heap Dump): گزینه Dump Java Heap را انتخاب کرده و دکمه Record را بزنید تا فایل اسنپ شات
.hprofایجاد شود. - فیلتر کردن اکتیویتی های نشت یافته: در پنجره باز شده، تیک گزینه
Activity/Fragment Leaksرا فعال کنید. اگر اکتیویتی بسته شده همچنان در لیست با تعداد نمونه بزرگ تر از صفر وجود داشته باشد، با یک نشت قطعی روبرو هستید.
یکی از قابلیت های پیشرفته پروفایلر اندروید استودیو، امکان مقایسه دو فایل هیپ دپ (Diffing Snapshots) است. شما می توانید یک اسنپ شات قبل از ورود به صفحه و یک اسنپ شات پس از خروج ثبت کنید.
با انتخاب حالت Compare، اندروید استودیو تمامی اشیایی را که در طول این فرآیند ایجاد شده و پس از بستن صفحه همچنان در حافظه باقی مانده اند با علامت مثبت قرمز رنگ مشخص می سازد.
علاوه بر حافظه جاوایی، توسعه دهندگان باید به بخش حافظه گرافیکی (Graphics Memory) و حافظه نیتیو (Native Memory) نیز توجه ویژه داشته باشند.
در نسخه های مدرن اندروید، بافرهای پیکسلی تصاویر بیت مپ مستقیماً در حافظه نیتیو نگه داری می شوند تا فضای هیپ اپلیکیشن پر نشود.
اگر شیء Bitmap در لایه کاتلین نشت کند، فضای نیتیو متناظر با آن نیز قفل می ماند و باعث افزایش ناگهانی مصرف رم در بخش Graphics نمودار پروفایلر می گردد.
۵. راه اندازی و پیکربندی بدون کد LeakCanary در پروژه های مدرن
کتابخانه LeakCanary شاهکاری از شرکت Square است که فرآیند کشف نشت حافظه را به صورت صد درصد خودکار درمی آورد.
شگفت انگیزترین ویژگی LeakCanary، عدم نیاز به حتی یک خط کد جاوا یا کاتلین برای راه اندازی اولیه است.
تنها کافی است وابستگی زیر را در بخش build.gradle.kts ماژول اپلیکیشن اضافه کنید:
// build.gradle.kts (Module: app)
dependencies {
// وابستگی LeakCanary منحصراً برای سورس ست debug
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
}
به کارگیری کلمه کلیدی debugImplementation تضمین می کند که کدهای این ابزار سنگین هرگز وارد نسخه پروداکشن انتشار یافته در مارکت ها نخواهند شد.
کتابخانه LeakCanary با بهره گیری از مکانیزم App Startup اندروید، در هنگام بالا آمدن برنامه به صورت نامحسوس مقداردهی اولیه می شود و به چرخه حیات تمامی اکتیویتی ها و فرگمنت ها گوش می دهد.
۶. سازوکار داخلی LeakCanary: از ObjectWatcher تا موتور تحلیلی Shark
درک نحوه کارکرد داخلی LeakCanary به شما کمک می کند هشدارهای آن را به درستی تفسیر کنید:
پایپ لاین کشف نشت شامل ۵ گام مهندسی دقیق است:
- رصد شیء با ObjectWatcher: هنگامی که متد
onDestroyیک اکتیویتی اجرا می شود، LeakCanary یک رفرنس ضعیف (KeyedWeakReference) به آن ایجاد می کند. - مهلت زمانی ۵ ثانیه ای: سیستم ۵ ثانیه صبر می کند و سپس یک بار زباله روب را به صورت پس زمینه فعال می سازد.
- بررسی زنده بودن رفرنس: اگر پس از ۵ ثانیه شیء همچنان در حافظه باقی مانده باشد، به عنوان شیء باقی مانده (Retained Object) علامت گذاری می شود.
- ثبت خودکار Dump Heap: با رسیدن تعداد اشیاء باقی مانده به عدد آستانه (پیش فرض ۵ عدد)، یک فایل هیپ دپ روی حافظه گوشی ذخیره می شود.
- تحلیل با موتور Shark: موتور بومی و فوق العاده سریع Shark فایل هیپ را پردازش کرده، کوتاه ترین مسیر اتصال به GC Root را پیدا می کند و یک اعلان نوتیفیکیشن با جزئیات کامل به توسعه دهنده نمایش می دهد.
کتابخانه LeakCanary با تکیه بر موتور Shark، زمان پردازش فایل هیپ دپ را تا ۸۲٪ کاهش داده و مصرف رم در زمان تحلیل را به زیر ۲۵ مگابایت محدود کرده است.
موتور Shark با خواندن مستقیم متادیتاهای باینری فایل .hprof بدون ساخت نمونه های جاوا در حافظه رم، سرعت جستجوی مسیرهای ریشه نشت را به چند ثانیه می رساند.
برای سفارشی سازی رفتارهای پیش فرض LeakCanary، می توان در کلاس Application تنظیمات اختصاصی اعمال نمود:
// سفارشی سازی آستانه ثبت هیپ دپ در LeakCanary
class DebugApp : Application() {
override fun onCreate() {
super.onCreate()
LeakCanary.config = LeakCanary.config.copy(
retainedVisibleThreshold = 3,
dumpHeap = true
)
}
}
کاهش پارامتر retainedVisibleThreshold به عدد ۳، موجب می شود فرآیند عیب یابی در تست های تیمی سریع تر آغاز گردد.
همچنین در بسیاری از دیوایس های سامسونگ و شیائومی، نشت هایی در لایه سرویس های فریم ورک کارخانه رخ می دهد که خارج از کنترل برنامه نویس است.
موتور Shark با بهره گیری از لیست هوشمند AndroidReferenceMatchers، این نشت های سیستمی را فیلتر کرده و منحصراً کدهای پروژه را ردیابی می نماید.
۷. تحلیل و رفع رایج ترین الگو های نشت حافظه در اپلیکیشن های اندروید
اکنون به بررسی خطرناک ترین الگو های کد نویسی که منجر به نشت حافظه می شوند و نحوه بازنویسی استاندارد آن ها می پردازیم:
الگوی اول: نگه داری Context اکتیویتی در متغیرهای استاتیک یا شیء Singleton
اگر کانتکست یک اکتیویتی را درون یک فیلد استاتیک ذخیره کنید، با خروج کاربر از صفحه، اکتیویتی تا پایان عمر پروسه اپلیکیشن در حافظه قفل خواهد شد.
برای رفع این نقص، باید همواره از applicationContext استفاده کرده یا ارتباط را بر پایه اینترفیس های ایزوله معماری تمیز بازطراحی نمایید:
// کد نادرست: نشت مستقیم اکتیویتی در شیء سینگلتون
object BadLocationManager {
private var context: Context? = null // نشت وحشتناک اکتیویتی!
fun init(ctx: Context) { context = ctx }
}
// کد صحیح و بهینه: استفاده از ApplicationContext ایمن
class SafeLocationManager private constructor(context: Context) {
private val appContext = context.applicationContext
companion object {
@Volatile private var instance: SafeLocationManager? = null
fun getInstance(context: Context): SafeLocationManager =
instance ?: synchronized(this) {
instance ?: SafeLocationManager(context).also { instance = it }
}
}
}
استفاده از کانتکست اپلیکیشن تضمین می کند که هیچ پیوندی به لایف سایکل بصری اکتیویتی برقرار نماند.
۸. پیشگیری از نشت حافظه در Coroutines و Kotlin Flow
با وجود اینکه کاتلین فرآیند برنامه نویسی همزمانی را دگرگون ساخته است، بی دقتی در انتخاب اسکوپ ها می تواند زمینه ساز نشت های پنهان شود.
استفاده از GlobalScope یکی از بزرگ ترین خطاهای برنامه نویسی است، زیرا جاب های راه اندازی شده با آن به چرخه حیات هیچ صفحه ای متصل نبوده و پس از بسته شدن صفحه به اجرای خود ادامه می دهند:
// کد نادرست: اجرای جاب در GlobalScope بدون امکان لغو
class LeakyDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GlobalScope.launch {
// این کلوژر رفرنس مستقیم به اکتیویتی را تا ابد نگه می دارد!
delay(60000)
updateUiState()
}
}
}
// کد صحیح: اتصال کار به چرخه حیات با lifecycleScope
class SafeDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
// با فراخوانی onDestroy، این کوروتین فوراً کنسل می شود
delay(60000)
updateUiState()
}
}
}
به لطف معماری متکی بر کروتین و فلو در کاتلین، بهره گیری از lifecycleScope در اکتیویتی ها و viewModelScope در لایه مدل، ابطال خودکار جاب ها را در زمان انهدام تضمین می نماید.
علاوه بر این، در هنگام جمع آوری استریم های StateFlow یا SharedFlow در فرگمنت ها یا اکتیویتی ها، نباید مستقیماً از متد lifecycleScope.launch به تنهایی استفاده کرد.
الگوی استاندارد و امن گوگل، استفاده از اکستنشن repeatOnLifecycle است:
// گوش دادن ایمن به Flow با تابع repeatOnLifecycle
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
renderState(state)
}
}
}
این ساختار موجب می شود هنگامی که صفحه به پس زمینه می رود (onStop)، مصرف استریم و تخصیص منابع متوقف شود و با بازگشت کاربر (onStart) مجدداً برقرار گردد.
۹. چالش های نشت حافظه در دنیای نوین Jetpack Compose
برخی از توسعه دهندگان تصور می کنند چون کامپوز بر پایه توابع رندر می شود و ویوهای سنتی XML را حذف کرده، خطر نشت حافظه به صفر رسیده است.
اما در عمل، کلوژرهای به کار رفته در افکت های جانبی نظیر LaunchedEffect و DisposableEffect در صورت عدم مدیریت صحیح، مراجع قدیمی را در طول بازترکیب های مکرر (Recomposition) در حافظه محبوس می کنند:
// جلوگیری از نشت کلوژر در کامپوز با rememberUpdatedState
@Composable
fun TimerListener(onTimeout: () -> Unit) {
// گرفتن رفرنس به روز رسانی شده بدون ریست کردن افکت
val currentOnTimeout by rememberUpdatedState(onTimeout)
LaunchedEffect(Unit) {
delay(10000)
currentOnTimeout()
}
}
همچنین در صورت ثبت لیسنرهای سیستمی مانند برودکست رسیورها درون کامپوز، استفاده از DisposableEffect برای لغو ثبت (Unregister) در بلاک onDispose الزامی است:
// آزادسازی لیسنرها در زمان خروج از درخت کامپوزیشن
@Composable
fun SystemBroadcastObserver(context: Context, receiver: BroadcastReceiver) {
DisposableEffect(context) {
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
context.registerReceiver(receiver, filter)
onDispose {
context.unregisterReceiver(receiver) // ممانعت قطعی از نشت
}
}
}
۱۰. بهینه سازی مصرف حافظه در لایه داده و شبکه: Room و رتروفیت
در لایه مخزن داده و دسترسی به بانک اطلاعاتی، نگه داری کوئری های باز یا لود کردن مستقیم دیتاهای حجیم در رم عامل اصلی پر شدن Heap است.
هنگام کار با دیتابیس Room در اندروید، نباید لیست های بزرگ حاوی تصاویر یا متون طولانی را در یک کوئری ساده بازخوانی کرد.
پنجره مکان نما (CursorWindow) در پایگاه داده SQLite اندروید دارای محدودیت حجم فیزیکی ۲ مگابایت است.
اگر کوئری شما داده هایی بزرگ تر از این آستانه بازگرداند، سیستم با خطای استثنای CursorWindowAllocationException متوقف می شود.
رویکرد استاندارد، واگذاری مدیریت حافظه به صفحه بندی داده ها است تا آیتم ها به صورت تدریجی لود شده و رکوردهای دور از صفحه به صورت خودکار از رم بازیافت شوند.
همچنین در لایه ارتباطات شبکه ای با کتابخانه رتروفیت Retrofit، باید از کش کردن پاسخ های باینری سنگین در اینترسپتورهای OkHttp بدون محدودیت حجم حافظه خودداری کرد.
نشت کرسر دیتابیس (Cursor Leak) یکی از باگ های کلاسیک اما به شدت پرتکرار در اپلیکیشن های اندرویدی است.
هنگامی که یک شیء Cursor در دیتابیس SQLite باز می شود اما در بلوک finally یا با متد use بسته نمی شود، فایل دیسک باز می ماند.
خوشبختانه با بهره گیری از کتابخانه Room و معماری متکی بر Flow و suspend functions، چرخه بستن کرسرها به صورت خودکار توسط فریم ورک مدیریت می شود.
۱۱. تست های خودکار و اعتبارسنجی نشت حافظه در پایپ لاین CI/CD
کتابخانه LeakCanary دارای ابزار تحلیلی بی نظیری به نام LeakSentry / LeakWatcher برای محیط تست نویسی است.
همان طور که در راهنمای جامع تست نویسی واحد در اندروید تشریح شده است، تست های خودکار سنگ بنای پایداری نرم افزار هستند.
شما می توانید با نوشتن تست های ابزاری (Instrumentation Tests)، وقوع نشت حافظه را پیش از انتشار نسخه نهایی در پایپ لاین CI بررسی کنید:
// تست اعتبارسنجی نشت حافظه با ابزار تستی LeakCanary
@RunWith(AndroidJUnit4::class)
class MainActivityLeakTest {
@Test
fun openAndCloseActivity_assertNoMemoryLeaks() {
val scenario = ActivityScenario.launch(MainActivity::class.java)
scenario.close()
// راستی آزمایی عدم باقی ماندن رفرنس اکتیویتی در حافظه
AppWatcher.objectWatcher.expectWeaklyReachable(
scenario,
"MainActivity must be garbage collected after finish"
)
}
}
در صورتی که اکتیویتی پس از بسته شدن آزاد نشود، تست بلافاصله Fail شده و مانع از ادغام کدهای معیوب می گردد.
۱۲. اشتباهات مهلک و خطاهای رایج پروداکشن در مدیریت حافظه
بررسی کدهای اپلیکیشن های ناموفق نشان می دهد که خطاهای زیر در بیش از ۸۵ درصد پروژه ها تکرار می شوند:
- نگه داری رفرنس View در فرگمنت ها پس از onDestroyView: ویوهای فرگمنت باید در متد onDestroyView برابر null قرار گیرند؛ در غیر این صورت تا زمان نابودی کامل فرگمنت والد نشت می کنند.
- تعریف کلاس های داخلی غیر استاتیک (Non-static Inner Classes): کلاس های داخلی پیش فرض در جاوا رفرنس پنهانی به کلاس والد دارند که باید با کلمه کلیدی static یا در کاتلین با کلاس های مجزا جایگزین شوند.
- فراموش کردن لغو لیسنرهای Location و سنسورها: عدم فراخوانی متد removeUpdates در زمان onPause یا onStop.
- بارگذاری تصاویر Bitmap با ابعاد اصلی: دیکود کردن عکس های با رزولوشن اصلی بدون استفاده از کتابخانه های بهینه نظیر Coil.
۱۳. چک لیست نهایی ارزیابی پایداری و سلامت حافظه اپلیکیشن
برای تضمین رهایی از باگ های نشت حافظه قبل از انتشار هر ریلیز جدید، چک لیست زیر را به دقت ارزیابی نمایید:
- فعال سازی LeakCanary در بیلد Debug: بررسی لاگ ها حین تست های دستی و تایید صفر بودن نشت ها.
- تست چرخش صفحه (Orientation Stress Test): چرخاندن پی در پی صفحات کلیدی و بررسی پایدار ماندن مصرف رم در Profiler.
- بازبینی کانتکست ها: اطمینان از عدم پاس دادن اکتیویتی به سینگلتون ها و استفاده از ApplicationContext.
- پاکسازی اشتراک ها: اعتبارسنجی لغو ثبت تمامی EventBus ها، BroadcastReceiver ها و Coroutine Job ها در متدهای پایانی لایف سایکل.
پرسش های متداول و نکات کلیدی
۱. تفاوت نشت حافظه (Memory Leak) با مصرف بالای رم (High Memory Usage) چیست؟
مصرف بالای رم یعنی برنامه داده های زیادی را به درستی برای پردازش بارگذاری کرده که پس از پایان کار آزاد می شوند، اما نشت حافظه یعنی داده های بلااستفاده به دلیل وجود مراجع سرگردان هرگز آزاد نمی گردند.
۲. چرا LeakCanary نباید در نسخه ریلیز (Release Build) فعال باشد؟
زیرا پروسه ثبت اسنپ شات هیپ دپ باعث فریز چند ثانیه ای رابط کاربری می شود و مصرف منابع پردازشی دستگاه کاربر را به شدت افزایش می دهد.
۳. منظور از LeakTrace در گزارش های LeakCanary چیست؟
زنجیره کاملی از ارجاعات است که نشان می دهد دقیقاً کدام متغیر استاتیک یا شیء زنده، شیء نابود شده را در حافظه نگه داشته و مانع از جمع آوری آن توسط زباله روب شده است.
۴. آیا استفاده از WeakReference راهکار مناسبی برای رفع نشت حافظه است؟
بله؛ مراجع ضعیف مانع از جمع آوری شیء توسط زباله روب نمی شوند و برای کش های موقت یا کالبک هایی با چرخه حیات نامشخص بسیار ایده آل هستند.
۵. چگونه نشت حافظه در فرگمنت ها را شناسایی و برطرف کنیم؟
با صفر کردن متغیرهای ViewBinding در متد onDestroyView فرگمنت و استفاده از viewLifecycleOwner به جای کانتکست والد برای مشاهده داده های لایو دیتا و فلو.
۶. علت پرتاب خطای OutOfMemoryError هنگام بارگذاری تصاویر چیست؟
بیت مپ ها حافظه پیکسلی بسیار بالایی اشغال می کنند؛ اگر اندازه تصویر قبل از دیکود کردن با inSampleSize فشرده نشود یا از کش مناسب استفاده نگردد، رم پر می شود.
۷. آیا کتابخانه Coil از نشت حافظه جلوگیری می کند؟
بله؛ کویل به صورت خودکار به لایف سایکل متصل می شود، تصاویر را متناسب با اندازه دقیق View در حافظه بارگذاری کرده و در زمان خروج از صفحه رم را آزاد می سازد.
۸. متد rememberUpdatedState در کامپوز چگونه مانع از نشت کلوژر می شود؟
این تابع بدون ریست کردن افکت های دیرپا نظیر LaunchedEffect، آخرین نسخه لامبدا یا پارامتر ورودی را در دسترس نگه می دارد و از انباشت مراجع قدیمی جلوگیری می کند.
۹. تفاوت اندازه Shallow Size و Retained Size در چیست؟
شلو سایز حجم فیزیکی خود شیء را نشان می دهد، در حالی که ریتیند سایز مجموع حجم کل اشیایی است که با نابودی این شیء توسط زباله روب آزاد خواهند شد.
۱۰. آیا ابزار Android Studio Profiler بدون کابل و به صورت وای فای کار می کند؟
بله؛ با فعال سازی قابلیت Wireless Debugging در اندروید ۱۱ به بعد، می توان اپلیکیشن را بدون کابل مستقیماً با پروفایلر متصل و عیب یابی نمود.
امتیاز و نظرات
هنوز نظری ثبت نشده است. اولین نفر باشید.