چگونه‌ها

اولویت‌بندی بهره‌وری حافظه: مراحل ضروری برای اندروید ۱۷

مطالعه ۱۰ دقیقه‌ای
۳ نویسنده
Alice Yuan, Ajesh Pai, Fung Lam

در حالی که عملکرد برنامه اغلب با رابط کاربری روان و زمان شروع سریع برابر است، حافظه به عنوان پایه خاموشی عمل می‌کند که این معیارهای قابل مشاهده بر روی آن ساخته می‌شوند. بر کسی پوشیده نیست که ما شاهد تغییری هستیم که در آن حافظه دستگاه بیش از هر زمان دیگری اهمیت پیدا می‌کند. ما نه تنها در بهینه‌سازی حافظه اندروید با اندروید ۱۷ گام‌های بلندی برداشته‌ایم، بلکه ابزارها و پشتیبانی API را نیز ارائه می‌دهیم تا به شما کمک کنیم از الزامات سختگیرانه‌تر حافظه در اواخر امسال جلوتر بمانید.

برای اطمینان از پایداری دستگاه، از اندروید ۱۷ به بعد، سیستم شروع به اعمال محدودیت‌های حافظه برنامه بر اساس کل رم دستگاه خواهد کرد. اگر برنامه‌ای از این محدودیت‌ها فراتر رود، اندروید فرآیند را بدون ردیابی پشته مرتبط، متوقف می‌کند.

فراتر از این خاتمه‌های اجباری، استفاده‌ی بهینه نشده از حافظه، به ناچار تجربه‌ی کاربری را کاهش می‌دهد. وقتی برنامه به محدودیت حافظه‌ی هیپ نزدیک می‌شود، جمع‌آوری مکرر زباله را آغاز می‌کند که منجر به وقفه‌های قابل توجه در رابط کاربری می‌شود. علاوه بر این، وقتی حافظه‌ی موجود در دستگاه تمام می‌شود، سیستم برای بازیابی صفحات تلاش می‌کند و باعث فشار بر CPU، تأخیر رابط کاربری و تخلیه‌ی باتری می‌شود. اگر کمبود حافظه خیلی شدید باشد، می‌تواند باعث رویدادهای Low Memory Killer (LMK) شود که به طور ناگهانی فرآیندهای پس‌زمینه را خاتمه می‌دهند و برنامه‌ها را مجبور می‌کنند که شروع سرد کندی داشته باشند و وضعیت کاربر را از دست بدهند.

برای ساخت برنامه‌های با کارایی بالا و جلوگیری از این خاتمه‌های اجباری، توصیه می‌کنیم استراتژی‌های بهینه‌سازی حافظه زیر را اتخاذ کنید:

  1. بهینه‌سازی بایت‌کد را با R8 به حداکثر برسانید
  2. بهینه سازی بارگذاری تصاویر
  3. تشخیص و رفع نشت حافظه با اندروید استودیو
  4. مرتب کردن حافظه هنگامی که برنامه از حالت قابل مشاهده خارج می‌شود
  5. قابلیت مشاهده پیشرفته حافظه با ProfilingManager

نسخه فشرده‌ای از این پست وبلاگ در قالب ویدیو نیز موجود است، آن را ببینید!

درک محدودیت‌های حافظه برنامه اندروید ۱۷

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

در اینجا به تفصیل دلایل این تغییر معماری آورده شده است:

  • جلوگیری از حذف‌های آبشاری : وقتی یک برنامه در حالی که در حالت ممتاز (مثلاً در حال اجرای یک سرویس پیش‌زمینه) است، دچار حجم زیاد یا نشت حافظه می‌شود، در ابتدا از Low Memory Killer (LMK) سیستم محافظت می‌شود. از آنجایی که این برنامه به طور کنترل نشده‌ای رشد می‌کند و رم را اشغال می‌کند، LMK مجبور می‌شود با حذف ده‌ها برنامه کوچک‌تر و کارآمدتر ذخیره شده در حافظه پنهان و کارهای پس‌زمینه، فضای مورد نیاز برای اشغال حافظه را بازپس بگیرد.
  • حفظ چندوظیفگی و وضعیت کاربر: وقتی سیستم مجبور می‌شود برنامه‌های ذخیره‌شده در حافظه پنهان را برای تطبیق با یک فرآیند نشتی پاک کند، تجربه چندوظیفگی به شدت کاهش می‌یابد. کاربرانی که به برنامه‌های ذخیره‌شده قبلی برمی‌گردند، به جای شروع مجدد تقریباً فوری و گرم، با شروع سرد و کند مواجه می‌شوند. این ناکارآمدی باعث فشار بیشتر بر CPU و تسریع تخلیه باتری می‌شود. همچنین می‌تواند زمینه کاربر را در برنامه‌های اخیراً استفاده‌شده، مانند موقعیت‌های پیمایش، پشته‌های ناوبری و پیشرفت در بازی، از بین ببرد.

