หน้านี้อธิบายวิธีลดการใช้งานหน่วยความจำภายในแอปอย่างมีประสิทธิภาพ ดูข้อมูลเกี่ยวกับวิธีที่ระบบปฏิบัติการ Android จัดการหน่วยความจำได้ที่ภาพรวมของการจัดการหน่วยความจำ
หน่วยความจำแบบสุ่ม (RAM) เป็นทรัพยากรที่มีค่าสำหรับสภาพแวดล้อมการพัฒนาซอฟต์แวร์
ทุกประเภท และมีค่ามากยิ่งขึ้นสำหรับระบบปฏิบัติการบนอุปกรณ์เคลื่อนที่ซึ่งมักมี
หน่วยความจำจริงที่จำกัด แม้ว่าทั้ง Android Runtime (ART) และเครื่องเสมือน Dalvik จะทำการระบบจัดการหน่วยความจำที่ไม่ใช้แล้วตามปกติ แต่ก็ไม่ได้หมายความว่า คุณจะละเลยเวลาและตำแหน่งที่แอปจัดสรรและปล่อยหน่วยความจำได้ คุณยังคงต้องหลีกเลี่ยงการทำให้เกิดหน่วยความจำรั่ว ซึ่งมักเกิดจากการเก็บการอ้างอิงออบเจ็กต์ไว้ในตัวแปรสมาชิกแบบคงที่ และปล่อยออบเจ็กต์ Reference
ในเวลาที่เหมาะสมตามที่กำหนดโดยการเรียกกลับของวงจร
ลดร่องรอยของโค้ดและทรัพยากรของแอป
ทรัพยากรและไลบรารีบางอย่างภายในโค้ดอาจใช้หน่วยความจำโดยที่คุณไม่รู้ตัว ขนาดโดยรวมของแอป รวมถึงไลบรารีของบุคคลที่สามหรือทรัพยากรที่ฝังไว้ อาจส่งผลต่อปริมาณหน่วยความจำที่แอปใช้ คุณสามารถปรับปรุงการใช้หน่วยความจำของแอปได้โดยการนำคอมโพเนนต์ ทรัพยากร และไลบรารีที่ซ้ำซ้อน ไม่จำเป็น หรือมีขนาดใหญ่เกินไปออกจากโค้ด
ลดขนาดแอปโดยรวมด้วยการเปิดใช้ R8
โค้ดแอปพลิเคชันที่คอมไพล์แล้วเป็นส่วนที่ใช้งานอยู่ของร่องรอยหน่วยความจำรันไทม์ คลาส เมธอด การอ้างอิงไลบรารี และค่าคงที่สตริงทุกรายการต้อง โหลดลงใน RAM เมื่อเรียกใช้ ยิ่งโค้ดเบสที่คอมไพล์มีขนาดใหญ่เท่าใด แอปก็จะยิ่งต้องใช้ RAM จริงมากขึ้นเท่านั้น
คุณใช้ R8 เพื่อลดร่องรอยหน่วยความจำของแอปได้ แม้ว่า R8 จะเป็นที่รู้จักกันในเรื่องการลดขนาด APK แต่ก็มีผลกระทบเชิงบวกโดยตรงต่อหน่วยความจำรันไทม์ (RAM) R8 จะวิเคราะห์ไบต์โค้ดของแอปเพื่อลบโค้ดที่ไม่ได้ใช้ ผสานคลาสที่ซ้ำซ้อน เมธอดอินไลน์ และลดขนาดตัวระบุ การโหลดไบต์โค้ดที่คอมไพล์แล้วจาก APK ลงใน RAM น้อยลงจะช่วยลดร่องรอยหน่วยความจำพื้นฐานโดยรวมของแอป นอกจากนี้ การลดขนาดชื่อคลาส เมธอด และฟิลด์ให้เป็นตัวระบุที่สั้นลงยังช่วยลดค่าใช้จ่ายของ RAM โดยตรงด้วย การเพิ่มประสิทธิภาพ เช่น การผสานคลาสและการแทรกเมธอดอย่างกว้างขวาง ยังช่วยแทนที่การค้นหาและการจัดสรรรันไทม์ที่มีค่าใช้จ่ายสูง ซึ่งส่งผลให้มีการเพิ่มประสิทธิภาพหน่วยความจำฮีปและหน่วยความจำสแต็ก
ทำความเข้าใจกฎการเก็บ
กฎการเก็บรักษาคือวิธีการกำหนดค่าที่บอก R8 ว่าส่วนใดของโค้ดที่ต้องเก็บรักษาไว้ในระหว่างการเพิ่มประสิทธิภาพ เพื่อป้องกันไม่ให้ระบบนำโค้ดที่แอปใช้พึ่งพาออกหรือลดขนาด ดูข้อมูลเพิ่มเติมได้ที่ภาพรวมของกฎการเก็บ
กฎการเก็บรักษาที่เขียนไม่ดีจะป้องกันไม่ให้ R8 เพิ่มประสิทธิภาพโค้ดเบสส่วนใหญ่ หลีกเลี่ยงกฎการเก็บที่กว้างเกินไปและทำตามแนวทางปฏิบัติแนะนำต่อไปนี้
- กฎส่วนกลางที่ควรหลีกเลี่ยง
-dontoptimize: ปิดใช้การเพิ่มประสิทธิภาพสำหรับทั้งแอปโดยสมบูรณ์ ส่งผลให้ไฟล์ที่เรียกใช้งานมีขนาดใหญ่ขึ้นและทำงานช้าลง-dontshrink: ป้องกันการนำโค้ดและทรัพยากรที่ไม่ได้ใช้ออก-dontobfuscate: ป้องกันการลดขนาดชื่อ ทำให้พลาดการประหยัดหน่วยความจำที่มีค่า (โดยเฉพาะในแอปขนาดใหญ่)
หลีกเลี่ยงการใช้อักขระไวด์การ์ดในระดับแพ็กเกจ: กฎแบบกว้าง เช่น
-keep class com.example.package.** { *; }บังคับให้ R8 เก็บรักษาทุกคลาส ฟิลด์ และเมธอดในแพ็กเกจนั้น ซึ่งจะหยุดความสามารถของ R8 ในการนำออก เพิ่มประสิทธิภาพ หรือลดขนาดโค้ดในแพ็กเกจนั้นโดยสิ้นเชิงใช้ไฟล์การกำหนดค่า R8 เริ่มต้น: ใช้
proguard-android-optimize.txtเสมอ
ดูข้อมูลเพิ่มเติมเกี่ยวกับการเขียนกฎการเก็บรักษาได้ที่ภาพรวมของกฎการเก็บรักษา ดูรูปแบบที่ควรใช้และควรหลีกเลี่ยงได้ที่แนวทางปฏิบัติแนะนำในการใช้กฎ
เครื่องมือวิเคราะห์การกำหนดค่า R8 จะให้ข้อมูลเชิงลึกเกี่ยวกับการกำหนดค่า R8 และวิธีที่กฎการเก็บแต่ละรายการส่งผลต่อแอปของคุณ ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีระบุกฎที่บล็อกการเพิ่มประสิทธิภาพได้ที่เครื่องมือวิเคราะห์การกำหนดค่า R8
โปรดระมัดระวังในการใช้ไลบรารีภายนอก
โค้ดไลบรารีภายนอกมักไม่ได้เขียนขึ้นสำหรับสภาพแวดล้อมบนอุปกรณ์เคลื่อนที่และอาจทำงานไม่ มีประสิทธิภาพเมื่อใช้ในไคลเอ็นต์บนอุปกรณ์เคลื่อนที่ เมื่อใช้ไลบรารีภายนอก คุณอาจต้องเพิ่มประสิทธิภาพไลบรารีนั้นสำหรับอุปกรณ์เคลื่อนที่ วางแผนงานนี้ล่วงหน้าและวิเคราะห์ไลบรารีในแง่ของขนาดโค้ดและรอยเท้า RAM ก่อนใช้งาน
แม้แต่ไลบรารีบางรายการที่เพิ่มประสิทธิภาพสำหรับมือถือแล้วก็อาจทำให้เกิดปัญหาเนื่องจากการติดตั้งใช้งานที่แตกต่างกัน เช่น ไลบรารีหนึ่งอาจใช้ Protobuf แบบ Lite ขณะที่อีกไลบรารีหนึ่งใช้ Protobuf แบบ Micro ซึ่งส่งผลให้มีการใช้งาน Protobuf 2 แบบที่แตกต่างกันในแอปของคุณ กรณีนี้อาจเกิดขึ้นกับการใช้งานการบันทึก ข้อมูลวิเคราะห์ เฟรมเวิร์กการโหลดรูปภาพ การแคช และอื่นๆ อีกมากมายที่คุณไม่คาดคิด
แม้ว่าการเพิ่มประสิทธิภาพแอปโดยใช้ R8 จะนำโค้ดที่ไม่ได้ใช้ออกจาก การอ้างอิงได้ แต่ประสิทธิภาพมักถูกจำกัดโดยการกำหนดค่าภายในของไลบรารี ตัวอย่างเช่น กฎการเก็บรักษาแบบกว้างหรือการใช้การสะท้อนภายในไลบรารี อาจทำให้ R8 ไม่สามารถลดขนาดโค้ด ซึ่งส่งผลให้ใช้หน่วยความจำมากขึ้น ดูกลยุทธ์ในการเลือกไลบรารีที่มีประสิทธิภาพได้ที่เลือกไลบรารี อย่างชาญฉลาด
หลีกเลี่ยงการใช้ไลบรารีที่ใช้ร่วมกันสำหรับฟีเจอร์เพียง 1 หรือ 2 รายการจากหลายสิบรายการ อย่า ดึงโค้ดและค่าใช้จ่ายที่ไม่จำเป็นจำนวนมาก เมื่อพิจารณาว่าจะใช้ไลบรารีหรือไม่ ให้มองหาการใช้งานที่ตรงกับความต้องการของคุณอย่างยิ่ง ไม่เช่นนั้น คุณอาจเลือกที่จะสร้างการติดตั้งใช้งานของคุณเอง
ใช้ Hilt หรือ Dagger 2 สำหรับการแทรกทรัพยากร Dependency
เฟรมเวิร์กการแทรกทรัพยากร Dependency ช่วยลดความซับซ้อนของโค้ดที่คุณเขียนและมอบสภาพแวดล้อมที่ปรับเปลี่ยนได้ซึ่งมีประโยชน์สำหรับการทดสอบและการเปลี่ยนแปลงการกำหนดค่าอื่นๆ
หากต้องการใช้เฟรมเวิร์กการแทรกทรัพยากร Dependency ในแอป ให้พิจารณา ใช้ Hilt หรือ Dagger Hilt เป็นไลบรารีการแทรกทรัพยากร Dependency สำหรับ Android ที่ทำงานบน Dagger Dagger ไม่ใช้การสะท้อนเพื่อสแกนโค้ดของแอป คุณสามารถใช้การติดตั้งใช้งานแบบคงที่ของ Dagger ในเวลาคอมไพล์ในแอป Android ได้โดยไม่ต้องเสียค่าใช้จ่ายที่ไม่จำเป็นในรันไทม์หรือการใช้งานหน่วยความจำ
เฟรมเวิร์กการแทรกทรัพยากร Dependency อื่นๆ ที่ใช้การสะท้อนจะเริ่มต้นกระบวนการ โดยการสแกนโค้ดเพื่อหาคำอธิบายประกอบ กระบวนการนี้อาจต้องใช้รอบ CPU และ RAM มากขึ้นอย่างมาก และอาจทำให้เกิดความล่าช้าที่สังเกตได้เมื่อเปิดแอป
เมื่อใช้การแทรกการอ้างอิง ให้ระมัดระวังเพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำโดยตรวจสอบว่า ออบเจ็กต์มีขอบเขตที่เหมาะสม การเก็บออบเจ็กต์ไว้นานกว่าที่จำเป็น โดยการเชื่อมโยงออบเจ็กต์กับวงจรที่ไม่ถูกต้องอาจทำให้เกิดหน่วยความจำรั่วได้ ดูข้อมูลเพิ่มเติมได้ที่คำแนะนำเกี่ยวกับการหลีกเลี่ยงหน่วยความจำรั่วด้วยออบเจ็กต์ที่กำหนดขอบเขต
ตั้งใจโหลดรูปภาพ
โดยปกติแล้วบิตแมปกราฟิกมักเป็นออบเจ็กต์ทั่วไปที่ใหญ่ที่สุดซึ่งอยู่ในหน่วยความจำของแอป แม้ว่าคุณจะทำงานกับไฟล์ที่บีบอัด เช่น JPEG แต่ไฟล์ก็ต้อง ขยายเป็นบิตแมปที่ไม่ได้บีบอัดเพื่อแสดงบนหน้าจอ ไฟล์รูปภาพที่บีบอัดขนาดเล็กอาจขยายเป็นบิตแมปขนาดใหญ่มากได้
เช่น บิตแมปส่วนใหญ่ใช้การกำหนดค่า ARGB_8888 ซึ่งหมายความว่าแต่ละพิกเซลต้องใช้หน่วยความจำ 4 ไบต์ โดยใช้ 1 ไบต์สำหรับสีแดง สีเขียว สีน้ำเงิน และอัลฟ่า (ความโปร่งใส) หากคุณมี JPEG ขนาด 100 KB และแสดงในมุมมองขนาด 1000×1000 พิกเซล บิตแมปจะต้องใช้หน่วยความจำ 4 ไบต์สำหรับแต่ละพิกเซลจาก 1,000,000 พิกเซล ซึ่งรวมเป็นหน่วยความจำ 4 MB
คุณสามารถทำหลายอย่างเพื่อเพิ่มประสิทธิภาพการใช้รูปภาพได้ เช่น การใช้ไลบรารีการโหลดรูปภาพจะช่วยให้คุณปล่อยหน่วยความจำได้เมื่อไม่จำเป็น ดูข้อมูลเกี่ยวกับวิธีจัดการรูปภาพอย่างมีประสิทธิภาพได้ที่การเพิ่มประสิทธิภาพรูปภาพ บิตแมป
ตรวจสอบหน่วยความจำที่พร้อมใช้งานและการใช้งานหน่วยความจำ
คุณต้องค้นหาปัญหาการใช้งานหน่วยความจำของแอปก่อนจึงจะแก้ไขได้ โดย เครื่องมือสร้างโปรไฟล์หน่วยความจำของ Android Studio จะช่วยคุณค้นหาและวินิจฉัยปัญหาเกี่ยวกับหน่วยความจำ ด้วยวิธีต่อไปนี้
- ดูว่าแอปจัดสรรหน่วยความจำอย่างไรเมื่อเวลาผ่านไป เครื่องมือสร้างโปรไฟล์หน่วยความจำจะแสดงกราฟแบบเรียลไทม์ของปริมาณหน่วยความจำที่แอปใช้ จำนวนออบเจ็กต์ Java ที่จัดสรร และเวลาที่เกิดระบบจัดการหน่วยความจำที่ไม่ใช้แล้ว
- เริ่มเหตุการณ์ระบบจัดการหน่วยความจำที่ไม่ใช้แล้วและถ่ายภาพรวมของฮีป Java ขณะที่แอปทํางาน
- บันทึกการจัดสรรหน่วยความจำของแอป ตรวจสอบออบเจ็กต์ที่จัดสรรทั้งหมด ดูสแต็กเทรซสำหรับการจัดสรรแต่ละรายการ และข้ามไปยังโค้ดที่เกี่ยวข้องในตัวแก้ไข Android Studio
โปรไฟล์หน่วยความจำยังผสานรวมกับไลบรารีการตรวจหาหน่วยความจำรั่วไหลของ LeakCanary ด้วย การใช้ LeakCanary จะช่วยให้คุณย้ายการวิเคราะห์หน่วยความจำรั่วไหลจากอุปกรณ์ทดสอบไปยังคอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์ได้ ซึ่งจะช่วยเพิ่มความเร็วของเวิร์กโฟลว์ได้อย่างมาก ดูข้อมูลเพิ่มเติมได้ที่บันทึกประจำรุ่นของ Android Studio
คุณสามารถใช้เครื่องมืออื่นๆ เพื่อวินิจฉัยปัญหาเกี่ยวกับหน่วยความจำโดยอิงตามข้อมูลจาก ผู้ใช้ที่เรียกใช้แอปเวอร์ชันที่ใช้งานจริง
- ใช้ Android Vitals เพื่อติดตามเหตุการณ์การหยุดทำงานเนื่องจากหน่วยความจำไม่เพียงพอ (LMK)
- ใช้ Profiling Manager เพื่อติดตามข้อผิดพลาดเนื่องจากไม่มีหน่วยความจำ รวมถึง ลักษณะการทำงานที่ผิดปกติของแอปซึ่งอาจเกิดจากหน่วยความจำรั่ว
ปล่อยหน่วยความจำเพื่อตอบสนองต่อเหตุการณ์
Android สามารถเรียกคืนหน่วยความจำจากแอปหรือหยุดแอปทั้งหมดได้หากจำเป็น
เพื่อเพิ่มหน่วยความจำสำหรับงานที่สำคัญ ดังที่อธิบายไว้ในภาพรวมของการจัดการหน่วยความจำ หากต้องการช่วยปรับสมดุลหน่วยความจำของระบบและหลีกเลี่ยงไม่ให้ระบบต้องหยุดกระบวนการของแอป คุณสามารถใช้ComponentCallbacks2
อินเทอร์เฟซในคลาส Activity ได้ เมธอด Callback onTrimMemory() ที่ระบุจะแจ้งให้แอปทราบถึงเหตุการณ์ที่เกี่ยวข้องกับวงจรหรือหน่วยความจำ ซึ่งเป็นโอกาสที่ดีที่แอปจะลดการใช้งานหน่วยความจำโดยสมัครใจ
การเพิ่มพื้นที่หน่วยความจำอาจช่วยลดความถี่ที่
กระบวนการที่หยุดแอปเมื่อหน่วยความจำเหลือน้อยจะปิดแอปของคุณ
การติดตั้งใช้งาน onTrimMemory() ควรเน้นเฉพาะเหตุการณ์ TRIM_MEMORY_UI_HIDDEN และ TRIM_MEMORY_BACKGROUND (เริ่มตั้งแต่ Android 14 เป็นต้นไป ระบบจะไม่ส่งการแจ้งเตือนสำหรับค่าคงที่อื่นๆ ที่เลิกใช้งานแล้วอีกต่อไป ระบบได้เลิกใช้งานค่าคงที่เหล่านั้นอย่างเป็นทางการใน Android 15)
TRIM_MEMORY_UI_HIDDEN: สัญญาณนี้บ่งบอกว่า UI ของแอปได้เปลี่ยนออกจากมุมมองของผู้ใช้แล้ว การเปลี่ยนนี้เป็นโอกาสในการปล่อยการจัดสรรหน่วยความจำจำนวนมากที่เชื่อมโยงกับ UI อย่างเคร่งครัด เช่น บิตแมป บัฟเฟอร์การเล่นวิดีโอ หรือทรัพยากรภาพเคลื่อนไหวที่ซับซ้อนTRIM_MEMORY_BACKGROUND: สัญญาณนี้บ่งบอกว่ากระบวนการของคุณ อยู่ในเบื้องหลัง และตอนนี้เป็นตัวเลือกในการสิ้นสุดเพื่อตอบสนอง ความต้องการหน่วยความจำส่วนกลางของระบบ หากต้องการขยายระยะเวลาที่กระบวนการของคุณ ยังคงอยู่ในสถานะแคช และลดจำนวนการเริ่มแอปแบบเย็น คุณ ควรกำหนดให้ปล่อยทรัพยากรใดๆ ที่สร้างขึ้นใหม่ได้ง่าย เมื่อผู้ใช้กลับมาใช้เซสชันต่อ
ตัวอย่างโค้ดนี้แสดงวิธีใช้onTrimMemory() Callback เพื่อตอบสนอง
ต่อเหตุการณ์ต่างๆ ที่เกี่ยวข้องกับหน่วยความจำ
Kotlin
import android.content.ComponentCallbacks2
// Other import statements.
class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
// Other activity code.
/**
* Release memory when the UI becomes hidden or when system resources become low.
* @param level the memory-related event that is raised.
*/
override fun onTrimMemory(level: Int) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
Java
import android.content.ComponentCallbacks2;
// Other import statements.
public class MainActivity extends AppCompatActivity
implements ComponentCallbacks2 {
// Other activity code.
/**
* Release memory when the UI becomes hidden or when system resources become low.
* @param level the memory-related event that is raised.
*/
public void onTrimMemory(int level) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
ตรวจสอบว่าคุณต้องการหน่วยความจำเท่าใด
Android จะกำหนดขีดจำกัดที่แน่นอนสำหรับขนาดฮีปที่จัดสรรให้แต่ละแอปเพื่อให้กระบวนการหลายอย่างทำงานได้พร้อมกัน ขีดจำกัดขนาดฮีปที่แน่นอนจะแตกต่างกันไปในแต่ละอุปกรณ์ตามจำนวน RAM ที่อุปกรณ์มีโดยรวม หากแอปของคุณมีฮีปเต็มความจุและพยายามจัดสรรหน่วยความจำเพิ่มเติม ระบบจะแสดง OutOfMemoryError
หากต้องการหลีกเลี่ยงปัญหาหน่วยความจำไม่เพียงพอ คุณสามารถค้นหาระบบเพื่อดูว่าอุปกรณ์ปัจจุบันมี
พื้นที่ฮีปเท่าใดได้ โดยเรียกใช้ getMemoryInfo() เพื่อค้นหาตัวเลขนี้ ระบบจะแสดงออบเจ็กต์ ActivityManager.MemoryInfo ซึ่งให้ข้อมูลเกี่ยวกับสถานะหน่วยความจำปัจจุบันของอุปกรณ์ รวมถึงหน่วยความจำที่ใช้ได้ หน่วยความจำทั้งหมด และ
เกณฑ์หน่วยความจำ ซึ่งเป็นระดับหน่วยความจำที่ระบบเริ่มหยุดกระบวนการทำงาน นอกจากนี้ ออบเจ็กต์ ActivityManager.MemoryInfo ยังแสดง lowMemory ซึ่งเป็นบูลีนอย่างง่ายที่บอกว่าอุปกรณ์มีหน่วยความจำเหลือน้อยหรือไม่
ข้อมูลโค้ดตัวอย่างต่อไปนี้แสดงวิธีใช้เมธอด getMemoryInfo()
ในแอป
Kotlin
fun doSomethingMemoryIntensive() {
// Before doing something that requires a lot of memory,
// check whether the device is in a low memory state.
if (!getAvailableMemory().lowMemory) {
// Do memory intensive work.
}
}
// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
return ActivityManager.MemoryInfo().also { memoryInfo ->
activityManager.getMemoryInfo(memoryInfo)
}
}
Java
public void doSomethingMemoryIntensive() {
// Before doing something that requires a lot of memory,
// check whether the device is in a low memory state.
ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();
if (!memoryInfo.lowMemory) {
// Do memory intensive work.
}
}
// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
return memoryInfo;
}
ตรวจสอบการสิ้นสุดกระบวนการเนื่องจากหน่วยความจำเหลือน้อย
การหยุดทำงานเนื่องจากหน่วยความจำไม่เพียงพอ (LMK) ที่ผู้ใช้มองเห็นได้จะเกิดขึ้นเมื่อหน่วยความจำของระบบเหลือน้อยอย่างยิ่ง
เมื่อหน่วยความจำเหลือน้อย lmkd (Low Memory Killer
Daemon) จะสิ้นสุดกระบวนการตาม oom_adj_score แอปที่แคชไว้หรือเรียกใช้บริการที่ไม่มี UI ที่เชื่อมโยง (เช่น งาน) จะมีคะแนนสูงสุดและจะถูกปิดก่อน หากหน่วยความจำยังคงเหลือน้อยมาก ระบบจะบังคับให้ Daemon เรียกคืนหน่วยความจำจากกระบวนการที่มี oom_adj_score เป็น 0
เนื่องจากคะแนนดังกล่าวสงวนไว้สำหรับแอปที่มองเห็นได้ การสิ้นสุดแอปจึงส่งผลให้เกิดการออกจากการประมวลผลโดยทันทีและไม่ราบรื่น สำหรับผู้ใช้ปลายทาง ดูเหมือนว่าแอป
ขัดข้อง ซึ่งมักจะข้ามกลไกการบันทึกสถานะวงจรมาตรฐานและ
ส่งผลให้ความคืบหน้าของผู้ใช้สูญหาย
การหยุดทำงานของกระบวนการที่ทำงานอยู่เบื้องหน้าเป็นจุดสนใจหลักใน Android Vitals เนื่องจากเป็นตัวแทนที่มีความเที่ยงตรงสูงสำหรับการจัดการหน่วยความจำที่ไม่ถูกต้อง แม้ว่าอัตรา LMK ที่สูงกว่า 1% จะบ่งบอกถึงความจำเป็นอย่างยิ่งในการดำเนินการทันที แต่อัตราที่ต่ำก็ไม่ได้บ่งบอกถึงความสมบูรณ์เสมอไป อัตรา LMK ที่ผู้ใช้รับรู้ต่ำอาจหมายความว่า Daemon LMK จะหยุดกระบวนการทำงานบ่อยครั้งขณะที่กระบวนการทำงานอยู่เบื้องหลัง ซึ่งจะทำให้ประสิทธิภาพ "การเริ่มต้นแบบวอร์ม" และความลื่นไหลของการทำงานแบบมัลติทาสก์ลดลง ดังนั้น เราขอแนะนำให้ปฏิบัติตามแนวทางปฏิบัติแนะนำด้านหน่วยความจำโดยไม่คำนึงถึงคะแนน LMK ปัจจุบัน เพื่อให้มั่นใจถึงความเสถียรในระยะยาวและความสมบูรณ์ของอุปกรณ์
ใช้ ProfilingManager เพื่อติดตามปัญหาเกี่ยวกับหน่วยความจำ
แพลตฟอร์ม Android มี
ProfilingManager ซึ่งเป็น
API การสังเกตการณ์ขั้นสูงที่ช่วยให้คุณบันทึกข้อมูลผู้ใช้ในเวอร์ชันที่ใช้งานจริงได้โดยอิงตามทริกเกอร์ที่คุณตั้งค่าไว้ การทำเช่นนี้จะช่วยให้คุณระบุปัญหาเกี่ยวกับหน่วยความจำที่จำลองซ้ำได้ยาก
ทริกเกอร์ใหม่ 2 รายการที่เปิดตัวใน Android 17 มีประโยชน์อย่างยิ่ง ในการตรวจหาปัญหาเกี่ยวกับหน่วยความจำ
TRIGGER_TYPE_OOMระบุว่าแอปได้ส่งOutOfMemoryErrorโดยจะทริกเกอร์ ในครั้งถัดไปที่แอปเริ่มทำงานหลังจากแอปขัดข้อง เมื่อแอปได้ลงทะเบียนเพื่อ ทริกเกอร์การสร้างโปรไฟล์TRIGGER_TYPE_ANOMALYจะทริกเกอร์เมื่อระบบตรวจพบพฤติกรรมที่ผิดปกติจากแอป ซึ่งอาจเกิดจากการใช้งานหน่วยความจำมากเกินไปและอื่นๆ โดยจะทริกเกอร์หลังจากที่แอปมีการใช้งานหน่วยความจำมากเกินไป และก่อนที่ระบบจะดำเนินการใดๆ เพื่อหยุดกระบวนการที่ทำให้เกิดปัญหา ตัวอย่างเช่น หากแอปมีการใช้งานหน่วยความจำเกินขีดจำกัดหน่วยความจำที่เปิดตัวใน Android 17TRIGGER_TYPE_ANOMALYจะทริกเกอร์ก่อนที่ระบบจะปิดแอป
ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ ProfilingManager เพื่อลงทะเบียนและเรียกทริกเกอร์โดยอัตโนมัติได้ในเอกสารประกอบการสร้างโปรไฟล์ตามทริกเกอร์
นอกจากนี้ คุณยังใช้การสร้างโปรไฟล์ที่ขับเคลื่อนด้วยแอปเพื่อ กำหนดจุดเริ่มต้นและจุดสิ้นสุดของการติดตามด้วยตนเองได้ด้วย เราขอแนะนำให้ทำเช่นนี้เพื่อบันทึกฮีปดัมป์หรือโปรไฟล์ฮีปด้วยตนเองในส่วนที่คุณสงสัยว่าอาจมีหน่วยความจำรั่วไหลหรือมีการใช้งานหน่วยความจำมากเกินไป
ใช้โครงสร้างโค้ดที่มีประสิทธิภาพด้านหน่วยความจำมากขึ้น
ฟีเจอร์บางอย่างของ Android, คลาส Java และโครงสร้างโค้ดบางอย่างใช้หน่วยความจำมากกว่า ฟีเจอร์อื่นๆ คุณลดปริมาณหน่วยความจำที่แอปใช้ได้โดยเลือกตัวเลือก ที่มีประสิทธิภาพมากขึ้นในโค้ด
ใช้บริการเท่าที่จำเป็น
เราขอแนะนำอย่างยิ่งว่าอย่าปล่อยให้บริการทำงานเมื่อไม่จำเป็น การปล่อยให้บริการที่ไม่จำเป็นทำงานอยู่เป็นหนึ่งในข้อผิดพลาดด้านการจัดการหน่วยความจำที่แย่ที่สุดที่แอป Android สามารถทำได้ หากแอปต้องใช้ service เพื่อทำงานในเบื้องหลัง อย่าปล่อยให้ทำงานอยู่เว้นแต่จะต้องเรียกใช้ Job หยุดบริการเมื่อทำงานเสร็จแล้ว ไม่เช่นนั้น คุณอาจทำให้เกิดหน่วยความจำรั่วไหล
เมื่อคุณเริ่มบริการ ระบบจะพยายามรักษากระบวนการสำหรับบริการนั้นให้ทำงานต่อไป ลักษณะการทำงานนี้ทำให้กระบวนการของบริการมีค่าใช้จ่ายสูงมากเนื่องจาก RAM ที่บริการใช้จะยังคงไม่พร้อมใช้งานสำหรับกระบวนการอื่นๆ ซึ่งจะลด จำนวนกระบวนการที่แคชไว้ที่ระบบสามารถเก็บไว้ในแคช LRU ทำให้ การสลับแอปมีประสิทธิภาพน้อยลง ซึ่งอาจทำให้เกิดการสลับหน่วยความจำในระบบเมื่อหน่วยความจำเหลือน้อยและระบบไม่สามารถรักษากระบวนการให้เพียงพอต่อการโฮสต์บริการทั้งหมดที่กำลังทำงานอยู่
โดยทั่วไปแล้ว ให้หลีกเลี่ยงการใช้บริการที่ทำงานตลอดเวลาเนื่องจากบริการดังกล่าวต้องใช้หน่วยความจำที่มีอยู่ตลอดเวลา เราขอแนะนำให้คุณใช้การติดตั้งใช้งานอื่นแทน เช่น WorkManager
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้ WorkManager เพื่อกำหนดเวลาการประมวลผลในเบื้องหลังได้ที่งานที่ทำงานตลอดเวลา
ใช้คอนเทนเนอร์ข้อมูลที่เพิ่มประสิทธิภาพ
คลาสบางคลาสที่ภาษาโปรแกรมมีให้ไม่ได้เพิ่มประสิทธิภาพสําหรับ
การใช้งานในอุปกรณ์เคลื่อนที่ ตัวอย่างเช่น การใช้งานทั่วไปของ
HashMap อาจใช้หน่วยความจําอย่างไม่มีประสิทธิภาพเนื่องจากต้องมีออบเจ็กต์รายการแยกสําหรับการแมปทุกรายการ
เฟรมเวิร์ก Android มีคอนเทนเนอร์ข้อมูลที่ได้รับการเพิ่มประสิทธิภาพหลายรายการ ซึ่งรวมถึง
SparseArray
SparseBooleanArray และ
LongSparseArray ตัวอย่างเช่น คลาส SparseArray มีประสิทธิภาพมากกว่าเนื่องจากหลีกเลี่ยงไม่ให้ระบบต้องทำการแปลงกล่องอัตโนมัติสำหรับคีย์และบางครั้งก็สำหรับค่า ซึ่งจะสร้างออบเจ็กต์อีก 1 หรือ 2 รายการต่อรายการ
หากจำเป็น คุณสามารถเปลี่ยนไปใช้อาร์เรย์ดิบเพื่อโครงสร้างข้อมูลที่กระชับได้เสมอ
โปรดระมัดระวังการแยกโค้ด
นักพัฒนาแอปมักใช้แอบสแตรกชันเป็นแนวทางปฏิบัติในการเขียนโปรแกรมที่ดีเนื่องจากช่วยปรับปรุงความยืดหยุ่นและการบำรุงรักษาโค้ดได้ อย่างไรก็ตาม โดยทั่วไปแล้วแอบสแตรกชันต้องใช้โค้ดเพิ่มเติมในการดำเนินการ ดังที่ระบุไว้ในลดร่องรอยของโค้ดและทรัพยากรของแอป ฐานของโค้ดที่คอมไพล์ขนาดใหญ่จะเพิ่ม RAM จริงที่แอปต้องการโดยตรง หากแอบสแตรกชันไม่เป็นประโยชน์อย่างมาก ให้หลีกเลี่ยงแอบสแตรกชัน
ใช้ Protobuf แบบ Lite สำหรับข้อมูลที่ทำการซีเรียล
Protocol Buffers (protobuf) เป็นกลไกที่ขยายได้ซึ่ง Google ออกแบบมาเพื่อ จัดรูปแบบ Structured Data ให้เป็นอนุกรม โดยไม่ขึ้นอยู่กับภาษาและแพลตฟอร์ม ซึ่งคล้ายกับ XML แต่มีขนาดเล็กกว่า เร็วกว่า และง่ายกว่า หากคุณใช้ Protobuf สำหรับข้อมูล ให้ใช้ Protobuf แบบ Lite ในโค้ดฝั่งไคลเอ็นต์เสมอ Protobuf ปกติจะสร้างโค้ดที่ยาวมาก ซึ่งจะเพิ่มร่องรอยของโค้ดแอปใน RAM (ดูจัดการและเพิ่มประสิทธิภาพร่องรอยของโค้ดแอป) และทำให้ขนาด APK เพิ่มขึ้น
ดูข้อมูลเพิ่มเติมได้ที่อ่าน Protobuf
โปรดระมัดระวังเกี่ยวกับหน่วยความจำรั่ว
การจัดการการอ้างอิงที่ไม่เหมาะสมอาจทำให้เกิดหน่วยความจำรั่ว ซึ่งออบเจ็กต์จะมีอายุการใช้งานนานกว่าอายุการใช้งานที่เป็นประโยชน์ ทำให้ตัวเก็บขยะไม่สามารถเรียกคืนหน่วยความจำของออบเจ็กต์ที่รั่วได้ ใช้การออกแบบที่รับรู้ถึงวงจรขององค์ประกอบเพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำ
หลีกเลี่ยงการเสียหน่วยความจำ
เหตุการณ์ระบบจัดการหน่วยความจำที่ไม่ใช้แล้วไม่ส่งผลต่อประสิทธิภาพของแอป อย่างไรก็ตาม เหตุการณ์ระบบจัดการหน่วยความจำที่ไม่ใช้แล้วหลายรายการที่เกิดขึ้นในช่วงเวลาสั้นๆ อาจทำให้แบตเตอรี่หมดเร็วขึ้น รวมถึงอาจเพิ่มเวลาในการตั้งค่าเฟรมเล็กน้อยเนื่องจากการโต้ตอบที่จำเป็นระหว่างระบบจัดการหน่วยความจำที่ไม่ใช้แล้วกับเทรดของแอป ยิ่งระบบใช้เวลาในการระบบจัดการหน่วยความจำที่ไม่ใช้แล้วนานเท่าใด แบตเตอรี่ก็จะยิ่งหมดเร็วขึ้นเท่านั้น
โดยปกติแล้ว การเสียหน่วยความจำ อาจทำให้เกิดเหตุการณ์ระบบจัดการหน่วยความจำที่ไม่ใช้แล้วจำนวนมาก ในทางปฏิบัติ การเสียหน่วยความจำ อธิบายถึงจำนวนออบเจ็กต์ชั่วคราวที่จัดสรร ซึ่งเกิดขึ้นในช่วงเวลาหนึ่งๆ
เช่น คุณอาจจัดสรรออบเจ็กต์ชั่วคราวหลายรายการภายในลูป for
หรืออาจสร้างออบเจ็กต์ Paint หรือ Bitmap ใหม่ภายในฟังก์ชัน onDraw() ของมุมมอง ในทั้ง 2 กรณี แอปจะสร้างออบเจ็กต์จำนวนมากอย่างรวดเร็วในปริมาณสูง
ซึ่งอาจใช้หน่วยความจำทั้งหมดที่มีในรุ่นอายุน้อยอย่างรวดเร็ว ทำให้เกิดเหตุการณ์ระบบจัดการหน่วยความจำที่ไม่ใช้แล้ว
ใช้ Memory Profiler เพื่อค้นหา ตำแหน่งในโค้ดที่มีการใช้หน่วยความจำสูงก่อนที่จะแก้ไขได้
หลังจากระบุส่วนที่เป็นปัญหาในโค้ดแล้ว ให้พยายามลดจำนวนการจัดสรรภายในส่วนที่สำคัญต่อประสิทธิภาพ ลองย้ายสิ่งต่างๆ ออกจากลูปด้านใน หรืออาจย้ายไปยังโครงสร้างการจัดสรรตามโรงงาน
นอกจากนี้ คุณยังประเมินได้ว่าพูลออบเจ็กต์เป็นประโยชน์ต่อกรณีการใช้งานหรือไม่ เมื่อใช้พูลออบเจ็กต์ แทนที่จะทิ้งอินสแตนซ์ออบเจ็กต์ คุณจะปล่อยอินสแตนซ์นั้นลงในพูล หลังจากที่ไม่จำเป็นต้องใช้อีกต่อไป เมื่อใดก็ตามที่จำเป็นต้องใช้อินสแตนซ์ออบเจ็กต์ประเภทนั้น คุณจะรับอินสแตนซ์จากพูลได้แทนที่จะจัดสรร
ประเมินประสิทธิภาพอย่างละเอียดเพื่อพิจารณาว่าพูลออบเจ็กต์เหมาะกับสถานการณ์ที่กำหนดหรือไม่ มีกรณีที่พูลออบเจ็กต์อาจทำให้ประสิทธิภาพแย่ลง แม้ว่าพูลจะหลีกเลี่ยงการจัดสรร แต่ก็ทำให้เกิดค่าใช้จ่ายอื่นๆ ตัวอย่างเช่น การดูแลรักษาพูลมักเกี่ยวข้องกับการซิงโครไนซ์ ซึ่งมีค่าใช้จ่ายที่ไม่ควรมองข้าม นอกจากนี้ การล้างอินสแตนซ์ออบเจ็กต์ที่รวมไว้เพื่อหลีกเลี่ยง การรั่วไหลของหน่วยความจำระหว่างการเผยแพร่ และการเริ่มต้นระหว่างการได้มาอาจมีค่าใช้จ่ายที่ไม่ใช่ 0
การเก็บอินสแตนซ์ออบเจ็กต์ไว้ในพูลมากกว่าที่จำเป็นยังเป็นภาระต่อ การเก็บขยะด้วย แม้ว่าพูลออบเจ็กต์จะช่วยลดจำนวนการเรียกใช้การเก็บขยะ แต่ก็ทำให้ปริมาณงานที่จำเป็นสำหรับการเรียกใช้แต่ละครั้งเพิ่มขึ้น เนื่องจากเป็นสัดส่วนกับจำนวนไบต์ที่ใช้งานอยู่ (เข้าถึงได้)