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

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

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

  1. صفحه‌های تمیز با پشتیبانی فایل (مانند کد غیرفعال و دارایی‌های نگاشت‌شده) ابتدا تخلیه می‌شوند زیرا درصورت نیاز می‌توان آن‌ها را دوباره از فضای ذخیره‌سازی خواند.
  2. صفحه‌های کثیف با پشتیبانی فایل به فضای ذخیره‌سازی برگردانده و تخلیه می‌شوند.
  3. صفحه‌های حافظه ناشناس (مانند تخصیص‌های پشته) فشرده می‌شوند و به zRAM مبادله می‌شوند.

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

وقتی برنامه‌ای از بودجه‌اش فراتر می‌رود چه اتفاقی می‌افتد

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

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

برای آشنایی با نحوه ردیابی و بازیابی حافظه توسط سیستم‌عامل، راهنمای معماری حافظه را ببینید:

اعلام بودجه‌ها در مانیفست Android

اعلام بودجه‌های حافظه در AndroidManifest.xml روش اصلی و توصیه‌شده برای تعریف بودجه است. این ویژگی به کد زمان اجرا نیاز ندارد، بلافاصله پس‌از راه‌اندازی فرایند اعمال می‌شود، و قرارداد واضحی برای سیستم‌عامل ارائه می‌دهد.

اعلامیه‌های <memory-budget> در دستگاه‌هایی که از Android 17 QPR2 (سطح ای‌پی‌آی ۳۷.۲) و بالاتر استفاده می‌کنند اعمال می‌شود. در نسخه‌های پایین‌تر Android، تجزیه‌کننده مانیفست پلتفرم عناصر XML ناشناخته را به‌طور ایمن نادیده می‌گیرد، بنابراین می‌توانید <memory-budget> را بدون تأثیر بر سازگاری با نسخه‌های قدیمی اتخاذ کنید.

اعلام کردن بودجه مبنا

برای اکثر برنامه‌ها، تعریف یک بودجه واحد برای برنامه تنها چیزی است که نیاز است. عنصر <memory-budget> را مستقیماً در برچسب <application> اعلام کنید:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.simpleapp">

    <application
        android:label="@string/app_name">

        <!-- Baseline budget for the application -->
        <memory-budget android:maxMb="256" />

    </application>
</manifest>

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

تغییر بودجه براساس وضعیت پردازش

برنامه بسته به رؤیت‌پذیری آن برای کاربر به مقادیر مختلفی از حافظه نیاز دارد:

  • پیش‌زمینه: فرایند درحال میزبانی «فعالیت» قابل‌مشاهده‌ای است که با کاربر تعامل دارد. این حالت معمولاً به‌دلیل رابط کاربری و گرافیک فعال، بیشترین ردپا را دارد.
  • قابل‌درک: فرایند برای کاربر قابل‌درک است اما پنجره قابل‌مشاهده‌ای را میزبانی نمی‌کند (برای مثال، میزبانی سرویس پیش‌زمینه‌ای بازپخش رسانه، بارگیری پس‌زمینه‌ای فعال، ناوبری گام‌به‌گام، یا روش ورودی فعال).
  • پس‌زمینه: فرایند درحال اجرای کارهای پس‌زمینه، گیرنده‌ها، یا همگام‌سازی‌های داده است. انتظار می‌رود که ردپای حداقلی داشته باشد.

می‌توانید چندین عبارت <memory-budget> را برای مطابقت با این حالت‌ها اعلام کنید:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.simpleapp">

    <application
        android:label="@string/app_name">

        <!-- Default budget for visible foreground UI -->
        <memory-budget android:maxMb="200" />

        <!-- Tighter budget when playing audio in background -->
        <memory-budget
            android:maxMb="120"
            android:state="perceptible" />

        <!-- Minimal budget when fully in background -->
        <memory-budget
            android:maxMb="48"
            android:state="background" />

    </application>
</manifest>

بند پایه بدون android:state الزامی نیست؛ اگر فقط بندهای مختص ایالت (برای مثال، android:state="background") را مشخص کنید، سایر ایالت‌ها بدون محدودیت بودجه برنامه باقی می‌مانند. وقتی گنجانده شود، عبارت بدون android:state به‌عنوان بازگشت پیش‌فرض برای حالت‌های نامشخص (مانند پیش‌زمینه) عمل می‌کند، که عبارت‌های محدودکننده‌تر بعدی هنگام انتقال برنامه به حالت‌های perceptible یا background آن را لغو می‌کنند.