برای تعیین اینکه آیا جلسه برنامه شما تحت تأثیر این محدودیت‌های موجود در فیلد قرار گرفته است یا خیر، می‌توانید تابع getDescription() را در ApplicationExitInfo فراخوانی کنید. اگر سیستم محدودیتی اعمال کرده باشد، دلیل خروج به عنوان REASON_OTHER گزارش می‌شود و رشته توضیحات شامل "MemoryLimiter:AnonSwap" خواهد بود. همچنین می‌توانید از پروفایلینگ مبتنی بر trigger با استفاده از TRIGGER_TYPE_ANOMALY برای ثبت خودکار داده‌های هیپ هنگام رسیدن به محدودیت حافظه استفاده کنید. علاوه بر این، اندروید به طور فعال در تلاش است تا معیارهای حافظه درون فیلد بیشتری را در اختیار توسعه‌دهندگان در کنسول Google Play قرار دهد.

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

بهینه‌سازی بایت‌کد را با R8 به حداکثر برسانید

یک راه بسیار مؤثر برای کاهش فضای اشغال شده توسط حافظه برنامه، فعال کردن بهینه‌ساز R8 است. با کوچک کردن کلاس‌ها، متدها و فیلدها به نام‌های کوتاه‌تر و حذف کدها و منابع بلااستفاده، R8 با به حداقل رساندن مقدار کد موجود مورد نیاز در طول اجرا، فضای اشغال شده توسط حافظه برنامه شما را به میزان قابل توجهی کاهش می‌دهد.

R8 کد مقیم را به حداقل می‌رساند، ردپای حافظه را کاهش می‌دهد و خطر خاتمه LMK را پایین می‌آورد. این امر منجر به شروع گرم مکرر نسبت به شروع سرد کند می‌شود. علاوه بر این، بایت‌کد ساده‌شده، سربار پردازنده اصلی را کاهش می‌دهد و مستقیماً نرخ ANR را برای یک تجربه کاربری روان‌تر کاهش می‌دهد. به عنوان مثال، بانک دیجیتال مونزو بهینه‌سازی کامل R8 را فعال کرد و شاهد کاهش 35 درصدی نرخ ANR، بهبود 30 درصدی نرخ شروع سرد و کاهش 9 درصدی اندازه کلی برنامه بود.

pic1-IO26_113_TSV-monzo-casestudy.jpg
بانک دیجیتال مونزو بهینه‌سازی کامل R8 را امکان‌پذیر کرد و معیارهای عملکرد را تا 35 درصد افزایش داد.

برای پیکربندی صحیح R8 در فایل build.gradle خود:

  • مقادیر isShrinkResources = true و isMinifyEnabled = true قرار دهید.
  • به جای فایل قدیمی proguard- proguard-android.txt proguard-android-optimize.txt استفاده کنید، که در واقع از بهینه‌سازی‌ها جلوگیری می‌کند و دیگر در افزونه Gradle نسخه ۹ اندروید پشتیبانی نمی‌شود.
  • android.enableR8.fullMode = false از gradle.properties خود حذف کنید.

اگر در کد بیس خود از reflection استفاده می‌کنید، قوانین Keep را اضافه کنید تا از بهینه‌سازی آن قسمت‌های کد توسط R8 جلوگیری شود. مطمئن شوید که قوانین Keep را به طور محدود اعمال می‌کنید تا حداکثر بهینه‌سازی را داشته باشید.

برای به دست آوردن حداکثر بهینه‌سازی، حتماً این بهترین شیوه‌ها را در فایل قاعده Keep خود دنبال کنید.

  • گزینه‌های سراسری مانند -dontoptimize ، -dontshrink و -dontobfuscate که مانع از بهینه‌سازی کل کدبیس توسط R8 می‌شوند را حذف کنید.
  • قوانین Keep که مانع بهینه‌سازی اجزای اندروید مانند Activity، Services، Views یا Broadcast receivers می‌شوند را حذف کنید.
  • قوانین کلی keep در کل پکیج را اصلاح کنید تا فقط کلاس‌ها یا متدهای خاصی را هدف قرار دهند.

