فریزر برنامه‌های حافظه نهان

‫Android 11 (سطح میانای برنامه کاربردی ۳۰) یا بالاتر از «فریز کردن برنامه‌های حافظه نهان» پشتیبانی می‌کند. این ویژگی اجرای فرایندهای ذخیره‌شده در حافظه نهان را متوقف می‌کند و استفاده از منابع را توسط برنامه‌هایی که ممکن است درحالی‌که در حافظه نهان هستند سعی کنند کار کنند کاهش می‌دهد.

«فریزکننده برنامه‌های حافظه نهان» برنامه‌ها را در «حافظه دسترسی تصادفی» نگه می‌دارد و درعین‌حال آن‌ها را از «واحد پردازش مرکزی» خاموش نگه می‌دارد. اگر Android تشخیص دهد که برنامه‌ای نباید کار کند اما ممکن است در آینده به آن نیاز باشد، فرایند برنامه را به‌جای بستن آن متوقف می‌کند. این کار از شروع سرد وقتی برنامه دوباره موردنیاز است جلوگیری می‌کند.

‫Android با انتقال پردازش‌های برنامه‌های ذخیره‌شده در حافظه نهان به cgroup منجمد، برنامه‌های ذخیره‌شده در حافظه نهان را منجمد می‌کند. این کار مصرف واحد پردازش مرکزی فعال و غیرفعال را در حضور برنامه‌های فعال ذخیره‌شده در حافظه نهان کاهش می‌دهد. می‌توانید بااستفاده از پرچم پیکربندی سیستم یا گزینه توسعه‌دهنده، فریز کردن برنامه را فعال کنید.

در Android 14 (سطح API‏ ۳۴) و بالاتر، فریز کردن برنامه‌های ذخیره‌شده در حافظه نهان شامل رفتارهای قوی زیر است:

  • فرایندهای برنامه در حالت ذخیره‌شده در حافظه نهان ۱۰ ثانیه پس‌از ورود به حالت ذخیره‌شده در حافظه نهان متوقف می‌شوند.
  • سیستم بلافاصله فرایند برنامه ازکارایستاده را درطول رویداد چرخه حیات از حالت ازکارایستاده خارج می‌کند. این رویدادها شامل دریافت هدف، شروع سرویس کار، یا کاربر ازسرگیری فعالیت می‌شود.

‫ActivityManagerService همه فرایندهای برنامه را مدیریت می‌کند و درباره چرخه حیات برنامه تصمیم می‌گیرد. ‫CachedAppOptimizer مسئول متوقف کردن فرایند برنامه است.

وقتی فرایند برنامه‌ای متوقف می‌شود، همه رشته‌های آن تعلیق می‌شوند و تا زمانی که از حالت توقف خارج نشوند نمی‌توانند کار CPU انجام دهند. در نتیجه، برنامه نمی‌تواند جمع‌آوری زباله (GC) انجام دهد و نمی‌تواند به رویدادهای کاهش حافظه پاسخ دهد. برای اطلاعات بیشتر، ComponentCallbacks2.onTrimMemory(int) را ببینید. برای تطبیق با این موضوع، از Android 14:

  • برنامه‌هایی که نمونه Activity نمایان دارند، به‌محض اینکه به پس‌زمینه منتقل می‌شوند، از TRIM_MEMORY_UI_HIDDEN مطلع می‌شوند. برنامه‌هایی که در چرخه حیات بدون واسط کاربر باقی می‌مانند، مثل برنامه‌هایی که سرویس پیش‌زمینه‌ای دارند، ممکن است TRIM_MEMORY_BACKGROUND را دریافت کنند. سایر رویدادهای trim ارسال نمی‌شوند، زیرا وقتی برنامه‌ها واجدشرایط آن رویدادها هستند، انتظار می‌رود که منجمد شوند.
  • کمی پس‌از ورود به حالت حافظه نهان، سیستم ممکن است از زمان اجرای برنامه درخواست کند برای آماده شدن برای احتمالاً منجمد شدن، «جمع‌آوری زباله» انجام دهد.
  • وقتی فرایند برنامه متوقف می‌شود، ممکن است مراحل فشرده‌سازی حافظه اضافی رخ دهد، مانند نوشتن صفحه‌های کثیف در فضای ذخیره‌سازی پشتیبان و جابه‌جایی صفحه‌های ناشناس به ZRAM.
  • اگر همه فرایندهای یک برنامه خاص متوقف شوند، سیستم همه سوکت‌های TCP فعال نگهداری‌شده توسط برنامه را می‌بندد. این کار مانع از آن می‌شود که سمت سرور سوکت، پینگ‌های TCP keepalive ارسال کند که باعث بیدار شدن مودم دستگاه می‌شود.