هنگام تعیین اندازه بودجه برای هر وضعیت، مجموعه کاری فعال آن وضعیت را هدف قرار دهید نه اوج حافظه مقیم (نشانگر سطح بالای RSS):

  • بودجه‌های اندازه foreground برای رندر کردن روان فریم: به واسط کاربر پیش‌زمینه فضای کافی برای مجموعه کاری فعال آن بدهید تا بازیابی همگام رندر کردن فریم را به‌تأخیر نیندازد. اگر برنامه‌تان شامل گردش کار مستقل با مصرف حافظه بالا است (مثل ویرایشگر ویدیو یا تصویر با وضوح بالا)، آن فعالیت را به فرایند اختصاصی منتقل کنید (به برنامه‌های چندفرایندی مراجعه کنید) تا تخصیص‌های آن به‌طور مستقل بودجه‌بندی شود و وقتی کاربر خارج می‌شود، ازبین برود.
  • بودجه‌های background را برای کارهای بدون واسط به‌طور دقیق تعیین کنید: RSS پس‌زمینه بالا در دورسنجی میدانی اغلب نشان‌دهنده تخصیص‌های واسط کاربری است که پس‌از ترک پیش‌زمینه حفظ شده‌اند (به قرار دادن خدمات محدودکننده در یک مکان با واسط کاربری با مصرف حافظه بالا و آزاد کردن حافظه در پاسخ به رویدادها مراجعه کنید) یا خواندن‌های یک‌باره فایل درطول نمایه‌سازی و همگام‌سازی‌های پس‌زمینه. ازآنجایی‌که کار پس‌زمینه بدون سر هیچ مهلت فریمی ندارد، بیرون کردن صفحه‌های حافظه نهان فایل یک‌باره و جابه‌جا کردن صفحه‌های توده غیرفعال به zRAM وقتی همگام‌سازی پس‌زمینه به‌طور خلاصه فراتر از بودجه‌اش اختصاص می‌یابد، هیچ پرش قابل‌مشاهده‌ای برای کاربر ایجاد نمی‌کند و درعین‌حال از بیرون کردن برنامه پیش‌زمینه کاربر توسط جهش‌های پس‌زمینه جلوگیری می‌کند.

نحوه نگاشت وضعیت‌های بودجه به وضعیت‌های پردازش

پلاتفرم حالت‌های بودجه مانیفست را براساس RunningAppProcessInfo.importance به حالت‌های فرایند زمان اجرا نگاشت می‌کند، که «مدیر فعالیت» آن را هم از مؤلفه‌های فعال خود فرایند و هم از هرگونه اتصال سرویس ورودی یا پُرسمان‌های ارائه‌دهنده محتوا از برنامه‌های دیگر محاسبه می‌کند:

  • foreground: تعاملات فعال و قابل‌مشاهده کاربر، مانند میزبانی یک فعالیت ازسرگرفته‌شده، حفظ وضعیت برنامه برتر هنگام خاموش شدن صفحه‌نمایش، یا میزبانی یک سرویس یا ارائه‌دهنده محتوا که به‌طور فعال توسط برنامه پیش‌زمینه برتر محدود یا پرس‌وجو می‌شود.
  • perceptible: حجم‌های کاری که بدون پنجره نمایان برای کاربر قابل‌درک هستند، مثل بازپخش رسانه فعال، ناوبری گام‌به‌گام، ضبط با دوربین یا میکروفون، بارگیری‌های فعال پس‌زمینه، سرویس‌های پیش‌زمینه همگام‌سازی داده، یا سرویس‌های محدودشده توسط کلاینت پیش‌زمینه بااستفاده از پرچم‌هایی مثل BIND_NOT_FOREGROUND (به کنترل کردن وراثت با پرچم‌های BIND مراجعه کنید). درحالی‌که سنجه‌های پلاتفرم داخلی ممکن است برخی‌از این حجم‌های کاری را PROCESS_STATE_IMPORTANT_FOREGROUND برچسب‌گذاری کنند، آن ثابت داخلی نشان‌دهنده پنجره قابل‌مشاهده نیست و تحت کنترل بودجه perceptible است.
  • background: کاری که بلافاصله برای کاربر قابل‌درک نیست، مثل کارهای پس‌زمینه، هشدارها، گیرنده‌های رویداد ثبت‌شده، یا «فعالیت» قبلی پس‌از اینکه کاربر از آن خارج می‌شود.

جدول زیر نشان می‌دهد که سطوح اهمیت زمان اجرا چگونه با حالت‌های مانیفست مطابقت دارند:

مانیفست android:state اهمیت زمان اجرا (RunningAppProcessInfo) اجزای معمول
foreground IMPORTANCE_FOREGROUND
IMPORTANCE_TOP_SLEEPING
«فعالیت» نمایان ازسرگرفته‌شده، برنامه برتر با صفحه قفل‌شده، ارائه‌دهنده سرویس یا محتوا که توسط برنامه برتر محدود شده است
perceptible IMPORTANCE_FOREGROUND_SERVICE
IMPORTANCE_VISIBLE
سرویس‌های پیش‌زمینه‌ای فعال بازپخش رسانه، پیمایش، بارگیری، یا همگام‌سازی؛ سرویس محدودشده با پرچم‌های سرویس پیش‌زمینه‌ای یا نمایان
background IMPORTANCE_PERCEPTIBLE
IMPORTANCE_CANT_SAVE_STATE
IMPORTANCE_SERVICE
کارهای پس‌زمینه، گیرنده‌ها، همگام‌سازی پس‌زمینه، «فعالیت» قبلی یا برنامه صفحه اصلی در پس‌زمینه

برای جزئیات درباره نحوه ارتقای فرایند میزبان سرویس یا ارائه‌دهنده توسط اتصال‌های کارخواه و نحوه بازگشت فرایند به background یا cached پس‌از لغو اتصال کارخواه یا خروج از صفحه، به اتصال‌های سرویس و وضعیت‌های فرایند مراجعه کنید.

وقتی فرایندی بدون مؤلفه‌های فعال وارد حالت ذخیره‌شده در حافظه نهان (IMPORTANCE_CACHED) می‌شود، پلاتفرم بودجه حافظه فعال آن را آزاد می‌کند و حافظه آن را ازطریق بازیابی استاندارد فرایند ذخیره‌شده در حافظه نهان مدیریت می‌کند.