برای مشاهده‌ی بهترین شیوه‌های بیشتر، مستندات قوانین نگهداری ما را مشاهده کنید.

بهترین شیوه‌های توسعه‌دهنده کتابخانه R8

اگر شما یک توسعه‌دهنده کتابخانه هستید، قوانینی را که مصرف‌کنندگان شما نیاز دارند، اکیداً در consumer-rules file خود قرار دهید و قوانین حفاظت داخلی کتابخانه خود را در فایل proguard-rules.pro خود نگه دارید. برای اطلاعات بیشتر در مورد نحوه بهینه‌سازی کتابخانه‌ها، به بهینه‌سازی برای نویسندگان کتابخانه مراجعه کنید.

تحلیلگر پیکربندی R8

برای بررسی بهینه‌سازی R8 خود، از ابزار Configuration Analyzer استفاده کنید.   تحلیلگر پیکربندی، وضعیت فعلی بهینه‌سازی را نشان می‌دهد.   نمرات مبهم‌سازی، بهینه‌سازی و کوچک‌سازی. با استفاده از آنالیزور پیکربندی، می‌توانید بفهمید که هر قانون keep از بهینه‌سازی چند کلاس، متد یا فیلد جلوگیری می‌کند. این قوانین keep که در کل بسته گسترده هستند را اصلاح کنید تا حداکثر بهینه‌سازی را آزاد کنید.

با استفاده از تحلیلگر پیکربندی، می‌توانید قوانین Keep که سایر قوانین Keep را در بر می‌گیرند، قوانین Keep اضافی و قوانین Keep استفاده نشده را نیز شناسایی کنید.

pic2-r8-config-analyzer.png
تحلیلگر پیکربندی، وضعیت فعلی بهینه‌سازی را با نمرات Obfuscation، Optimization و Shrinking نشان می‌دهد.

مهارت مامور R8

همچنین می‌توانید از مهارت R8 Agent با Android Studio agent یا سایر ابزارهای هوش مصنوعی برای رفع پیکربندی‌های نادرست و اصلاح قوانین خود استفاده کنید که منجر به بهبود عملکرد برنامه می‌شود. (بینش‌های حاصل از مهارت‌های مبتنی بر هوش مصنوعی نیاز به تأیید فنی دارد)

بهینه سازی بارگذاری تصاویر

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

گوگل استفاده از کتابخانه‌های بارگذاری تصویر Coil را برای پروژه‌های Kotlin-first، به ویژه هنگام توسعه با Jetpack Compose و Glide برای برنامه‌های مبتنی بر جاوا، توصیه می‌کند.

این پنج روش برتر را اتخاذ کنید

  1. تصاویر را به صورت دستی بارگذاری کنید: اگر بیت‌مپ‌ها را به صورت دستی بارگذاری می‌کنید، از بارگذاری یک تصویر عظیم در یک نمای کوچک خودداری کنید؛ برای بارگذاری نسخه کوچکتر از inSampleSize استفاده کنید. Glide و Coil به طور پیش‌فرض تصاویر را به صورت خودکار نمونه‌برداری می‌کنند و می‌توانید این استراتژی نمونه‌برداری را به ترتیب با استفاده از DownsampleStrategy و ImageLoader پیکربندی کنید.
  2. برش: از تعبیه‌ی مستقیم padding در یک فایل تصویری برای اهداف letterboxing (مثلاً ایجاد یک حاشیه‌ی شفاف برای گسترش ابعاد تصویر) خودداری کنید. به جای قرار دادن این حاشیه‌ها در bake، از InsetDrawable استفاده کنید یا padding را مستقیماً در View یا Composable حاوی بیت‌مپ اعمال کنید.
  3. پیکربندی: با انتخاب فرمت پیکسل مناسب، حافظه و کیفیت را متعادل کنید. وقتی شفافیت مورد نیاز نیست RGB_565 استفاده کنید که نیمی از حافظه فرمت پیش‌فرض ARGB_8888 اشغال می‌کند. در Glide می‌توانید این را با استفاده از DecodeFormat و در Coil می‌توانید از ویژگی bitmapConfig پیکربندی کنید.
  4. اولویت‌بندی فایل‌های برداری قابل ترسیم: برای فایل‌های هندسی پایه، از ShapeDrawable به عنوان یک جایگزین سبک برای رمزگشایی بیت‌مپ‌های رستری استفاده کنید. با تعریف این فایل‌ها از طریق XML، اطمینان حاصل می‌کنید که آنها به طور یکپارچه در تمام تراکم‌های نمایشگر مقیاس‌بندی می‌شوند و در عین حال به طور مؤثر از افزایش بیش از حد حافظه ناشی از منابع جلوگیری می‌کنند.
  5. استفاده مجدد: اگر برنامه شما بیت‌مپ‌ها را به صورت دستی مدیریت می‌کند، برای به حداقل رساندن اتلاف حافظه، وقتی دیگر به یک بیت‌مپ نیاز نیست، برنامه باید bitmap.recycle() را فراخوانی کند و بلافاصله مرجع Bitmap حذف کند. اگر از یک کتابخانه بارگذاری تصویر مانند Glide یا Coil استفاده می‌کنید، بیت‌مپ را به مخزن مدیریت‌شده کتابخانه برگردانید. با ارائه یک بافر موجود برای نیازهای حافظه آینده، مخزن به طور مؤثر از سربار تخصیص‌های جدید جلوگیری می‌کند.