وقتی وضعیت فرایند برنامه ذخیره‌شده در حافظه نهان از ذخیره‌شده در حافظه نهان به وضعیت اهمیت بالاتر ارتقا می‌یابد، فرایندهای برنامه ذخیره‌شده در حافظه نهان از حالت تعلیق خارج می‌شوند. برای کاهش رویدادهای رفع انسداد در Android 14 و نسخه‌های بالاتر، سیستم هم‌زمان با اینکه برنامه در حالت حافظه نهان است، همه‌فرستی‌های ثبت‌شده در بافت را در صف قرار می‌دهد. پخش‌های ثبت‌شده در بافت گیرنده‌هایی هستند که برنامه به‌صورت پویا با فراخوانی Context.registerReceiver ثبت می‌کند. سیستم این همه‌فرستی‌های در صف را فقط پس‌از رفع انجماد برنامه ارائه می‌دهد. درمقابل، سیستم پخش‌های اعلام‌شده در مانیفست را در صف قرار نمی‌دهد. همه‌فرستی‌های اعلام‌شده در مانیفست گیرنده‌هایی هستند که به‌صورت ایستا در AndroidManifest.xml بااستفاده از عنصر <receiver> اعلام شده‌اند. سیستم بلافاصله برنامه ذخیره‌شده را از حالت تعلیق خارج می‌کند تا همه‌فرستی‌های اعلام‌شده در مانیفست را ارائه دهد.

تأثیر سلامت سیستم

اگر بیش‌از MAX_CACHED_PROCESSES فرایند برنامه حافظه نهان وجود داشته باشد، Android فرایند برنامه حافظه نهان را که اخیراً استفاده شده است خاتمه می‌دهد. در دستگاه‌های پشتیبانی‌شده‌ای که Android 14 یا بالاتر را اجرا می‌کنند، MAX_CACHED_PROCESSES به‌طور قابل‌توجهی افزایش یافته است، که به دستگاه‌ها اجازه می‌دهد فرایندهای برنامه ذخینه‌شده در حافظه نهان را به‌طور قابل‌توجهی بیشتر در RAM نگه دارند.

نگهداری برنامه‌های بیشتر در حافظه نهان «حافظه دسترسی تصادفی» باعث کاهش حداکثر ۳۰٪ در شروع سرد می‌شود، و این کاهش براساس کل «حافظه دسترسی تصادفی» دستگاه مقیاس‌بندی می‌شود. درعین‌حال، مصرف CPU توسط برنامه‌های حافظه نهان به حداقل می‌رسد که منجر به صرفه‌جویی قابل‌توجهی در باتری می‌شود.

معافیت‌های مسدود کردن

تحت شرایط خاصی، فرایند برنامه ممکن است وارد وضعیت حافظه نهان شود اما یخ‌زده باقی بماند. این معافیت‌ها جزئیات پیاده‌سازی هستند و ممکن است در نسخه‌های آینده Android تغییر کنند:

  • قفل‌های فایل: اگر فرایند ذخیره‌شده در حافظه نهان قفل فایلی را نگه دارد که فرایندهای غیرذخیره‌شده در حافظه نهان دیگر را مسدود کند، فرایندی که قفل را نگه داشته است منجمد نمی‌شود.
  • BIND_WAIVE_PRIORITY پیوندها: برنامه‌هایی با پیوندهای ورودی ایجادشده بااستفاده از Context.BIND_WAIVE_PRIORITY می‌توانند وارد حالت حافظه نهان شوند اما تا زمانی که همه فرایندهای کارخواه متصل نیز در حافظه نهان قرار گیرند، منجمد نمی‌شوند. این معافیت از برنامه‌های چندفرایندی، مانند مرورگرهای وب که از «برگه‌های سفارشی» استفاده می‌کنند، پشتیبانی می‌کند.

پیاده‌سازی فریز کردن برنامه‌ها

فریزکننده برنامه‌های حافظه نهان از فریزکننده cgroup v2 هسته استفاده می‌کند. دستگاه‌هایی که با هسته سازگار عرضه می‌شوند می‌توانند آن را فعال کنند. گزینه توسعه‌دهنده تعلیق اجرا برای برنامه‌های ذخیره‌شده در حافظه نهان را فعال کنید یا پرچم پیکربندی دستگاه activity_manager_native_boot use_freezer را روی true تنظیم کنید. برای مثال:

adb shell device_config put activity_manager_native_boot use_freezer true && adb reboot