بازرسی کردن وضعیت پردازش و بودجه برنامه

یکی از راه‌های بررسی وضعیت فرایند فعال و اهمیت برنامه درطول توسعه، پرسمان کردن «مدیر فعالیت» بااستفاده از ADB است:

adb shell dumpsys activity processes <package-name>

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

ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)
  All known processes:
  *APP* UID 10123 ProcessRecord{edf056c 3919:com.example.app/u0a123}
    pid=3919
    oom adj: max=1001 curRaw=200 setRaw=200 cur=200 set=200
    curProcState=4 mRepProcState=4 setProcState=4 lastStateTime=-42s360ms
    hasStartedServices=true
    mHasForegroundServices=true forcingToImportant=null
...
  Process OOM control (48 total):
    Proc #22: prcp  F/S/FGS  ---NFU-TI  t: 0 3919:com.example.app/u0a123 (fg-service)
        oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
        state: cur=FGS  set=FGS  lastRss=0.00 lastCachedRss=0.00

در این برونداد:

  • curProcState=4 و state: cur=FGS نشان می‌دهند که فرایند در وضعیت سرویس پیش‌زمینه‌ای است. اگر برنامه‌تان میزبان فعالیت قابل‌مشاهده فعالی باشد، این مورد به‌صورت TOP نشان داده می‌شود. اگر مؤلفه پیش‌زمینه مهمی درحال اجرا باشد (مثل بارگیری یا همگام‌سازی)، به‌صورت IMPF نشان داده می‌شود.
  • در جدول کنترل «کمبود حافظه» پردازش، prcp نشان می‌دهد که پردازش تحت ردیف اولویت قابل‌درک ارزیابی می‌شود که با بودجه perceptible مطابقت دارد.

برای بررسی بودجه حافظه اعمال‌شده فعلی و استفاده از حافظه مقیم:

adb shell dumpsys meminfo <package-name>

از Android 17 QPR2، این خروجی شامل بخش بودجه حافظه است که سقف محدودیت فعال، منبع محدودیت (مانند AndroidManifest یا MemoryBudgetManager)، و حافظه مقیم فعلی را نمایش می‌دهد.

برنامه‌های چندفرایندی

اگر برنامه‌تان کارش را بین چند فرایند تقسیم می‌کند، بااستفاده از برچسب <process> در <processes>، بودجه‌های فرایند اختصاصی را پیکربندی کنید.

برای مثال، یک برنامه جاری‌سازی موسیقی (com.example.radio) را درنظر بگیرید:

  1. فرایند اصلی: میزبان واسط کاربر نمایان و موتور بازپخش صدا است (MediaSessionService با یک mediaPlayback سرویس پیش‌زمینه‌ای). وقتی قابل‌مشاهده باشد، فرایند تحت بودجه پیش‌زمینه ۱۸۰ مگابایتی عمل می‌کند. وقتی کاربر برنامه را ترک می‌کند درحالی‌که موسیقی همچنان درحال پخش است، فرایند وارد حالت perceptible می‌شود که در آن بودجه ۶۴ مگابایتی برای موتور بازپخش و میان‌گیر صوتی کافی است.
  2. فرایند همگام‌سازی (:sync): فرایند اختصاصی که همگام‌سازی فراداده پس‌زمینه و نمایه‌سازی بارگیری را اجرا می‌کند. چون این فرایند فقط در پس‌زمینه فعال است، نیازی نیست state="background" را به‌طور صریح اعلام کنید؛ یک بودجه واحد اعمال می‌شود.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.radio">

    <application
        android:label="@string/app_name">

        <!-- Package baseline: main process with UI and audio playback -->
        <memory-budget android:maxMb="180" />

        <!-- Tighter budget when audio plays in the background -->
        <memory-budget
            android:maxMb="64"
            android:state="perceptible" />

        <processes>
            <!-- Dedicated background sync process -->
            <process android:process=":sync">
                <memory-budget android:maxMb="32" />
            </process>
        </processes>

        <service
            android:name=".playback.AudioPlayerService"
            android:foregroundServiceType="mediaPlayback"
            android:exported="false" />

        <service
            android:name=".sync.PlaylistSyncService"
            android:process=":sync"
            android:exported="false" />

    </application>
</manifest>