برای کسب اطلاعات بیشتر، مستندات ما در مورد بهینه‌سازی عملکرد تصاویر را بررسی کنید.

ابزارهای اندروید استودیو

همچنین می‌توانید با استفاده از اندروید استودیو Narwhal 4، بیت‌مپ‌های اضافی را حذف کنید. در اینجا نحوه‌ی یافتن آنها در پنج مرحله‌ی ساده آمده است:

  1. تب Profiler را در اندروید استودیو باز کنید.
  2. روی Heap Dump (یا «Analyze Memory Usage») کلیک کنید و دکمه‌ی ضبط را بزنید تا از وضعیت فعلی حافظه‌ی برنامه‌تان یک snapshot بگیرید.
  3. نتایج تحلیل را برای یافتن مثلث هشدار زرد ⚠️ اسکن کنید، که اندروید استودیو از آن برای علامت‌گذاری بیت‌مپ‌های تکراری که چندین بار ذخیره شده‌اند استفاده می‌کند. روش دیگر، رفتن به سربرگ پروفایلر، انتخاب «فیلتر بر اساس:» و انتخاب تنظیم «بیت‌مپ‌های تکراری» است.
  4. روی هر ورودی علامت‌گذاری‌شده کلیک کنید تا پنجره‌ی پیش‌نمایش بیت‌مپ باز شود و به شما این امکان را می‌دهد که دقیقاً ببینید کدام تصویر تکرارشونده است.
  5. از آن تأیید بصری برای ردیابی منطق بارگذاری اضافی در کد خود استفاده کنید و یک استراتژی ذخیره‌سازی بهتر پیاده‌سازی کنید.
pic3-IO26_113_TSV -dup-bitmaps-cropped.jpg
هنگام استفاده از Android Studio Profiler، به دنبال مثلث هشدار زرد ⚠️ در فایل‌های heap dump باشید.

تشخیص و رفع نشت حافظه با اندروید استودیو

نشت حافظه در اندروید زمانی رخ می‌دهد که کد شما مدت‌ها پس از پایان چرخه حیات یک شیء، ارجاع به آن را نگه می‌دارد. این امر مانع از آن می‌شود که Garbage Collector (GC) آن حافظه را بازیابی کند و در نهایت منجر به عملکرد کند یا OutOfMemoryError (OOM) شود.

اندروید استودیو پاندا ۳ دارای یک وظیفه پروفایلر اختصاصی LeakCanary است که به توسعه‌دهندگان اجازه می‌دهد نشت حافظه را به‌صورت بلادرنگ تجزیه و تحلیل کرده و ردپاها را مستقیماً درون IDE ترسیم کنند.

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

pic4-android-studio-leaks.png
تحلیل نشت حافظه LeakCanary با استفاده از اعلان Go to برای اشکال‌زدایی، زمینه‌سازی شده است.

علاوه بر این، تجزیه و تحلیل نشتی اکنون در IDE زمینه‌سازی شده و کاملاً با کد منبع شما یکپارچه شده است و ویژگی‌هایی مانند اعلان go to و سایر اتصالات مفید کد را ارائه می‌دهد که به طور چشمگیری اصطکاک و زمان مورد نیاز برای بررسی و رفع نشتی‌های حافظه را کاهش می‌دهد.

