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

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

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

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

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

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

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

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

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

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

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

اعلان‌های <memory-budget> روی دستگاه‌هایی که اندروید 17 QPR2 (سطح API 37.2) و بالاتر را اجرا می‌کنند، اعمال می‌شوند. در نسخه‌های پایین‌تر اندروید، تجزیه‌کننده‌ی مانیفست پلتفرم با خیال راحت عناصر 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 ، آنها را لغو می‌کنند.

چگونه ایالت‌های بودجه‌ای، ایالت‌های فرآیندی را ترسیم می‌کنند

این پلتفرم، وضعیت‌های بودجه‌ی مانیفست را بر اساس RunningAppProcessInfo.importance به وضعیت‌های فرآیند زمان اجرا نگاشت می‌کند:

  • foreground : تعاملات فعال و قابل مشاهده کاربر، مانند میزبانی یک فعالیت از سر گرفته شده یا حفظ وضعیت بالای برنامه هنگام خاموش شدن صفحه نمایش.
  • perceptible : بارهای کاری که بدون پنجره قابل مشاهده برای کاربر قابل درک هستند، مانند پخش فعال رسانه، ناوبری گام به گام، ضبط دوربین یا میکروفون، دانلودهای فعال در پس‌زمینه یا سرویس‌های همگام‌سازی داده‌ها در پیش‌زمینه. در حالی که معیارهای داخلی پلتفرم ممکن است برخی از این بارهای کاری را 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
IMPORTANCE_CACHED
کارهای پس‌زمینه، گیرنده‌ها، همگام‌سازی پس‌زمینه، فرآیندهای ذخیره‌شده در حافظه پنهان

وضعیت فرآیند و بودجه برنامه خود را بررسی کنید

یک راه برای بررسی وضعیت و اهمیت فرآیند فعال برنامه شما در طول توسعه، پرس و جو از Activity Manager با استفاده از 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 ظاهر می‌شود.
  • در جدول کنترل فرآیند OOM، prcp نشان می‌دهد که فرآیند تحت ردیف اولویت محسوس ارزیابی می‌شود که مربوط به بودجه perceptible است.

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

adb shell dumpsys meminfo <package-name>

از اندروید ۱۷ به بعد، این خروجی شامل یک بخش بودجه حافظه است که سقف محدودیت فعال، منبع محدودیت (مانند 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:additionalMbPerDensity ) : مگابایت‌ها را متناسب با نسبت چگالی نمایشگر نسبت به mdpi (1.0x / 160 dpi) اضافه می‌کند. این مورد زمانی مناسب است که استفاده از حافظه با چگالی رابط کاربری، مانند ذخیره سازی فایل‌های رستری با وضوح بالاتر یا فایل‌های رابط کاربری، مقیاس‌بندی شود:

    <!-- Baseline 180MB + 16MB per 1.0x density ratio -->
    <memory-budget
        android:maxMb="180"
        android:additionalMbPerDensity="16" />
    

    در یک نمایشگر mdpi (1.0x)، بودجه برابر است با 180 + 16 × 1 = 196 مگابایت. در یک نمایشگر xxhdpi (3.0x)، بودجه به 180 + 16 × 3 = 228 مگابایت افزایش می‌یابد.

  • مقیاس‌بندی بر اساس وضوح فیزیکی نمایشگر ( 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" />
    

    در یک نمایشگر 1080p (1080 × 2400 ≈ 2.59M پیکسل)، این مقدار تقریباً 41.4 مگابایت به بودجه پایه اضافه می‌کند. در یک نمایشگر 1440p (1440 × 3120 ≈ 4.49M پیکسل)، این مقدار تقریباً 71.8 مگابایت اضافه می‌کند.

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

تخصص در طراحی دستگاه‌هایی با فرم فاکتور متفاوت

هنگام ارسال یک فایل 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) بیان می‌شوند و به cgroup memory.current charge لینوکس نگاشت می‌شوند (که حافظه مشترک مانند Zygote را شامل نمی‌شود).

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

APIهای زمان اجرا (گزینه پویای ثانویه)

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

رابط برنامه‌نویسی کاربردی (API) زمان اجرا به شما امکان می‌دهد:

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

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

سرویس سیستمی MemoryBudgetManager برای برنامه‌های نوشته شده با کاتلین و جاوا از اندروید ۱۷ QPR2 (نسخه فرعی SDK، سطح API ۳۷.۲ / Build.VERSION_CODES_FULL.CINNAMON_BUN_2 ) در دسترس است.

سرویس را پس بگیرید

قبل از دسترسی به MemoryBudgetManager ، با استفاده از SDK_INT_FULL بررسی کنید که دستگاه نسخه پایین‌تری از اندروید ۱۷ 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()

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

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

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

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

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

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)

بهترین شیوه‌ها برای تماس‌های برگشتی با بودجه‌ی بیش از حد:

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

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

برنامه‌های بومی می‌توانند از API C NDK که توسط libandroid.so ارائه شده است، از اندروید 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 با قابلیت حذف خودکار مانع (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 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);

مثال‌های API زمان اجرا

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

مثال SDK اندروید: ویرایشگر تصویر تطبیقی

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

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

وقتی کاربر در حال مرور گالری تصاویر کوچک است، برنامه از API اندروید SDK در کاتلین استفاده می‌کند تا به صورت پویا بودجه پردازش خود را به ۹۶ مگابایت کاهش دهد. وقتی کاربر بوم ویرایش چندلایه را باز می‌کند، برنامه بودجه پویا را پاک می‌کند تا سقف مانیفست کامل ۲۵۶ مگابایتی را بازیابی کند. همچنین یک 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;
};