وقتی بلوک <processes> را در مانیفست خود اعلام می‌کنید، ورودی‌های <process> را فقط برای فرایندهایی که <memory-budget> را اعلام می‌کنند فهرست کنید. اگر می‌خواهید فقط برای زیرفرایندهای خاصی بودجه تعیین کنید و بقیه برنامه را در حد سیستم برای دستگاه رها کنید، می‌توانید عناصر <memory-budget> را مستقیماً زیر <application> حذف کنید.

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

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

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

  • مقیاس‌بندی براساس دسته تراکم نمایش (android:additionalMbPerDensity): مگابایت‌ها را به‌طور متناسب به مربع نسبت تراکم نمایشگر نسبت‌به mdpi (۱٫۰ برابر / ۱۶۰ نقطه در اینچ) اضافه می‌کند، زیرا دارایی‌های میانای کاربر و ترسیم‌شدنی‌های شطرنجی دوبعدی در هر دو جهت عرض و ارتفاع مقیاس‌بندی می‌شوند:

    <!-- Baseline 180MB + 16MB multiplied by (density ratio)^2 -->
    <memory-budget
        android:maxMb="180"
        android:additionalMbPerDensity="16" />
    

    در نمایشگر mdpi (۱٫۰ برابر)، بودجه ۱۸۰ + ۱۶ × ۱۲ = ۱۹۶ مگابایت است. در نمایشگر xxhdpi (۳٫۰ برابر)، بودجه به ۱۸۰ + ۱۶ × ۳۲ = ۳۲۴ مگابایت مقیاس‌بندی می‌شود.

  • مقیاس‌بندی براساس وضوح نمایشگر فیزیکی (android:additionalBytesPerDisplayPixel): بایت‌ها را مستقیماً به‌ازای هر پیکسل نمایشگر فیزیکی (عرض × ارتفاع) اضافه می‌کند. این برای برنامه‌هایی ایده‌آل است که سطوح گرافیکی تمام‌صفحه، میان‌گیرهای رندر، یا حافظه‌های پنهان عکس با وضوح کامل را اختصاص می‌دهند، جایی که مصرف حافظه مستقیماً با تعداد پیکسل‌های نمایشگر خام مقیاس‌بندی می‌شود، نه با دسته‌های تراکم واسط کاربر:

    <!-- Baseline 128MB + 16 bytes per physical display pixel -->
    <!-- For example, a 4-byte RGBA full-screen buffer with double or quadruple buffering -->
    <memory-budget
        android:maxMb="128"
        android:additionalBytesPerDisplayPixel="16" />
    

    در نمایشگر ۱۰۸۰ پیکسل (۲۴۰۰ × ۱۰۸۰ &approx; ۲٫۵۹ میلیون پیکسل)، این تقریباً ۴۱٫۴ مگابایت به بودجه پایه اضافه می‌کند. در نمایشگر 1440p (۱۴۴۰ × ۳۱۲۰ &approx; ۴٫۴۹ میلیون پیکسل)، &approx; ۷۱٫۸ مگابایت اضافه می‌کند.

این دو مشخصه جایگزین یکدیگر هستند. مشخصه‌ای را انتخاب کنید که با ضریب مقیاس اصلی برنامه‌تان مطابقت داشته باشد و از ترکیب هر دو در یک بند خودداری کنید.

تخصصی کردن برای ضریب‌های شکل دستگاه

هنگام ارسال APK در تلفن‌ها، رایانه‌های لوحی، و Wear OS، از ویژگی android:feature برای تنظیم بودجه برای اهداف سخت‌افزاری مختلف استفاده کنید.

در ساعت‌های Wear OS، حافظه دسترسی تصادفی محدود است و واسط کاربر و مجموعه ویژگی‌های برنامه بسیار ساده‌تر است. می‌توانید بودجه محدودتری را برای ویژگی watch اعلام کنید:

<!-- General phone and tablet baseline -->
<memory-budget android:maxMb="180" />

<!-- Wear OS override: simpler UI and constrained hardware -->
<memory-budget
    android:maxMb="48"
    android:feature="watch" />

قانون حل‌وفصل: آخرین بند قابل‌اجرا اعمال می‌شود

هنگام تعریف چندین عنصر <memory-budget> برای یک برنامه یا فرایند، سیستم آن‌ها را به ترتیبی که در مانیفست اعلام شده‌اند ارزیابی می‌کند. آخرین بند بودجه قابل‌اجرا، بندی است که اعمال می‌شود.

چون آخرین بودجه قابل‌اعمال برنده می‌شود، ترتیب مهم است. همیشه ابتدا کلی‌ترین بودجه پایه را قرار دهید و سپس لغوهای خاص‌تر (مانند بندهای خاص ایالت یا سخت‌افزار) را اضافه کنید.

مرجع مشخصه XML

همه مشخصه‌های اندازه حافظه برحسب مگابایت (MB) بیان می‌شوند و به Linux cgroup memory.current charge (که حافظه مشترک مانند Zygote را مستثنی می‌کند) نگاشت می‌شوند.

مشخصه قالب پیش‌فرض شرح
android:maxMb عدد صحیح (بزرگ‌تر از ۰) الزامی حد بودجه حافظه مقیم مبنا برحسب مگابایت.
android:state شمارشی همه وضعیت فرایندی که این بودجه برای آن اعمال می‌شود: foreground، perceptible، یا background. اگر حذف شود، این عبارت به‌عنوان جایگزین برای هر وضعیت نامشخصی عمل می‌کند.
android:additionalMbPerDensity عدد صحیح (≥ ۰) 0 مگابایت‌های اضافی ضرب در مربع نسبت تراکم نمایشگر نسبت به mdpi (۱٫۰ برابر).
android:additionalBytesPerDisplayPixel عدد صحیح (≥ ۰) 0 بایت‌های اضافی اختصاص‌داده‌شده به هر پیکسل نمایشگر فیزیکی (عرض × ارتفاع)، مفید برای میان‌گیرهای سطح و نقشه‌های بیتی.
android:feature رشته همه عبارت را به دستگاه‌هایی محدود می‌کند که ویژگی‌های سخت‌افزاری خاصی را اعلام می‌کنند: watch، automotive، یا leanback.

میاناهای برنامه‌سازی کاربردی زمان اجرا (گزینه پویای ثانویه)