وقتی پرچم use_freezer را روی false تنظیم کنید یا گزینه توسعه‌دهنده را غیرفعال کنید، فریزر غیرفعال می‌شود. برای مثال:

adb shell device_config put activity_manager_native_boot use_freezer false && adb reboot

می‌توانید این تنظیم را با تغییر پیکربندی دستگاه در نسخه یا به‌روزرسانی نرم‌افزار تغییر دهید.

برای ملغی کردن MAX_CACHED_PROCESSES، برای مثال، برای تنظیم مقدار روی ۱۰۲۴ برای آزمایش:

adb shell device_config put activity_manager max_cached_processes 1024
adb shell device_config set_sync_disabled_for_tests persistent

برای برگرداندن ملغی کردن MAX_CACHED_PROCESSES:

adb shell device_config delete activity_manager max_cached_processes
adb shell device_config set_sync_disabled_for_tests none

از Android 16 (سطح API 36) و بالاتر، Android میاناهای برنامه‌سازی کاربردی عمومی رسمی، مانند IBinder.FrozenStateChangeCallback و IBinder.addFrozenStateChangeCallback، برای مشاهده زمان توقف یا ازسرگیری پردازش‌های از دور ارائه می‌دهد. عناصری که با برنامه‌هایی که ممکن است در حافظه نهان ذخیره شوند تعامل دارند می‌توانند از این میاناهای برنامه‌سازی کاربردی برای پیگیری وضعیت توقف موقت فرایندهای از دور استفاده کنند.

الزامات دستگاه و هسته

فریز کردن برنامه‌های حافظه نهان به پشتیبانی از cgroup v2 هسته نیاز دارد. علاوه‌براین، اعلان‌های تغییر وضعیت توقف بااستفاده از IBinder.FrozenStateChangeCallback به پشتیبانی درایور متصل‌کننده هسته نیاز دارند که در «هسته‌های مشترک Android» (ACK) و «تصاویر هسته عمومی» (GKI) از Android 14 (سطح API‏ ۳۴) و بالاتر استاندارد است.

می‌توانید بااستفاده از فرمان‌های استاندارد adb، به‌صورت زیر، بررسی کنید که آیا دستگاه از این قابلیت‌ها پشتیبانی می‌کند یا نه:

  • بررسی کنید که آیا فریزر در دستگاه فعال است (برای کاربر یا ساخت‌های اشکال‌زدایی):

    adb shell device_config get activity_manager_native_boot use_freezer

    یا تأیید کنید که فرایندها به‌طور فعال توسط سیستم مسدود می‌شوند:

    adb shell dumpsys activity | grep -A 20 "Apps frozen:"
  • بررسی پشتیبانی کنترل‌کننده فریزکننده cgroup v2 (برای هر دستگاه):

    تأیید کنید که freezer در فهرست کنترل‌کننده‌های cgroup v2 دردسترس باشد:

    adb shell cat /sys/fs/cgroup/cgroup.controllers

    یا در دستگاه با دسترسی ریشه‌ای یا ساخت userdebug، تأیید کنید که گره فریزکننده cgroup v2 در cgroup فرزند سوار شده باشد:

    adb root && adb shell ls /sys/fs/cgroup/uid_0/cgroup.freeze

    اگر این فایل وجود داشته باشد، هسته از فریزکننده cgroup v2 پشتیبانی می‌کند.

  • بررسی پشتیبانی از اعلان تغییر وضعیت توقف (برای هر دستگاه):

    در دستگاه‌های دارای Android 14 و بالاتر با درایورهای بایندر سازگار با «تصویر هسته عمومی» (GKI)، IBinder.addFrozenStateChangeCallback با موفقیت تماس‌های برگشتی را ثبت می‌کند. اگر برنامه درایور پیوند هسته زیرین از اعلان‌های توقف پشتیبانی نکند، روش UnsupportedOperationException را پرتاب می‌کند.

ویژگی‌های سفارشی را مدیریت کنید

وقتی برنامه‌ها در حافظه نهان هستند، انتظار نمی‌رود که فرایندهای برنامه کاری انجام دهند، اما برخی‌از برنامه‌ها ممکن است ویژگی‌های سفارشی داشته باشند که ازطریق فرایندهایی پشتیبانی می‌شوند که انتظار می‌رود درحالی‌که در حافظه نهان هستند اجرا شوند. وقتی «یخچال برنامه‌ها» در دستگاهی که چنین برنامه‌هایی را اجرا می‌کند فعال باشد، فرایندهای حافظه نهان منجمد می‌شود و ممکن است مانع از کار کردن ویژگی‌های سفارشی شود.

