حافظه برنامه خود را مدیریت کنید

این صفحه نحوه کاهش پیشگیرانه مصرف حافظه را در برنامه خود توضیح می دهد. برای اطلاعات در مورد نحوه مدیریت حافظه توسط سیستم عامل Android، به نمای کلی مدیریت حافظه مراجعه کنید.

حافظه با دسترسی تصادفی (RAM) یک منبع با ارزش برای هر محیط توسعه نرم افزاری است، و حتی برای سیستم عامل تلفن همراه که در آن حافظه فیزیکی اغلب محدود است، ارزشمندتر است. اگرچه هم اندروید Runtime (ART) و هم ماشین مجازی Dalvik به طور معمول جمع‌آوری زباله را انجام می‌دهند، اما این بدان معنا نیست که می‌توانید زمان و مکان تخصیص و انتشار حافظه را نادیده بگیرید. همچنان باید از معرفی نشت حافظه - که معمولاً به دلیل نگه داشتن ارجاعات شیء در متغیرهای عضو استاتیک ایجاد می شود - اجتناب کنید و هر شیء Reference را در زمان مناسب که توسط فراخوان چرخه حیات تعریف شده است، رها کنید.

حافظه و میزان مصرف حافظه موجود را نظارت کنید

قبل از اینکه بتوانید آنها را برطرف کنید، باید مشکلات استفاده از حافظه برنامه خود را پیدا کنید. Memory Profiler در Android Studio به شما کمک می کند تا مشکلات حافظه را به روش های زیر پیدا و تشخیص دهید:

  • ببینید برنامه شما چگونه حافظه را در طول زمان تخصیص می دهد. Memory Profiler نموداری بیدرنگ از میزان حافظه استفاده شده از برنامه شما، تعداد اشیاء جاوا اختصاص داده شده و زمان جمع آوری زباله را نشان می دهد.
  • رویدادهای جمع‌آوری زباله را راه‌اندازی کنید و در حین اجرای برنامه‌تان، از پشته‌های جاوا عکس بگیرید.
  • تخصیص حافظه برنامه خود را ضبط کنید، همه اشیاء اختصاص داده شده را بررسی کنید، ردیابی پشته برای هر تخصیص را مشاهده کنید و به کد مربوطه در ویرایشگر Android Studio بروید.

در پاسخ به رویدادها حافظه را آزاد کنید

همانطور که در مرور کلی مدیریت حافظه توضیح داده شده است، Android می‌تواند حافظه را از برنامه شما بازیابی کند یا در صورت لزوم برنامه شما را به طور کامل متوقف کند تا حافظه برای کارهای مهم آزاد شود. برای کمک به تعادل بیشتر حافظه سیستم و جلوگیری از نیاز سیستم به توقف فرآیند برنامه شما، می توانید رابط ComponentCallbacks2 را در کلاس های Activity خود پیاده سازی کنید. روش ارائه شده onTrimMemory() به برنامه شما اجازه می‌دهد تا زمانی که برنامه شما در پیش‌زمینه یا پس‌زمینه است، به رویدادهای مرتبط با حافظه گوش دهد. سپس به برنامه شما اجازه می‌دهد در پاسخ به چرخه عمر برنامه یا رویدادهای سیستم که نشان می‌دهند سیستم نیاز به بازیابی حافظه دارد، اشیایی را آزاد کند.

همانطور که در مثال زیر نشان داده شده است، می توانید برای پاسخگویی به رویدادهای مختلف مرتبط با حافظه، callback onTrimMemory() را پیاده سازی کنید:

کاتلین

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

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {

    // Other activity code.

    /**
     * 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) {

        // Determine which lifecycle or system event is raised.
        when (level) {

            ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
            ComponentCallbacks2.TRIM_MEMORY_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */
            }

            else -> {
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
            }
        }
    }
}

جاوا

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

public class MainActivity extends AppCompatActivity
    implements ComponentCallbacks2 {

    // Other activity code.

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

        // Determine which lifecycle or system event is raised.
        switch (level) {

            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */

                break;

            default:
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
                break;
        }
    }
}

بررسی کنید که چقدر حافظه نیاز دارید

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