اعلام بودجه به‌صورت ایستا در AndroidManifest.xml راه‌حل ترجیحی برای تقریباً همه برنامه‌ها است. بااین‌حال، برای برنامه‌هایی با حجم کاری پویا یا برای آزمایش زمان اجرا، Android میاناهای برنامه‌سازی کاربردی کیت توسعه نرم‌افزار و کیت توسعه بومی زمان اجرا را به‌عنوان گزینه ثانویه ارائه می‌دهد.

‫API زمان اجرا به شما امکان می‌دهد:

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

میانای برنامه‌سازی کاربردی کیت توسعه نرم‌افزار Android‏ (MemoryBudgetManager)

سرویس سیستم MemoryBudgetManager برای برنامه‌های نوشته‌شده به زبان Kotlin و Java از Android 17 QPR2 (نسخه جزئی کیت توسعه نرم‌افزار، سطح میانای برنامه‌سازی کاربردی ۳۷.۲ / Build.VERSION_CODES_FULL.CINNAMON_BUN_2) دردسترس است.

بازیابی سرویس

قبل‌از دسترسی به MemoryBudgetManager، با بررسی SDK_INT_FULL مطمئن شوید که دستگاه از Android 17 QPR2 یا بالاتر استفاده می‌کند. اگر minSdk برنامه‌تان کمتر از ۳۶ است، ابتدا SDK_INT را بررسی کنید تا دستگاه‌هایی که Android نسخه ۱۵ (سطح API‏ ۳۵) یا پایین‌تر را اجرا می‌کنند هنگام خواندن SDK_INT_FULL، NoSuchFieldError را نشان ندهند:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA &&
    Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.CINNAMON_BUN_2
) {
    val budgetManager = context.getSystemService(MemoryBudgetManager::class.java)
}

استفاده از پُرسمان و بودجه‌ها

// Query current memory charged to this process and the package UID
val processUsageBytes = budgetManager.processCurrentUsageBytes
val packageUsageBytes = budgetManager.packageCurrentUsageBytes

// Query effective budgets (returns LIMIT_IS_DISABLED if unconstrained)
val processBudgetBytes = budgetManager.processBudgetBytes
val packageBudgetBytes = budgetManager.packageBudgetBytes

تنظیم یا پاک کردن پویا بودجه‌ها

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

// Set a tighter dynamic budget on the current process (e.g., 96 MB)
try {
    budgetManager.processBudgetBytes = 96L * 1024L * 1024L
} catch (e: IllegalArgumentException) {
    // Thrown if the budget is <= 0 or exceeds the manifest ceiling or system limit
    Log.e(TAG, "Requested budget exceeds manifest or system ceiling", e)
}

// Clear the dynamic process budget to restore the manifest limit (or unconstrained baseline)
budgetManager.clearProcessBudget()
تلاش برای تنظیم بودجه‌ای بالاتر از سقف فعال، IllegalArgumentException را برمی‌گرداند.

برخلاف بندهای مانیفست <memory-budget> که هرگاه فرایند بین حالت‌های foreground، perceptible، و background جابه‌جا می‌شود به‌طور خودکار بودجه‌ها را تغییر می‌دهد، بودجه پویایی که ازطریق MemoryBudgetManager تنظیم می‌شود تا زمانی که کد شما آن را به‌روزرسانی یا پاک کند، یک سقف واحد را اعمال می‌کند. اگر از میانای برنامه‌سازی کاربردی زمان اجرا برای اجرای آزمایش‌های میدانی یا مدیریت محدودیت‌های خاص حالت استفاده می‌کنید، بودجه پویا را در سراسر انتقال‌های چرخه حیات به‌روزرسانی یا پاک کنید (برای مثال، بااستفاده از ProcessLifecycleOwner یا فراخوانی‌های چرخه حیات فعالیت، همان‌طور که در نمونه ویرایشگر تصویر تطبیقی نشان داده شده است).

عملکرد و هم‌پایگی

هنگام کار با MemoryBudgetManager در زمان اجرا، ویژگی‌های زیر را به‌خاطر داشته باشید:

  • هم‌توانی: همه میاناهای برنامه‌سازی کاربردی تغییر بودجه هم‌توان هستند. Calling clearProcessBudget() or clearPackageBudget() repeatedly or when no dynamic budget is active is a safe no-op. Similarly, setting setProcessBudgetBytes() or setPackageBudgetBytes() to the same value consecutively doesn't trigger redundant system reconfigurations.
  • دفعات فراخوانی: تنظیم، به‌روزرسانی، یا پاک کردن بودجه باعث می‌شود فراخوانی بین‌فرایندی به سرویس سیستم برای به‌روزرسانی کنترل‌کننده حافظه سیستم‌عامل برای فرایند انجام شود. اگرچه سبک است، اما کاملاً رایگان نیست. این میاناهای برنامه‌سازی کاربردی را درطول انتقال‌های چرخه حیات گسسته و نقاط عطف تکلیف (مانند ورود یا خروج از فعالیت، شروع یا تکمیل پردازش پس‌زمینه، یا رسیدگی به درخواست‌های گفتار) فراخوانی کنید و از فراخوانی آن‌ها در حلقه‌های تنگ، رشته‌های پردازش صوتی، یا روال‌های رندر کردن هر فریم خودداری کنید.
  • Query frequency: ویژگی‌های پُرسمان processCurrentUsageBytes و packageCurrentUsageBytes استفاده از حافظه زنده را از سرویس سیستم واکشی می‌کنند. درحالی‌که سریع است، پُرسمان‌ها باید به‌جای نظرسنجی در حلقه‌های پیوسته، به‌صورت درخواستی (مثلاً درطول انتقال تکلیف یا داخل فراخوانی‌های برگشتی OnOverBudgetListener) فراخوانده شوند.