به‌عنوان راهکار موقت، می‌توانید وضعیت فرایند را قبل‌از اینکه فرایند نیاز به انجام کاری داشته باشد به غیرذخیره‌شده در حافظه نهان تغییر دهید. این تغییر به برنامه‌ها اجازه می‌دهد فعال بمانند. نمونه‌های وضعیت فعال شامل سرویس پیش‌زمینه‌ای محدود یا وضعیت پیش‌زمینه‌ای است.

حالت‌های رایج خرابی

وقتی فرایندهای برنامه متوقف می‌شوند، ارتباط بین‌فرایندی (IPC) نامناسب یا زمان‌بندی وظیفه می‌تواند منجر به بسته شدن برنامه یا رفتار غیرمنتظره شود.

تراکنش‌های پوشه هم‌زمان به فرایندهای منجمد

وقتی فرایند برنامه کارخواه تراکنش پیونددهنده هم‌زمان را به فرایند برنامه سرور منجمدشده ارسال می‌کند، سیستم بلافاصله فرایند برنامه سرور را خاتمه می‌دهد. این کار از مسدود شدن نامحدود رشته کارخواه درحین انتظار برای پاسخ از سرور منجمد جلوگیری می‌کند. سپس رشته مشتری RemoteException را دریافت می‌کند و هر شنونده ثبت‌شده‌ای راه‌اندازی می‌شود. برای اطلاعات بیشتر، به IBinder.linkToDeath مراجعه کنید.

علت ریشه‌ای: این خرابی معمولاً به‌دلیل اشکالی در برنامه کارخواه رخ می‌دهد. وقتی کارخواهی به سرویسی متصل می‌شود، فرایند سرور به کارخواه متصل می‌شود و از ورود به حالت ذخیره‌شده در حافظه نهان قبل‌از کارخواه جلوگیری می‌شود. برای اطلاعات بیشتر، به Context.bindService مراجعه کنید. بااین‌حال، وقتی کارخواه Context.unbindService را فرا می‌خواند، فرایند سرور می‌تواند در حافظه نهان ذخیره و ثابت شود. اگر کارخواه پس‌از لغو اتصال همچنان از مرجع IBinder ذخیره‌شده در حافظه نهان استفاده کند، با فرایندی متوقف‌شده ارتباط برقرار می‌کند.

برای جلوگیری از این مشکل، مطمئن شوید برنامه‌های کارخواه IBinder ارجاع‌ها را بلافاصله پس‌از فراخوانی Context.unbindService دور می‌اندازند.

مدیریت کردن تماس‌های برگشتی از دور برای پردازش‌های کارخواه

سرویس‌ها و عناصر سیستمی که کارگزارهای طولانی‌مدت binder را برای فرایندهای مشتری حفظ می‌کنند می‌توانند با ردیابی وضعیت منجمد مشتری، از خطاهای هم‌زمان و سرریزهای ناهم‌زمان بافر جلوگیری کنند:

  • ثبت شنونده تغییر وضعیت: از IBinder.addFrozenStateChangeCallback در نشان‌های صحافی مشتری ورودی استفاده کنید تا هنگام ورود یا خروج فرایند مشتری از فریز، اعلان دریافت کنید.
  • توقف موقت ارسال درحین مسدود بودن: وقتی مشتری وارد STATE_FROZEN می‌شود، ارسال تماس‌های برگشتی یا به‌روزرسانی‌های وضعیت به آن مشتری موقتاً متوقف می‌شود.
  • ازسرگیری و تحویل پس‌از رفع انجماد: وقتی کارخواه به STATE_UNFROZEN منتقل می‌شود، ارسال فراخوان برگشتی را ازسر بگیرید و هرگونه به‌روزرسانی دسته‌ای یا ادغام‌شده لازم را ارائه دهید.
  • استفاده از RemoteCallbackList: خدمات سیستم بااستفاده از RemoteCallbackList می‌توانند خط‌مشی‌های تماس‌گیرنده مسدودشده را پیکربندی کنند تا بدون حفظ منطق ردیابی دستی، ارسال تماس برگشتی به‌طور خودکار متوقف و ازسر گرفته شود. برای اطلاعات بیشتر، به توصیه‌های فریز کردن پیوند برای خدمات سیستم مراجعه کنید.

سرریز میان‌گیر تراکنش صحافی ناهمگام

وقتی فرایند برنامه سرور تراکنش‌های پیونددهنده ناهم‌زمان (oneway) را درحالی‌که منجمد است دریافت می‌کند، تراکنش‌ها در بافر هر فرایند ذخیره می‌شوند. اگر سرور درحالت ثابت بودن تراکنش‌های ناهمگام زیادی دریافت کند، میان‌گیر سرریز می‌شود و سیستم فرایند برنامه سرور را خاتمه می‌دهد.

