กำหนดงบประมาณหน่วยความจำของแอป

งบประมาณหน่วยความจำของแอปช่วยให้แอปประกาศงบประมาณหน่วยความจำของตัวเองได้ ซึ่งจะแจ้งให้ระบบลดการใช้หน่วยความจำของแอปเมื่อแอปใช้หน่วยความจำเกินงบประมาณที่ตั้งไว้ ซึ่งมีประโยชน์อย่างยิ่งสำหรับแอปของระบบและแอปที่มาพร้อมเครื่อง หรือสำหรับแอปที่กำหนดเป้าหมายเป็นอุปกรณ์ที่มีข้อจำกัดด้านหน่วยความจำ ซึ่งนักพัฒนาแอปทราบชุดการทำงานของหน่วยความจำที่คาดไว้และต้องการให้มั่นใจว่าแอปจะไม่ใช้ทรัพยากร RAM ที่ใช้ร่วมกันของระบบมากเกินไป

ระบบจะรักษาสมดุลของงบประมาณโดยใช้การดีดหน่วยความจำและการสลับเพื่อ นำหน้าหน่วยความจำที่ไม่ได้ใช้เมื่อเร็วๆ นี้ออก เพื่อให้แอปมุ่งเน้นการใช้หน่วยความจำ ในชุดการทำงานปัจจุบัน เมื่อแอปใช้งบประมาณเกินที่ประกาศไว้ ระบบปฏิบัติการจะกำหนดเป้าหมายการเรียกคืนที่แอปนั้นโดยเฉพาะ

  1. ระบบจะล้างหน้าเว็บที่สำรองข้อมูลไว้ในไฟล์ (เช่น โค้ดที่ไม่ได้ใช้งานและชิ้นงานที่แมป) ก่อน เนื่องจากสามารถอ่านซ้ำจากพื้นที่เก็บข้อมูลได้หากจำเป็น
  2. ระบบจะเขียนหน้าที่มีการสำรองข้อมูลไฟล์ซึ่งมีการแก้ไขกลับไปยังที่เก็บข้อมูลและนำออก
  3. หน้าหน่วยความจำที่ไม่ระบุชื่อ (เช่น การจัดสรรฮีป) จะได้รับการบีบอัดและ สลับไปยัง 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)

  1. กระบวนการหลัก: โฮสต์ UI ที่มองเห็นได้และเครื่องมือเล่นเสียง (MediaSessionService ที่มีmediaPlayback บริการที่ทำงานอยู่เบื้องหน้า) เมื่อมองเห็นได้ กระบวนการจะทำงานภายใต้งบประมาณเบื้องหน้า 180 MB เมื่อผู้ใช้ออกจากแอปขณะที่เพลงยังเล่นอยู่ กระบวนการจะเข้าสู่สถานะ perceptible ซึ่งงบประมาณ 64 MB เพียงพอสำหรับเครื่องมือการเล่นและบัฟเฟอร์เสียง
  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 มีกลไกทางเลือก 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 &approx; 2.59 ล้านพิกเซล) การดำเนินการนี้ จะเพิ่มงบประมาณพื้นฐานอีกประมาณ 41.4 MB ในจอแสดงผล 1440p (1440 × 3120 &approx; 4.49 ล้านพิกเซล) จะเพิ่ม &approx; 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 วิธี ได้แก่

  1. High-Level Watcher (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

ตัวอย่างต่อไปนี้แสดงวิธีติดตั้งใช้งาน 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;
};