Listen for over-budget pressure callbacks

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

val cleanupThread = HandlerThread("MemoryBudgetCleanup").apply { start() }

val listener = MemoryBudgetManager.OnOverBudgetListener { budgetBytes ->
    Log.w(TAG, "Process exceeded memory budget of $budgetBytes bytes")
    // Proactively evict thread-safe caches on the background Looper
    imageTileCache.evictAll()
}

// Register on a background Looper before tightening a dynamic budget
budgetManager.registerProcessOverBudgetListener(cleanupThread.looper, listener)

// When done (for example, in onStop)
budgetManager.unregisterProcessOverBudgetListener(listener)

روال‌های مطلوب برای بازخوانی‌های فراتر از بودجه:

  • استفاده از پس‌زمینه Looper: هر زمان ممکن بود، پس‌زمینه HandlerThread Looper را به registerProcessOverBudgetListener یا registerPackageOverBudgetListener منتقل کنید. اجرای تخلیه حافظه نهان یا پاک‌سازی فایل در رشته پس‌زمینه از رقابت با پردازش قاب در رشته میانای کاربر در شرایطی که فرایند تحت فشار حافظه است جلوگیری می‌کند. Looper.getMainLooper را برای شنوندگانی رزرو کنید که فقط با حالت واسط کاربر محدود به رشته اصلی در تماس هستند.
  • انتظار بازخوانی‌های مکرر را داشته باشید: بازخوانی‌های فراتر از بودجه اعلان‌های یک‌باره نیستند. وقتی استفاده از بودجه فراتر می‌رود، سیستم‌عامل ابتدا صفحات سرد را بازیابی می‌کند، و اگر فشار ادامه یابد، سیستم به بازیابی حافظه و فراخوانی شنونده شما حداکثر یک بار در ثانیه ادامه می‌دهد. فراخوانی برگشتی‌تان را هم‌توان کنید. اگر برنامه‌تان فقط به یک اعلان نیاز دارد یا می‌خواهد زمان استراحت طولانی‌تری بین مراحل پاک‌سازی داشته باشد، unregisterProcessOverBudgetListener (یا unregisterPackageOverBudgetListener) را در فراخوانی متقابل فراخوانی کنید، یا پاک‌سازی‌تان را با مُهر زمان استراحت یا AtomicBoolean محافظت کنید.
  • ثبت‌نام قبل‌از محدود کردن بودجه: اگر مصرف حافظه فعلی از قبل از بودجه جدید فراتر رفته باشد، وقتی setProcessBudgetBytes یا setPackageBudgetBytes را فراخوانی می‌کنید، به‌محض اعمال بودجه جدید، رویداد فراتر از بودجه راه‌اندازی می‌شود. شنونده‌تان را قبل‌از تنظیم بودجه ثبت کنید تا برنامه‌تان آن اعلان اولیه فراتر از بودجه را ازدست ندهد.
  • سریع باشید: عملیات بازپس‌گیری باید تسکین فوری ارائه دهد. محاسبات پیچیده درطول فشار، عملکرد را بدتر می‌کند.
  • از تخصیص دادن اجتناب کنید: در داخل callback، اشیاء جدید تخصیص ندهید یا رشته‌های جدید را شروع نکنید، زیرا انجام این کار می‌تواند باعث شود سیستم‌عامل فوراً بازیابی مستقیم را راه‌اندازی کند.
  • تمرکز بر اهداف با بازده بالا: حذف کردن Bitmaps بزرگ، میان‌گیرهای رندر، یا بستن فایل‌های نگاشت‌شده در حافظه بسیار مؤثرتر از آزاد کردن تعداد زیادی شیء کوچک است.

میانای برنامه‌سازی کاربردی NDK بومی (<android/memory_budget_manager.h>)

برنامه‌های بومی می‌توانند از C NDK API که توسط libandroid.so ارائه شده است، از Android 17 QPR2 (سطح API 37.2) استفاده کنند.

پیکربندی CMake

find_library(android-lib android)
target_link_libraries(my_native_engine PRIVATE ${android-lib})

گنجاندن استفاده از سرصفحه و پُرسمان

#include <android/memory_budget_manager.h>

// Query current memory usage
int64_t process_usage = AMemoryBudgetManager_getProcessCurrentUsageBytes();
int64_t package_usage = AMemoryBudgetManager_getPackageCurrentUsageBytes();

// Query current budget
int64_t process_budget = 0;
AMemoryBudgetResult result = AMemoryBudgetManager_getProcessBudget(&process_budget);
if (result == AMEMORY_BUDGET_RESULT_SUCCESS) {
    // Current budget available in process_budget
} else if (result == AMEMORY_BUDGET_RESULT_LIMIT_IS_DISABLED) {
    // No budget is currently active
}

پیکربندی پویا برای بودجه بومی