نمونه‌هایی از نشت حافظه رایج

نشت حافظه زمانی رخ می‌دهد که یک شیء بیش از طول عمر مورد انتظار خود در حافظه باقی بماند. این معمولاً به دلایل زیر اتفاق می‌افتد:

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

در اینجا چند سناریو به عنوان مثال آورده شده است:

سناریو مثال مبتنی بر نوشتن مثال مبتنی بر نمایش
نشت اطلاعات

مثال:
ارسال LocalContext.current به یک ViewModel

رفع اشکال:
منطق وابسته به Context را در لایه رابط کاربری نگه دارید. برای لایه‌های غیر رابط کاربری، با استفاده از تزریق وابستگی ، کد را بازسازی کنید یا وضعیت رابط کاربری را با استفاده از Kotlin flow مشاهده کنید.

مثال:
ذخیره یک Activity در یک شیء همراه یا متغیر استاتیک.

رفع اشکال:
ارجاعات استاتیک به کامپوننت‌های رابط کاربری نگه ندارید. برای استفاده از تزریق وابستگی یا مشاهده وضعیت رابط کاربری با استفاده از Kotlin flow، کد را Refactor کنید.

نشت شنوندگان

مثال:
استفاده از DisposableEffect برای شروع یک شنونده اما خالی گذاشتن onDispose

رفع اشکال:
منطق لغو ثبت و پاکسازی را درون بلوک onDispose انجام دهید.

مثال:
ثبت نام برای به‌روزرسانی‌های SensorManager و فراموش کردن لغو ثبت نام.

رفع اشکال:
فراخوانی دستی unregisterListener() در چرخه حیات onStop() یا onDestroy()

نشت نماها

مثال:
نگه‌داشتن ارجاع به یک View قدیمی درون یک AndroidView بدون استراتژی انتشار.


