งบประมาณหน่วยความจำของแอปช่วยให้แอปประกาศงบประมาณหน่วยความจำของตัวเองได้ ซึ่งจะแจ้งให้ระบบลดการใช้หน่วยความจำของแอปเมื่อแอปใช้หน่วยความจำเกินงบประมาณที่ตั้งไว้ ซึ่งมีประโยชน์อย่างยิ่งสำหรับแอปของระบบและแอปที่มาพร้อมเครื่อง หรือสำหรับแอปที่กำหนดเป้าหมายเป็นอุปกรณ์ที่มีข้อจำกัดด้านหน่วยความจำ ซึ่งนักพัฒนาแอปทราบชุดการทำงานของหน่วยความจำที่คาดไว้และต้องการให้มั่นใจว่าแอปจะไม่ใช้ทรัพยากร RAM ที่ใช้ร่วมกันของระบบมากเกินไป
ระบบจะรักษาสมดุลของงบประมาณโดยใช้การดีดหน่วยความจำและการสลับเพื่อ นำหน้าหน่วยความจำที่ไม่ได้ใช้เมื่อเร็วๆ นี้ออก เพื่อให้แอปมุ่งเน้นการใช้หน่วยความจำ ในชุดการทำงานปัจจุบัน เมื่อแอปใช้งบประมาณเกินที่ประกาศไว้ ระบบปฏิบัติการจะกำหนดเป้าหมายการเรียกคืนที่แอปนั้นโดยเฉพาะ
- ระบบจะล้างหน้าเว็บที่สำรองข้อมูลไว้ในไฟล์ (เช่น โค้ดที่ไม่ได้ใช้งานและชิ้นงานที่แมป) ก่อน เนื่องจากสามารถอ่านซ้ำจากพื้นที่เก็บข้อมูลได้หากจำเป็น
- ระบบจะเขียนหน้าที่มีการสำรองข้อมูลไฟล์ซึ่งมีการแก้ไขกลับไปยังที่เก็บข้อมูลและนำออก
- หน้าหน่วยความจำที่ไม่ระบุชื่อ (เช่น การจัดสรรฮีป) จะได้รับการบีบอัดและ สลับไปยัง zRAM
ตราบใดที่งบประมาณไม่เกินชุดการทำงาน แอปจะทำงานได้ดี โดยใช้หน่วยความจำไม่เกินงบประมาณที่ตั้งไว้ ระบบปฏิบัติการ จะนำหน่วยความจำที่ไม่ได้ใช้ออกและบีบอัดหน้าฮีปที่ไม่ได้ใช้งานเพื่อสลับเพื่อให้การจัดสรรหน่วยความจำ ยังคงมีขอบเขตโดยไม่สิ้นสุดกระบวนการ
ประกาศงบประมาณในไฟล์ Manifest ของ Android
การประกาศงบประมาณหน่วยความจำใน AndroidManifest.xml เป็นวิธีหลักและ
แนะนำในการกำหนดงบประมาณ โดยไม่ต้องใช้โค้ดรันไทม์ มีผลทันทีเมื่อเริ่มต้นกระบวนการ และมีสัญญาที่ชัดเจนสำหรับระบบปฏิบัติการ
ประกาศงบประมาณพื้นฐาน
สำหรับแอปส่วนใหญ่ การกำหนดงบประมาณเดียวสำหรับแอปพลิเคชันก็เพียงพอแล้ว
ประกาศองค์ประกอบ <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>
ซึ่งจะกำหนดงบประมาณหน่วยความจำที่ใช้งานอยู่ 256 MB ในกระบวนการและสถานะทั้งหมดสำหรับ แพ็กเกจ เมื่อหน่วยความจำที่ใช้ของแอปเกิน 256MB ระบบปฏิบัติการจะตัดแต่งหน้าหน่วยความจำที่ไม่ได้ใช้งานโดยใช้การขับออกและการสลับ
ปรับงบประมาณตามสถานะกระบวนการ
แอปต้องใช้หน่วยความจำในปริมาณที่แตกต่างกันไปตามระดับการมองเห็นของผู้ใช้ ดังนี้
- เบื้องหน้า: กระบวนการนี้โฮสต์กิจกรรมที่มองเห็นได้ซึ่งโต้ตอบกับผู้ใช้ โดยปกติแล้ว สถานะนี้จะมีร่องรอยการใช้งานมากที่สุดเนื่องจาก UI และกราฟิกที่ใช้งานอยู่
- รับรู้ได้: ผู้ใช้รับรู้กระบวนการได้ แต่กระบวนการไม่ได้โฮสต์ หน้าต่างที่มองเห็นได้ (เช่น การโฮสต์บริการที่ทำงานอยู่เบื้องหน้าสำหรับการเล่นสื่อ การนำทางแบบเลี้ยวต่อเลี้ยว หรือวิธีการป้อนข้อมูลที่ใช้งานอยู่)
- เบื้องหลัง: กระบวนการกำลังเรียกใช้ งานในเบื้องหลัง ตัวรับ หรือ การซิงค์ข้อมูล โดยคาดว่าจะใช้พื้นที่น้อยที่สุด
คุณสามารถประกาศคําสั่ง <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="foreground" ในอนุประโยคแรก เงื่อนไขที่ไม่มี android:state จะทำหน้าที่เป็นข้อความสำรองเริ่มต้นสำหรับทุกสถานะ ข้อความที่จำกัดมากกว่าด้านล่างจะลบล้างงบประมาณเมื่อแอปเปลี่ยน
เป็นสถานะ perceptible หรือ background
แอปแบบหลายกระบวนการ
หากแอปพลิเคชันแบ่งงานออกเป็นหลายกระบวนการ ให้กําหนดค่า
งบประมาณกระบวนการเฉพาะโดยใช้แท็ก <process> ภายใน <processes>
ตัวอย่างเช่น ลองพิจารณาแอปสตรีมมิงเพลง (com.example.radio)
- กระบวนการหลัก: โฮสต์ UI ที่มองเห็นได้และเครื่องมือเล่นเสียง
(
MediaSessionServiceที่มีmediaPlaybackบริการที่ทำงานอยู่เบื้องหน้า) เมื่อมองเห็นได้ กระบวนการจะทำงานภายใต้งบประมาณเบื้องหน้า 180 MB เมื่อผู้ใช้ออกจากแอปขณะที่เพลงยังเล่นอยู่ กระบวนการจะเข้าสู่สถานะperceptibleซึ่งงบประมาณ 64 MB เพียงพอสำหรับเครื่องมือการเล่นและบัฟเฟอร์เสียง - กระบวนการซิงค์ (
: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 มีกลไกทางเลือก 2 อย่างเพื่อปรับขนาดงบประมาณแบบไดนามิกตามข้อกำหนดของจอแสดงผล
ปรับขนาดตามกลุ่มความหนาแน่นของการแสดงผล (
android:additionalMbPerDensity): เพิ่มเมกะไบต์ตามสัดส่วนของอัตราส่วนความหนาแน่นของจอแสดงผล เทียบกับmdpi(1.0x / 160 dpi) วิธีนี้เหมาะสำหรับกรณีที่การใช้หน่วยความจำ ปรับขนาดตามกลุ่มความหนาแน่นของ UI เช่น การแคชภาพวาดแรสเตอร์ความละเอียดสูงหรือชิ้นส่วน UI<!-- Baseline 180MB + 16MB per 1.0x density ratio --> <memory-budget android:maxMb="180" android:additionalMbPerDensity="16" />ใน
mdpiจอแสดงผล (1.0x) งบประมาณคือ 180 + 16 × 1 = 196 MB ในxxhdpiจอแสดงผล (3.0x) ขนาด งบประมาณจะปรับเป็น 180 + 16 × 3 = 228 MBปรับขนาดตามความละเอียดของจอแสดงผลจริง (
android:additionalBytesPerDisplayPixel): เพิ่มไบต์โดยตรงต่อ พิกเซลของจอแสดงผลจริง (ความกว้าง × ความสูง) ซึ่งเหมาะสำหรับ แอปพลิเคชันที่จัดสรรพื้นผิวกราฟิกแบบเต็มหน้าจอ บัฟเฟอร์การแสดงผล หรือ แคชรูปภาพแบบเต็มความละเอียดที่การใช้หน่วยความจำจะปรับขนาดตาม จำนวนพิกเซลของจอแสดงผลดิบโดยตรง แทนที่จะเป็นกลุ่มความหนาแน่นของ UI<!-- 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.59 ล้านพิกเซล) การดำเนินการนี้ จะเพิ่มงบประมาณพื้นฐานอีกประมาณ 41.4 MB ในจอแสดงผล 1440p (1440 × 3120 ≈ 4.49 ล้านพิกเซล) จะเพิ่ม ≈ 71.8 MB
แอตทริบิวต์ทั้ง 2 นี้เป็นทางเลือก เลือกแอตทริบิวต์ที่ตรงกับค่าตัวคูณมาตราส่วนหลักของแอป และหลีกเลี่ยงการรวมทั้ง 2 อย่างไว้ในเงื่อนไขเดียวกัน
ปรับแต่งสำหรับรูปแบบของอุปกรณ์
เมื่อจัดส่ง APK ในโทรศัพท์ แท็บเล็ต และ Wear OS ให้ใช้แอตทริบิวต์
android:feature เพื่อปรับงบประมาณสำหรับเป้าหมายฮาร์ดแวร์ที่แตกต่างกัน
ในนาฬิกา Wear OS นั้น RAM มีข้อจำกัด และ UI รวมถึงชุดฟีเจอร์ของแอปจะ
เรียบง่ายกว่ามาก คุณสามารถประกาศงบประมาณที่เข้มงวดมากขึ้นซึ่งออกแบบมาสำหรับ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> หลายรายการสำหรับแอปพลิเคชันหรือกระบวนการ ระบบจะประเมินองค์ประกอบเหล่านั้นตามลำดับที่ประกาศไว้ในไฟล์ Manifest ระบบจะบังคับใช้ข้อกำหนดด้านงบประมาณที่เกี่ยวข้องล่าสุด
เนื่องจากงบประมาณสุดท้ายที่ใช้ได้จะเป็นผู้ชนะ การจัดลำดับจึงมีความสำคัญ วางงบประมาณพื้นฐานที่ทั่วไปที่สุดไว้ก่อนเสมอ ตามด้วยการลบล้างที่เฉพาะเจาะจงมากขึ้น (เช่น ข้อกำหนดเฉพาะรัฐหรือข้อกำหนดเฉพาะฮาร์ดแวร์)
การอ้างอิงแอตทริบิวต์ XML
แอตทริบิวต์ขนาดหน่วยความจำทั้งหมดแสดงในหน่วยเมกะไบต์ (MB) และแมปกับค่าใช้จ่ายของ cgroup memory.current ใน Linux (ซึ่งไม่รวมหน่วยความจำที่ใช้ร่วมกัน เช่น Zygote)
| แอตทริบิวต์ | รูปแบบ | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
android:maxMb |
จำนวนเต็ม (> 0) | ต้องระบุ | ขีดจำกัดงบประมาณหน่วยความจำที่ใช้งานอยู่พื้นฐานในหน่วย MB |
android:state |
ค่าแจกแจง | ช่วง | สถานะกระบวนการที่งบประมาณนี้ใช้: foreground, perceptible หรือ background |
android:additionalMbPerDensity |
จำนวนเต็ม (≥ 0) | 0 |
เมกะไบต์เพิ่มเติมที่จะเพิ่มต่อหน่วยของอัตราส่วนความหนาแน่นของจอแสดงผลเทียบกับ mdpi (1.0x) |
android:additionalBytesPerDisplayPixel |
จำนวนเต็ม (≥ 0) | 0 |
ไบต์เพิ่มเติมที่จัดสรรต่อพิกเซลของจอแสดงผลจริง (ความกว้าง × ความสูง) ซึ่งมีประโยชน์สำหรับบัฟเฟอร์พื้นผิวและบิตแมป |
android:feature |
สตริง | ช่วง | จำกัดข้อความเฉพาะอุปกรณ์ที่ประกาศฟีเจอร์ฮาร์ดแวร์ที่เฉพาะเจาะจง: watch, automotive หรือ leanback |
API รันไทม์ (ตัวเลือกแบบไดนามิกรอง)
การประกาศงบประมาณแบบคงที่ใน AndroidManifest.xml เป็นวิธีแก้ปัญหาที่แนะนำ
สำหรับแอปเกือบทั้งหมด อย่างไรก็ตาม สำหรับแอปพลิเคชันที่มีภาระงานแบบไดนามิกหรือสำหรับการทดสอบรันไทม์ Android มี API ของรันไทม์ SDK และ NDK เป็นตัวเลือกสำรอง
API รันไทม์ช่วยให้คุณทำสิ่งต่อไปนี้ได้
- ค้นหาการใช้งานหน่วยความจำปัจจุบันและงบประมาณที่มีผล
- ปรับงบประมาณกระบวนการลงโดยอัตโนมัติ
- ฟังเหตุการณ์ที่เกินงบประมาณเพื่อล้างแคชในเชิงรุกก่อนที่ระบบปฏิบัติการจะทริกเกอร์การเรียกคืนโดยตรง
Kotlin API (MemoryBudgetManager)
บริการของระบบ MemoryBudgetManager พร้อมให้บริการตั้งแต่
Android 17 QPR2 (Android 26Q4 SDK รุ่นที่เผยแพร่, ระดับ API 37.2 /
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-declared ceiling
Log.e(TAG, "Requested budget exceeds manifest or system ceiling", e)
}
// Clear the dynamic process budget to restore the manifest limit
budgetManager.clearProcessBudget()
ฟังการเรียกกลับเมื่อมีการแจ้งเตือนว่าใช้งบประมาณเกิน
แอปสามารถลงทะเบียน 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)
แนวทางปฏิบัติแนะนำสำหรับการเรียกกลับเมื่อใช้งบประมาณเกิน
- รวดเร็ว: การดำเนินการขอคืนต้องให้ความช่วยเหลือทันที การคำนวณที่ซับซ้อน ระหว่างที่มีแรงกดดันจะทำให้ประสิทธิภาพแย่ลง
- หลีกเลี่ยงการจัดสรร: อย่าจัดสรรออบเจ็กต์ใหม่หรือเริ่มเธรดใหม่ ภายในโค้ดเรียกกลับ เนื่องจากอาจทําให้ระบบปฏิบัติการ เรียกคืนโดยตรงทันที
- มุ่งเน้นเป้าหมายที่ให้ผลลัพธ์สูง: การนำ Bitmap ขนาดใหญ่ บัฟเฟอร์การแสดงผล หรือ การปิดไฟล์ที่แมปหน่วยความจำออกจะมีประสิทธิภาพมากกว่าการปล่อยออบเจ็กต์ขนาดเล็ก จำนวนมาก
Native NDK API (<android/memory_budget_manager.h>)
แอปที่มาพร้อมเครื่องสามารถใช้ C NDK API ที่แสดงโดย libandroid.so
การกำหนดค่า 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
AMemoryBudgetManager_clearProcessBudget();
ตรวจสอบเหตุการณ์แรงดันหน่วยความจำ
NDK มีวิธีตรวจสอบเหตุการณ์เกี่ยวกับหน่วยความจำ 2 วิธี ได้แก่
- High-Level Watcher (
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
ตัวอย่างต่อไปนี้แสดงวิธีติดตั้งใช้งาน Runtime API ใน Kotlin และ C++
ตัวอย่าง Kotlin: โปรแกรมแก้ไขรูปภาพแบบปรับขนาดได้
ตัวอย่างนี้แสดงแอปแก้ไขรูปภาพ (com.example.imageeditor) ที่
เพิ่มงบประมาณหน่วยความจำแบบไดนามิกเมื่อผู้ใช้เปิดการแก้ไขแบบหลายเลเยอร์
และล้างงบประมาณแบบไดนามิกเมื่อกลับไปที่มุมมอง
แกลเลอรีภาพขนาดย่อ นอกจากนี้ยังลงทะเบียน 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)
}
override fun onStop() {
super.onStop()
budgetManager.unregisterProcessOverBudgetListener(overBudgetListener)
}
/**
* Called when the user enters the high-resolution editing canvas.
*/
fun enterEditingCanvas() {
try {
// Dynamically set budget to 256MB for the editing canvas
budgetManager.processBudgetBytes = 256L * 1024L * 1024L
Log.i(TAG, "Dynamic budget applied: 256MB")
} catch (e: IllegalArgumentException) {
Log.e(TAG, "Could not apply dynamic budget", e)
}
}
/**
* Called when the user exits the editor back to the thumbnail gallery.
*/
fun exitToGallery() {
previewCache.trimToSize(8 * 1024 * 1024)
// Clear dynamic budget; restores the baseline manifest budget
budgetManager.clearProcessBudget()
}
companion object {
private const val TAG = "ImageEditor"
}
}
ตัวอย่าง NDK C++: เครื่องมือ 3 มิติแบบเนทีฟ
ตัวอย่างนี้แสดงเกมเอนจิน C++ แบบเนทีฟที่จัดการงบประมาณหน่วยความจำตามระดับคุณภาพกราฟิกที่ใช้งานอยู่ โดยใช้ 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;
};