// Set a tighter process budget (e.g. 160MB)
AMemoryBudgetResult result = AMemoryBudgetManager_setProcessBudget(160LL * 1024 * 1024);
if (result != AMEMORY_BUDGET_RESULT_SUCCESS) {
    const char* error_msg = AMemoryBudgetManager_resultToString(result);
    // Handle error (e.g. AMEMORY_BUDGET_RESULT_ERROR_EXCEEDS_MANIFEST_LIMIT)
}

// Clear the dynamic budget to resume manifest limits (or unconstrained baseline)
AMemoryBudgetManager_clearProcessBudget();

نظارت بر رویدادهای فشار حافظه

‫NDK دو روش برای نظارت بر رویدادهای حافظه ارائه می‌دهد:

  1. High-Level Watcher (AMemoryBudgetManager_Watcher_create): Monitors events on an ALooper with automatic debouncing.
  2. توصیف‌گر فایل سطح پایین: AMemoryBudgetManager_getProcessMemoryPressureFd یک توصیف‌گر فایل بومی برمی‌گرداند که می‌تواند مستقیماً در یک حلقه موتور epoll سفارشی ادغام شود.
void onMemoryPressure(int32_t event_mask, const AMemoryBudgetEvents* events, void* userdata) {
    // High-yield eviction of unused native textures or geometry caches
    purgeNativeTextureCaches();
}

// Register watcher on a background worker ALooper with a 1000ms debounce interval
AMemoryBudgetManagerWatcher* watcher = AMemoryBudgetManager_Watcher_create(
    background_looper,
    AMEMORY_BUDGET_MANAGER_EVENT_PROCESS,
    1000 /* debounce_ms */,
    &onMemoryPressure,
    NULL /* userdata */
);

// When done:
AMemoryBudgetManager_Watcher_destroy(watcher);

ناظر را به‌جای رشته اصلی میانای کاربر یا رشته پردازش به ALooper رشته عامل پس‌زمینه متصل کنید تا تخلیه حافظه نهان با پردازش فریم تداخل نداشته باشد. قبل‌از محدود کردن بودجه با AMemoryBudgetManager_setProcessBudget یا AMemoryBudgetManager_setPackageBudget، ناظر را ایجاد کنید تا برنامه‌تان رویداد اولیه فراتر از بودجه را ازدست ندهد. تا زمانی که فرایند یا بسته بیش‌از بودجه باقی بماند و سیستم‌عامل به بازیابی حافظه ادامه دهد، ناظر به‌طور مکرر (حداکثر یک‌بار در هر فاصله زمانی debounce_ms) فراخوانی برگشتی را فراخوانی می‌کند. فراخوانی برگشتی‌تان را یکسان‌توان کنید، یا اگر برنامه‌تان فقط به اعلان یک‌باره نیاز دارد، AMemoryBudgetManager_Watcher_destroy را فراخوانی کنید (که فراخوانی آن از داخل فراخوانی برگشتی ایمن است).

نمونه‌های Runtime API

مثال‌های زیر نحوه پیاده‌سازی میاناهای برنامه‌سازی کاربردی زمان اجرا را بااستفاده از میانای برنامه‌سازی کاربردی کیت توسعه نرم‌افزار Android (نوشته‌شده در Kotlin) و میانای برنامه‌سازی کاربردی NDK بومی (نوشته‌شده در ++C) نشان می‌دهد.

نمونه کیت توسعه نرم‌افزار Android: ویرایشگر تصویر تطبیقی

این مثال برنامه ویرایش تصویری (com.example.imageeditor) را نشان می‌دهد که مانیفست آن سقف ۲۵۶ مگابایتی را برای بوم چندلایه‌اش اعلام می‌کند:

<manifest ... >
    <application ... >
        <!-- Manifest ceiling accommodates the heaviest editing workload -->
        <memory-budget android:maxMb="256" />
    </application>
</manifest>

وقتی کاربر درحال مرور گالری ریزعکس سبک است، برنامه از میانای برنامه‌سازی کاربردی کیت توسعه نرم‌افزار Android در Kotlin استفاده می‌کند تا بودجه پردازش خود را به‌طور پویا به ۹۶ مگابایت کاهش دهد. وقتی کاربر بوم ویرایش چندلایه را باز می‌کند، برنامه بودجه پویا را پاک می‌کند تا سقف مانیفست کامل ۲۵۶ مگابایتی را بازیابی کند. همچنین یک OnOverBudgetListener را در HandlerThread پس‌زمینه Looper ثبت می‌کند تا قبل‌از اعمال بودجه گالری برای حذف نقشه‌های بیتی پیش‌نمایش ذخیره‌شده در حافظه نهان تحت فشار، بدون مسدود کردن رشته میانای کاربر، اقدام کند.

package com.example.imageeditor.ui

import android.app.Activity
import android.app.MemoryBudgetManager
import android.graphics.Bitmap
import android.os.Bundle
import android.os.HandlerThread
import android.util.Log
import android.util.LruCache

class ImageEditorActivity : Activity() {

    private lateinit var budgetManager: MemoryBudgetManager
    private lateinit var cleanupThread: HandlerThread

    // Thread-safe in-memory cache for rendered preview tiles (32MB limit)
    private val previewCache = object : LruCache<String, Bitmap>(32 * 1024 * 1024) {
        override fun sizeOf(key: String, value: Bitmap): Int = value.byteCount
    }

