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

بودجه‌های حافظه برنامه به برنامه‌ها اجازه می‌دهد بودجه حافظه‌ای برای خودشان اعلام کنند که به سیستم می‌گوید وقتی برنامه از بودجه تعیین‌شده‌اش بیشتر استفاده می‌کند، استفاده از حافظه آن را کاهش دهد. این کار به‌ویژه برای برنامه‌های سیستم و دسته‌ای یا برای برنامه‌هایی که دستگاه‌های دارای حافظه محدود را هدف‌یابی می‌کنند مفید است، زیرا توسعه‌دهنده مجموعه کاری حافظه موردانتظار خود (حافظه‌ای که برنامه به‌طور فعال برای کاری که کاربر درحال انجام آن است نیاز دارد) را می‌داند و می‌خواهد مطمئن شود که برنامه‌اش از منابع مشترک 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>

مثال خلاصه زیر ورودی‌های گزارش فرایند و کنترل OOM را برای برنامه‌ای که سرویس پیش‌زمینه‌ای اجرا می‌کند نشان می‌دهد:

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" />

        <!-- Dedicated background sync process -->
        <processes>
            <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>

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

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

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

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

    <!-- Baseline 180MB + 16MB per 1.0x density ratio -->
    <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" />
    

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

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

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

هنگام ارسال 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 memory.current (که حافظه مشترک مثل 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 (نسخه فرعی SDK، سطح API 37.2 / Build.VERSION_CODES_FULL.CINNAMON_BUN_2) دردسترس است.

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

قبل‌از دسترسی به MemoryBudgetManager، بااستفاده از SDK_INT_FULL بررسی کنید که دستگاه از نسخه پایین‌تر از Android 17 QPR2 استفاده نکند:

if (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()

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

عملکرد و خودتوان‌خواهی

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

  • خاصیت خودتکراری: همه «میاناهای برنامه‌سازی کاربردی» تغییر بودجه دارای خاصیت خودتکراری هستند. تماس با clearProcessBudget() یا clearPackageBudget() به‌طور مکرر یا زمانی که بودجه پویایی فعال نیست یک عملیات بی‌خطر است. به‌همین ترتیب، تنظیم setProcessBudgetBytes() یا setPackageBudgetBytes() روی مقدار یکسان به‌صورت متوالی باعث راه‌اندازی مجدد پیکربندی‌های سیستم اضافی نمی‌شود.
  • تعداد دفعات تماس: تنظیم، به‌روزرسانی، یا پاک کردن بودجه باعث می‌شود تماس بین‌فرایندی با خدمات سیستم برای به‌روزرسانی کنترل‌کننده حافظه سیستم‌عامل برای فرایند برقرار شود. اگرچه سبک است، اما کاملاً رایگان نیست. این APIها را درطول انتقال‌های چرخه حیات مجزا و نقاط عطف تکلیف فراخوانی کنید (مثل ورود یا خروج از فعالیت، شروع یا تکمیل پردازش پس‌زمینه، یا مدیریت درخواست‌های گفتار)، و از فراخوانی آن‌ها در حلقه‌های تنگ، رشته‌های پردازش صوتی، یا روال‌های پردازش هر قاب خودداری کنید.
  • تناوب پُرسمان: ویژگی‌های پُرسمان processCurrentUsageBytes و packageCurrentUsageBytes استفاده از حافظه زنده را از سرویس سیستم واکشی می‌کند. اگرچه پُرسمان‌ها سریع هستند، باید به‌جای اینکه در حلقه‌های پیوسته نظرسنجی شوند، درصورت نیاز فراخوانی شوند (مثلاً درحین انتقال تکلیف یا در داخل برگشت‌های OnOverBudgetListener).

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

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

val listener = MemoryBudgetManager.OnOverBudgetListener { budgetBytes ->
    Log.w(TAG, "Process exceeded memory budget of $budgetBytes bytes")
    // Proactively evict caches to release memory
    imageTileCache.evictAll()
}

// Register on the main Looper
budgetManager.registerProcessOverBudgetListener(mainLooper, listener)

// When done (e.g., in onStop)
budgetManager.unregisterProcessOverBudgetListener(listener)

روال‌های مطلوب برای تماس‌های برگشتی فراتر از بودجه:

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

Native NDK API (<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. تماشاگر سطح بالا (AMemoryBudgetManager_Watcher_create): رویدادهای ALooper را با لرزش‌گیری خودکار نظارت می‌کند.
  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 an ALooper with a 1000ms debounce interval
AMemoryBudgetManagerWatcher* watcher = AMemoryBudgetManager_Watcher_create(
    looper,
    AMEMORY_BUDGET_MANAGER_EVENT_PROCESS,
    1000 /* debounce_ms */,
    &onMemoryPressure,
    NULL /* userdata */
);

// When done:
AMemoryBudgetManager_Watcher_destroy(watcher);

مثال‌های Runtime API

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

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

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

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

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

package com.example.imageeditor.ui

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

class ImageEditorActivity : Activity() {

    private lateinit var budgetManager: MemoryBudgetManager

    // 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)
    }

    override fun onStart() {
        super.onStart()
        // Register listener for process-level memory breaches
        budgetManager.registerProcessOverBudgetListener(mainLooper, overBudgetListener)
        // Constrain memory during lightweight gallery browsing
        applyGalleryBudget()
    }

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

    /**
     * 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;
    }

    bool startMonitoring(ALooper* looper) {
        if (!looper) return false;

        // Monitor process budget events, debounced to at most once every 1000ms
        mWatcher = AMemoryBudgetManager_Watcher_create(
            looper,
            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, 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;
};