بودجههای حافظه برنامه به برنامهها اجازه میدهد بودجه حافظهای برای خودشان اعلام کنند که به سیستم میگوید وقتی برنامه از بودجه تعیینشدهاش بیشتر استفاده میکند، استفاده از حافظه آن را کاهش دهد. این کار بهویژه برای برنامههای سیستم و دستهای یا برای برنامههایی که دستگاههای دارای حافظه محدود را هدفیابی میکنند مفید است، زیرا توسعهدهنده مجموعه کاری حافظه موردانتظار خود (حافظهای که برنامه بهطور فعال برای کاری که کاربر درحال انجام آن است نیاز دارد) را میداند و میخواهد مطمئن شود که برنامهاش از منابع مشترک RAM سیستم بیشازحد استفاده نمیکند.
بودجه بااستفاده از حذف حافظه و مبادله برای برداشتن صفحههای حافظهای که اخیراً استفاده نشدهاند و تمرکز ردپای حافظه برنامه بر مجموعه کاری فعلی آن متعادل نگه داشته میشود. وقتی برنامهای از بودجه اعلامشدهاش فراتر میرود، سیستمعامل بهطور خاص آن برنامه را برای پسگیری هدفیابی میکند:
- صفحههای پشتیبانگیریشده فایل پاکسازی میشوند (مثل داراییهای کد غیرفعال و نگاشتشده) ابتدا تخلیه میشوند زیرا درصورت نیاز میتوان آنها را از فضای ذخیرهسازی دوباره خواند.
- صفحههای پشتیبانگیریشده فایل کثیف در فضای ذخیرهسازی نوشته میشوند و تخلیه میشوند.
- صفحههای حافظه ناشناس (مثل تخصیصهای پشته) فشرده و به zRAM مبادله میشوند.
تا زمانی که مجموعه کاری از بودجه فراتر نرود، برنامه بهخوبی کار خواهد کرد و از حافظهای بیشتر از آنچه در بودجه تعیینشدهاش است استفاده نخواهد کرد. سیستمعامل حافظه استفادهنشده را تخلیه میکند و صفحههای پشته غیرفعال را فشرده میکند تا مبادله شود و تخصیصهای حافظه بدون خاتمه دادن به فرایند محدود باقی بماند.
وقتی برنامهای از بودجهاش فراتر میرود چه اتفاقی میافتد
فراتر رفتن از بودجه حافظه باعث نمیشود سیستم برنامه شما را ببندد یا خراب کند. درعوض، وقتی برنامهای از حافظهای بیشتر از آنچه بودجهاش اجازه میدهد استفاده میکند، سیستمعامل حافظهای را که برنامه مدتی است از آن استفاده نکرده است پیدا میکند و آن را بهطور ایمن کنار میگذارد تا زمانی که دوباره به آن نیاز شود. این کار باعث میشود حافظه برای تخصیص به برنامههای جدید آزاد شود و استفاده کلی برنامه از حافظه در حد بودجه باقی بماند.
تا زمانی که بین آنچه برنامه در هر زمان معین نیاز دارد (مجموعه کاری آن) و بودجه تعیینشده فضای خالی وجود داشته باشد، برنامه به عملکرد عادی خود ادامه میدهد. اگر برنامهای بودجهای تعیین کند که از مجموعه کاری آن کوچکتر باشد، درنتیجه عملکرد برنامه در زمان اجرا میتواند کند شود.
برای آشنایی با نحوه ردیابی و بازپسگیری حافظه توسط سیستمعامل، به راهنمای معماری حافظه مراجعه کنید:
- بازپسگیری و مبادله حافظه: توضیح میدهد که سیستمعامل چگونه صفحات پشتیبان فایل و پشته خصوصی ناشناس را به برنامه شما اختصاص میدهد و وقتی از بودجه فراتر میرود، صفحات سرد را بازپس میگیرد.
- مفاهیم بنیادی: توضیح میدهد چرا حافظه Zygote مشترک از بودجه برنامه شما مستثنا شده است و چگونه بودجههای مقیم با RSS ناشناس + مبادله در تلهمتری فیلد مرتبط است.
- بیتمپها و حافظه: توضیح میدهد که چگونه سختافزار بیتمپهای پشتیبان GPU و تخصیصهای DMA-BUF خارج از پشته برنامه و بودجه حافظه شما محاسبه میشوند.
اعلام بودجهها در مانیفست 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_FOREGROUNDIMPORTANCE_TOP_SLEEPING |
«فعالیت» نمایان ازسر گرفته شد، برنامه برتر با صفحه قفل، سرویس یا ارائهدهنده محتوا که با برنامه برتر محدود شده است |
perceptible |
IMPORTANCE_FOREGROUND_SERVICEIMPORTANCE_VISIBLE |
سرویسهای پیشزمینهای فعال برای بازپخش رسانه، ناوبری، بارگیری، یا همگامسازی؛ سرویس مرتبط با سرویس پیشزمینهای یا پرچمهای نمایان |
background |
IMPORTANCE_PERCEPTIBLEIMPORTANCE_CANT_SAVE_STATEIMPORTANCE_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) را درنظر بگیرید:
- فرایند اصلی: میزبان واسط کاربر نمایان و موتور بازپخش صدا است
(
MediaSessionServiceباmediaPlaybackخدمات پیشزمینهای). وقتی نمایان باشد، این فرایند تحت بودجه پیشزمینهای ۱۸۰ مگابایت کار میکند. وقتی کاربر درحالیکه موسیقی درحال پخش است از برنامه خارج میشود، فرایند وارد حالتperceptibleمیشود که در آن بودجه ۶۴ مگابایتی برای موتور بازپخش و بافر صوتی کافی است. - فرایند همگامسازی (
: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 دو روش برای نظارت بر رویدادهای حافظه ارائه میدهد:
- تماشاگر سطح بالا (
AMemoryBudgetManager_Watcher_create): رویدادهایALooperرا با لرزشگیری خودکار نظارت میکند. - توصیفگر فایل سطح پایین:
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;
};