رفع اشکال:
از بلوک release block) مربوط به کامپوننت AndroidView برای پاکسازی نمای قدیمی (legacy View استفاده کنید.

مثال:
نگه داشتن ارجاع به یک شیء view binding پس از تخریب Fragment .

رفع اشکال:
متغیر اتصال را درون متد چرخه عمر onDestroyView() برابر با null قرار دهید.

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

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

اگر تصمیم گیری در مورد اینکه چه حافظه‌ای را از برنامه خود بازپس بگیرد را به سیستم عامل واگذار کنید، ممکن است متوجه شوید که سیستم عامل حافظه‌ای را که کمی پس از از سرگیری برنامه خود به آن نیاز خواهید داشت، بازپس گرفته است. در عوض، برنامه شما می‌تواند داوطلبانه تخصیص‌های حافظه‌ای را که می‌تواند بعداً، بنا به تقاضا و با هزینه کم، بازسازی کند، دور بریزد. برای انجام این کار، می‌توانید رابط ComponentCallbacks2 را پیاده‌سازی کنید. می‌توانید onTrimMemory در Activity ، Fragment ، Service یا حتی کلاس Application سفارشی خود پیاده‌سازی کنید. استفاده از آن در کلاس Application برای مدیریت حافظه پنهان سراسری بسیار مؤثر است.

متد فراخوانی onTrimMemory() که ارائه شده است، برنامه شما را از رویدادهای چرخه عمر یا مرتبط با حافظه مطلع می‌کند که فرصت خوبی را برای برنامه شما فراهم می‌کند تا به طور داوطلبانه میزان استفاده از حافظه خود را کاهش دهد.

از نظر مدیریت چرخه عمر حافظه، پیاده‌سازی شما باید منحصراً روی TRIM_MEMORY_UI_HIDDEN و TRIM_MEMORY_BACKGROUND تمرکز کند. از اندروید ۱۴، سیستم ارسال اعلان‌ها برای سایر ثابت‌های قدیمی را که رسماً در اندروید ۱۵ منسوخ شده بودند، متوقف کرده است.

TRIM_MEMORY_UI_HIDDEN : این سیگنال نشان می‌دهد که رابط کاربری برنامه شما از دید کاربر خارج شده است. این فرصتی را فراهم می‌کند تا تخصیص‌های قابل توجهی از حافظه که صرفاً به رابط کاربری وابسته هستند - مانند Bitmapها، بافرهای پخش ویدیو یا منابع انیمیشن پیچیده - آزاد شوند.

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

import android.content.ComponentCallbacks2
// Other import statements.

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    override fun onTrimMemory(level: Int) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

توجه: ادغام onTrimMemory ممکن است به پشتیبانی SDK بستگی داشته باشد. برای مثال، برخی از بازی‌ها برای فعال کردن این قابلیت به موتور بازی خود متکی هستند. لطفاً اسناد بهینه‌سازی حافظه بازی را بررسی کنید.

قابلیت مشاهده پیشرفته حافظه با ProfilingManager

برای شناسایی و تشخیص مشکلات حافظه در فیلد که به صورت محلی قابل بازتولید نیستند، باید از API ProfilingManager استفاده کنید. این API پیشرفته‌ی رصدپذیری که در اندروید ۱۵ معرفی شده است، به شما امکان می‌دهد پروفایل‌های Perfetto کاربر واقعی را به صورت برنامه‌نویسی جمع‌آوری کنید.

برای تیم‌هایی که فاقد زیرساخت اختصاصی برای مدیریت و میزبانی مصنوعات عملکردی هستند، Crashlytics در حال بررسی یک راه‌حل تخصصی برای ساده‌سازی این گردش کار است. آنها از توسعه‌دهندگان دعوت می‌کنند تا بازخورد خود را ارائه دهند .

اندروید ۱۷ تریگرهای رویدادمحور جدیدی را معرفی می‌کند که مهم‌ترین آن‌ها TRIGGER_TYPE_OOM و TRIGGER_TYPE_ANOMALY هستند:

  • تریگر OOM به طور خودکار یک فایل heap dump جاوا را دقیقاً در لحظه وقوع خطای OutOfMemoryError جمع‌آوری می‌کند و حالت‌های تخصیص دقیقی را ارائه می‌دهد. یک پروفایل OOM جمع‌آوری‌شده، دفعه بعد که برنامه شروع می‌شود و callback مربوط به registerForAllProfilingResults را ثبت می‌کند، ارائه می‌شود.
  • تریگر Anomaly مشکلات شدید عملکردی، مانند هرزنامه‌های بیش از حد binder یا نقض آستانه‌های حافظه را تشخیص می‌دهد. ناهنجاری حافظه، درست قبل از اینکه سیستم برنامه را خاتمه دهد، یک heap dump ارائه می‌دهد.
    val profilingManager = 
applicationContext.getSystemService(ProfilingManager::class.java)
    val triggers = ArrayList<ProfilingTrigger>()  


    triggers.add(ProfilingTrigger.Builder(
                 ProfilingTrigger.TRIGGER_TYPE_ANOMALY))
    val mainExecutor: Executor = Executors.newSingleThreadExecutor()
    val resultCallback = Consumer<ProfilingResult> { profilingResult ->
        if (profilingResult.errorCode != ProfilingResult.ERROR_NONE) {
            // upload profile result to server for further analysis          
            setupProfileUploadWorker(profilingResult.resultFilePath)
        } 

    profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

پس از جمع‌آوری داده‌های هیپ، می‌توانید پروفایل را از سرور یا به صورت محلی از طریق adb pull دانلود کنید و فایل را بکشید و در Perfetto UI رها کنید. برای ساده‌سازی گردش کار اشکال‌زدایی حافظه، از Heap Dump Explorer استفاده کنید، این نمای پیش‌فرض جدید برای هیپ دامپ‌ها در Perfetto UI است. این ابزار یک رابط کاربری بصری برای بررسی هیپ دامپ‌های جاوا ارائه می‌دهد و به شما امکان می‌دهد سلسله مراتب تخصیص اشیاء را تجسم کنید، اندازه حافظه حفظ شده را محاسبه کنید و کوتاه‌ترین مسیر را از ریشه جمع‌آوری زباله شناسایی کنید. با استفاده از Heap Dump Explorer، می‌توانید به سرعت نشت حافظه، اشیاء حفظ شده متورم مانند تخصیص بیش از حد بیت‌مپ را مشخص کنید و تخصیص اشیاء هیپ را همه در یک مکان تجزیه و تحلیل کنید.

pic5-perfettoheapdump-analyzer.png
از نمودار شعله‌ای تعبیه‌شده در Heap Dump Explorer برای بررسی بصری و پیمایش اشیاء با بالاترین تخصیص هیپ استفاده کنید.

نتیجه‌گیری

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

نوشته شده توسط:
ادامه مطلب