    private val overBudgetListener = MemoryBudgetManager.OnOverBudgetListener { budgetBytes ->
        Log.w(TAG, "Process memory pressure detected (budget: ${budgetBytes / 1048576}MB). Evicting preview cache.")
        previewCache.evictAll()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        budgetManager = getSystemService(MemoryBudgetManager::class.java)
        cleanupThread = HandlerThread("MemoryBudgetCleanup").apply { start() }
    }

    override fun onStart() {
        super.onStart()
        // Register listener on a background Looper before tightening the budget
        budgetManager.registerProcessOverBudgetListener(cleanupThread.looper, overBudgetListener)
        // Constrain memory during lightweight gallery browsing
        applyGalleryBudget()
    }

    override fun onStop() {
        super.onStop()
        budgetManager.unregisterProcessOverBudgetListener(overBudgetListener)
    }

    override fun onDestroy() {
        cleanupThread.quitSafely()
        super.onDestroy()
    }

    /**
     * Called when the user enters the high-resolution editing canvas.
     * Clears the dynamic budget, restoring the full 256MB manifest ceiling.
     */
    fun enterEditingCanvas() {
        // Clear the tighter dynamic budget to restore the full manifest ceiling (256MB)
        budgetManager.clearProcessBudget()
        Log.i(TAG, "Restored manifest budget ceiling (256MB) for editing canvas")
    }

    /**
     * Called when the user exits the editor back to the thumbnail gallery.
     * Re-applies the tighter dynamic budget.
     */
    fun exitToGallery() {
        previewCache.trimToSize(8 * 1024 * 1024)
        applyGalleryBudget()
    }

    private fun applyGalleryBudget() {
        try {
            // Dynamically tighten budget to 96MB for the lightweight gallery view
            budgetManager.processBudgetBytes = 96L * 1024L * 1024L
            Log.i(TAG, "Tighter dynamic budget applied for gallery: 96MB")
        } catch (e: IllegalArgumentException) {
            Log.e(TAG, "Could not apply dynamic budget", e)
        }
    }

    companion object {
        private const val TAG = "ImageEditor"
    }
}

نمونه NDK C++‎: موتور سه‌بعدی بومی

این مثال موتور بازی C++‎ بومی را نشان می‌دهد که بودجه‌های حافظه را براساس سطح کیفیت گرافیک فعال مدیریت می‌کند، با این فرض که مانیفست برنامه سقف ۵۱۲ مگابایت را برای تطبیق با گرافیک با کیفیت بالا (android:maxMb="512") اعلام می‌کند. موتور AMemoryBudgetManager_Watcher_create را در ALooper رشته عامل پس‌زمینه شروع می‌کند قبل‌از اینکه بودجه را برای پیش‌تنظیم‌های کیفیت پایین‌تر محدود کند، و mipmap بافت را هنگام فراتر رفتن از بودجه تخلیه می‌کند.

#include <android/memory_budget_manager.h>
#include <android/looper.h>
#include <android/log.h>

#define LOG_TAG "Native3DEngineMemory"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)

class MemoryGovernor {
public:
    MemoryGovernor() : mWatcher(nullptr) {}

    ~MemoryGovernor() {
        stopMonitoring();
    }

    // Configures process budget based on user graphics quality settings
    bool setQualityBudget(int qualityLevel) {
        int64_t targetBytes = 0;
        switch (qualityLevel) {
            case 0: // Low (budget: 128MB)
                targetBytes = 128LL * 1024 * 1024;
                break;
            case 1: // Medium (budget: 256MB)
                targetBytes = 256LL * 1024 * 1024;
                break;
            case 2: // High (budget: 512MB)
                targetBytes = 512LL * 1024 * 1024;
                break;
            default:
                // Clear dynamic override and restore manifest limit
                AMemoryBudgetManager_clearProcessBudget();
                return true;
        }

        AMemoryBudgetResult result = AMemoryBudgetManager_setProcessBudget(targetBytes);
        if (result != AMEMORY_BUDGET_RESULT_SUCCESS) {
            LOGW("Could not set quality budget: %s", AMemoryBudgetManager_resultToString(result));
            return false;
        }
        return true;
    }

    // Call on a background worker ALooper before tightening the budget with setQualityBudget
    bool startMonitoring(ALooper* backgroundLooper) {
        if (!backgroundLooper) return false;

        // Monitor process budget events, debounced to at most once every 1000ms
        mWatcher = AMemoryBudgetManager_Watcher_create(
            backgroundLooper,
            AMEMORY_BUDGET_MANAGER_EVENT_PROCESS,
            1000,
            &MemoryGovernor::onPressureEvent,
            this
        );
        return mWatcher != nullptr;
    }

    void stopMonitoring() {
        if (mWatcher) {
            AMemoryBudgetManager_Watcher_destroy(mWatcher);
            mWatcher = nullptr;
        }
    }

    void unloadUnusedTextures() {
        LOGW("Memory pressure callback triggered. Purging cached texture mipmaps...");
        // Fast, idempotent, high-yield eviction without allocating memory
    }

private:
    static void onPressureEvent(
        int32_t event_mask,
        const AMemoryBudgetEvents* events,
        void* userdata
    ) {
        auto* governor = static_cast<MemoryGovernor*>(userdata);
        governor->unloadUnusedTextures();
    }

    AMemoryBudgetManagerWatcher* mWatcher;
};