برای جلوگیری از تمام شدن حافظه، می توانید از سیستم پرس و جو کنید تا مشخص کنید چه مقدار فضای پشته در دستگاه فعلی موجود است. با فراخوانی getMemoryInfo() می‌توانید از سیستم برای این رقم پرس و جو کنید. این یک شی ActivityManager.MemoryInfo را برمی گرداند که اطلاعاتی در مورد وضعیت حافظه فعلی دستگاه، از جمله حافظه موجود، کل حافظه، و آستانه حافظه - سطح حافظه ای که در آن سیستم شروع به توقف فرآیندها می کند، ارائه می دهد. شی ActivityManager.MemoryInfo همچنین lowMemory را نشان می‌دهد، که یک بولین ساده است که به شما می‌گوید آیا حافظه دستگاه کم است یا خیر.

نمونه کد زیر نحوه استفاده از متد getMemoryInfo() در برنامه خود نشان می دهد.

کاتلین

fun doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    if (!getAvailableMemory().lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
    val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    return ActivityManager.MemoryInfo().also { memoryInfo ->
        activityManager.getMemoryInfo(memoryInfo)
    }
}

جاوا

public void doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

    if (!memoryInfo.lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
    ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    return memoryInfo;
}

از ساختارهای کد با حافظه کارآمدتر استفاده کنید

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

از خدمات کم استفاده کنید

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

هنگامی که یک سرویس را راه اندازی می کنید، سیستم ترجیح می دهد روند آن سرویس را در حال اجرا نگه دارد. این رفتار فرآیندهای سرویس را بسیار گران می کند، زیرا RAM مورد استفاده یک سرویس برای سایر فرآیندها در دسترس نمی ماند. این باعث کاهش تعداد فرآیندهای کش شده ای می شود که سیستم می تواند در حافظه پنهان LRU نگه دارد و باعث می شود سوئیچینگ برنامه کارآمدتر نباشد. حتی می‌تواند در زمانی که حافظه فشرده است و سیستم نمی‌تواند فرآیندهای کافی برای میزبانی همه سرویس‌هایی را که در حال حاضر در حال اجرا هستند، حفظ کند، منجر به thrash در سیستم شود.

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

از ظروف داده بهینه شده استفاده کنید

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

چارچوب Android شامل چندین کانتینر داده بهینه شده، از جمله SparseArray ، SparseBooleanArray و LongSparseArray است. به عنوان مثال، کلاس‌های SparseArray کارآمدتر هستند، زیرا از نیاز سیستم به جعبه‌سازی خودکار کلید و گاهی مقدار، که در هر ورودی یک یا دو شی دیگر ایجاد می‌کند، اجتناب می‌کنند.

در صورت لزوم، همیشه می توانید به آرایه های خام برای ساختار داده ناب تغییر دهید.

مراقب انتزاع کدها باشید

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

از پروتوباف های ساده برای داده های سریالی استفاده کنید

بافرهای پروتکل (protobufs) مکانیزمی غیر زبانی، پلتفرم خنثی و قابل توسعه هستند که توسط Google برای سریال‌سازی داده‌های ساختاریافته طراحی شده است - شبیه به XML، اما کوچک‌تر، سریع‌تر و ساده‌تر. اگر از پروتوباف ها برای داده های خود استفاده می کنید، همیشه از پروتوباف های ساده در کد سمت کلاینت خود استفاده کنید. پروتوباف‌های معمولی کد بسیار پرمخاطب تولید می‌کنند، که می‌تواند مشکلات زیادی را در برنامه شما ایجاد کند، مانند افزایش استفاده از RAM، افزایش قابل توجه اندازه APK و اجرای کندتر.

برای اطلاعات بیشتر، به protobuf readme مراجعه کنید.

از تضعیف حافظه جلوگیری کنید

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

اغلب، ریزش حافظه می تواند باعث رخ دادن تعداد زیادی رویداد جمع آوری زباله شود. در عمل، ریزش حافظه، تعداد اشیاء موقت تخصیص داده شده را که در مدت زمان معینی رخ می دهند، توصیف می کند.