برای جلوگیری از این سرریز بافر، از ارسال تراکنش‌های بیش‌ازحد ناهم‌زمان پیونددهنده به فرایندهایی که ممکن است حافظه نهان یا منجمد شوند خودداری کنید.

اجرای مکرر تکالیف زمان‌بندی‌شده پس‌از لغو توقف

اگر برنامه‌ای تکالیف تکراری اجرا کند، درحالی‌که فرایند متوقف است، این تکالیف تعلیق می‌شوند. برای اطلاعات بیشتر، به ScheduledThreadPoolExecutor.scheduleAtFixedRate یا Timer.scheduleAtFixedRate مراجعه کنید. وقتی فرایند از حالت تعلیق خارج می‌شود، اجراهای ازدست‌رفته انباشته‌شده ممکن است به‌سرعت و پشت سر هم بدون هیچ تأخیری اجرا شوند.

برای جلوگیری از افزایش ناگهانی اجراها هنگام ازانجماد درآمدن برنامه، به‌جای scheduleAtFixedRate برای کارهای پس‌زمینه، از scheduleWithFixedDelay استفاده کنید. همچنین می‌توانید از WorkManager استفاده کنید.

آزمایش و عیب‌یابی کردن منجمدکننده برنامه‌ها

برای تأیید اینکه برنامه منجمدکننده طبق انتظار کار می‌کند یا برای عیب‌یابی کردن مشکلات مربوط به منجمدکننده، از ابزارها و فرمان‌های تشخیصی زیر استفاده کنید:

فرمان‌های مدیر فعالیت

می‌توانید از فرمان‌های adb shell am برای کنترل دستی توقف و فشرده‌سازی برای فرایندی خاص استفاده کنید:

  • اجبار به ثابت شدن فرایند:

    adb shell am freeze <process>
  • اجبار فرایند به ازانجماد درآمدن:

    adb shell am unfreeze <process>
  • فشرده‌سازی کامل حافظه را در یک فرایند اجباری کنید:

    adb shell am compact full <process>

بررسی Logcat

هر بار که فرایندی به حالت فریز منتقل می‌شود یا از آن خارج می‌شود، logcat را مشاهده کنید تا ورودی‌های فریز‌شده و فریزنشده را ببینید:

adb logcat | grep -i "\(freezing\|froze\)"

برونداد گزارش‌های دلیل رفع انجماد مقادیر شمارشی را از UnfreezeReason پروتکل بافر شمارشی فهرست می‌کند.

بازرسی dumpsys

بااستفاده از dumpsys activity، فهرست پردازش‌های متوقف‌شده را بررسی کنید:

adb shell dumpsys activity | grep -A 20 "Apps frozen:"

وجود فایل /sys/fs/cgroup/uid_0/cgroup.freeze را بررسی کنید.

ApplicationExitInfo

برای پُرسمان کردن دلیل بسته شدن فرایند قبلی، به ActivityManager.getHistoricalProcessExitReasons مراجعه کنید. اگر فرایند برنامه‌ای به‌دلیل مشکل مربوط به فریز کردن، مثلاً دریافت تراکنش هم‌زمان پیونددهنده درحالی‌که فریز شده است، متوقف شود، دلیل خروج روی ApplicationExitInfo.REASON_FREEZER تنظیم می‌شود.

ردیابی Perfetto

رویدادهای مربوط به فریزر در مسیری به نام Freezer تحت فرایند system_server در ردهای Perfetto منتشر می‌شوند:

  • بخش‌های Freeze و Unfreeze نشان می‌دهند که فرایند چه زمانی وضعیت خود را تغییر می‌دهد.
  • رویدادهای updateAppFreezeStateLSP نشان می‌دهند که سرور سیستم چه زمانی مشخصه‌های پردازش را برای تصمیم‌گیری درباره توقف یا لغو توقف مجدداً بررسی می‌کند.

می‌توانید این رویدادها را مستقیماً در «واسط کاربر Perfetto» بازرسی کنید یا آن‌ها را بااستفاده از PerfettoSQL تجزیه‌وتحلیل کنید:

INCLUDE PERFETTO MODULE slices.with_context;
SELECT *
FROM process_slice
WHERE process_name = "system_server"
AND track_name = "Freezer"
AND (name LIKE "Freeze %" OR name LIKE "Unfreeze %");

در کتابخانه استاندارد PerfettoSQL، رویدادهای فریز کردن در جدول android_freezer_events نیز خلاصه می‌شوند.