برای مثال، ممکن است چندین شیء موقت را در یک حلقه for اختصاص دهید. یا، ممکن است اشیاء Paint یا Bitmap جدیدی در تابع onDraw() یک view ایجاد کنید. در هر دو مورد، برنامه تعداد زیادی اشیاء را به سرعت در حجم بالا ایجاد می کند. اینها می توانند به سرعت تمام حافظه موجود در نسل جوان را مصرف کنند و یک رویداد جمع آوری زباله رخ دهد.

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

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

همچنین می‌توانید ارزیابی کنید که آیا استخرهای آبجکت به درد استفاده می‌خورند یا خیر. با یک استخر آبجکت، به جای انداختن یک نمونه شی روی زمین، پس از اینکه دیگر مورد نیاز نیست، آن را در یک استخر رها می کنید. دفعه بعد که یک نمونه شی از آن نوع مورد نیاز است، می‌توانید به جای تخصیص، آن را از استخر دریافت کنید.

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

نگه‌داشتن نمونه‌های بیشتر از مقدار مورد نیاز در استخر نیز باری را بر جمع‌آوری زباله وارد می‌کند. در حالی که استخرهای اشیاء تعداد فراخوان های جمع آوری زباله را کاهش می دهند، در نهایت میزان کار مورد نیاز برای هر فراخوانی را افزایش می دهند، زیرا متناسب با تعداد بایت های زنده (قابل دسترسی) است.

منابع و کتابخانه های فشرده حافظه را حذف کنید

برخی منابع و کتابخانه های درون کد شما می توانند بدون اینکه متوجه شوید حافظه را مصرف کنند. اندازه کلی برنامه شما، از جمله کتابخانه های شخص ثالث یا منابع جاسازی شده، می تواند بر میزان حافظه مصرفی برنامه شما تأثیر بگذارد. می توانید مصرف حافظه برنامه خود را با حذف اجزای اضافی، غیر ضروری یا متورم یا منابع و کتابخانه ها از کد خود بهبود بخشید.

اندازه کلی APK را کاهش دهید

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

برای اطلاعات بیشتر درباره کاهش اندازه کلی برنامه، به کاهش اندازه برنامه مراجعه کنید.

از Hilt یا Dagger 2 برای تزریق وابستگی استفاده کنید

چارچوب‌های تزریق وابستگی می‌توانند کدی را که می‌نویسید ساده کرده و یک محیط تطبیقی ​​را فراهم کنند که برای آزمایش و سایر تغییرات پیکربندی مفید است.

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

سایر چارچوب‌های تزریق وابستگی که از بازتاب استفاده می‌کنند، با اسکن کد شما برای حاشیه‌نویسی، فرآیندها را مقداردهی اولیه می‌کنند. این فرآیند می‌تواند به چرخه‌های CPU و RAM بسیار بیشتری نیاز داشته باشد و می‌تواند باعث تاخیر قابل توجهی در هنگام راه‌اندازی برنامه شود.

مراقب استفاده از کتابخانه های خارجی باشید

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

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

اگرچه ProGuard می تواند به حذف API ها و منابع با پرچم های مناسب کمک کند، اما نمی تواند وابستگی های داخلی بزرگ کتابخانه را حذف کند. ویژگی‌هایی که در این کتابخانه‌ها می‌خواهید ممکن است به وابستگی‌های سطح پایین‌تری نیاز داشته باشند. وقتی کتابخانه‌ها از انعکاس استفاده می‌کنند، که رایج است و نیاز به تنظیم دستی ProGuard برای کارکرد آن دارد، این موضوع به‌ویژه زمانی مشکل‌ساز می‌شود که از یک زیرکلاس Activity از یک کتابخانه استفاده می‌کنید - که می‌تواند دارای طیف وسیعی از وابستگی‌ها باشد.

از استفاده از کتابخانه مشترک فقط برای یک یا دو ویژگی از ده ها ویژگی خودداری کنید. مقدار زیادی کد و سربار که استفاده نمی کنید وارد نکنید. هنگامی که به استفاده از کتابخانه فکر می کنید، به دنبال پیاده سازی باشید که به شدت با آنچه شما نیاز دارید مطابقت داشته باشد. در غیر این صورت، ممکن است تصمیم بگیرید که پیاده سازی خود را ایجاد کنید.