หน้านี้อธิบายวิธีลดการใช้งานหน่วยความจำภายในแอปของคุณอย่างมีประสิทธิภาพ สำหรับข้อมูลเกี่ยวกับวิธีที่ระบบปฏิบัติการ 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 มากขึ้นอย่างมาก และอาจทำให้เกิดความล่าช้าที่สังเกตได้เมื่อเปิดแอป
เมื่อใช้ทรัพยากร Dependency ให้ระมัดระวังเพื่อหลีกเลี่ยงหน่วยความจำรั่วไหลโดยตรวจสอบว่าออบเจ็กต์มีขอบเขตที่เหมาะสม การเก็บออบเจ็กต์ไว้นานเกินความจำเป็น โดยการเชื่อมโยงออบเจ็กต์กับวงจรที่ไม่ถูกต้องอาจทำให้เกิดหน่วยความจำรั่วได้ ดูข้อมูลเพิ่มเติมได้ที่คำแนะนำเกี่ยวกับการหลีกเลี่ยงหน่วยความจำรั่วด้วยออบเจ็กต์ที่มีขอบเขต
ตั้งใจโหลดรูปภาพ
โดยปกติแล้วบิตแมปกราฟิกจะเป็นออบเจ็กต์ทั่วไปที่ใหญ่ที่สุดซึ่งอยู่ในหน่วยความจำของแอป แม้ว่าคุณจะทำงานกับไฟล์ที่บีบอัด เช่น 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 จะหยุดกระบวนการทำงานบ่อยครั้งขณะที่กระบวนการทำงานเหล่านั้นอยู่ใน เบื้องหลัง ซึ่งจะทำให้ประสิทธิภาพ "Warm Start" และความลื่นไหลของการทำงานแบบมัลติทาสก์ลดลง ดังนั้น เราขอแนะนำให้ปฏิบัติตามแนวทางปฏิบัติแนะนำด้านหน่วยความจำไม่ว่าคะแนน 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 ออกแบบมาเพื่อ จัดรูปแบบข้อมูลที่มีโครงสร้างให้เป็นอนุกรม โดยไม่ขึ้นอยู่กับภาษาและแพลตฟอร์ม ซึ่งคล้ายกับ XML แต่มีขนาดเล็กกว่า เร็วกว่า และง่ายกว่า หากคุณใช้ Protobuf สำหรับข้อมูล ให้ใช้ Protobuf แบบ Lite ในโค้ดฝั่งไคลเอ็นต์เสมอ Protobuf ปกติจะสร้างโค้ดที่ยาวมาก ซึ่งจะเพิ่มร่องรอยของโค้ดแอปใน RAM (ดูจัดการและเพิ่มประสิทธิภาพร่องรอยของโค้ดแอป) และทำให้ขนาด APK เพิ่มขึ้น
ดูข้อมูลเพิ่มเติมได้ที่ protobuf readme
โปรดระมัดระวังเกี่ยวกับหน่วยความจำรั่ว
การจัดการการอ้างอิงที่ไม่เหมาะสมอาจทำให้เกิดหน่วยความจำรั่ว ซึ่งออบเจ็กต์จะมีอายุการใช้งานนานกว่า อายุการใช้งานที่เป็นประโยชน์ ทำให้ตัวเก็บขยะไม่สามารถเรียกคืนหน่วยความจำของออบเจ็กต์ที่รั่วได้ ใช้การออกแบบที่รับรู้ถึงวงจรขององค์ประกอบเพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำ
ดูข้อมูลเพิ่มเติมได้ที่หน่วยความจำรั่ว
หลีกเลี่ยงการเสียหน่วยความจำ
เหตุการณ์ระบบจัดการหน่วยความจำที่ไม่ใช้แล้วจะไม่ส่งผลต่อประสิทธิภาพของแอป อย่างไรก็ตาม เหตุการณ์ระบบจัดการหน่วยความจำที่ไม่ใช้แล้วหลายรายการที่เกิดขึ้นในช่วงเวลาสั้นๆ อาจทำให้แบตเตอรี่หมดเร็วขึ้น รวมถึงเพิ่มเวลาในการตั้งค่าเฟรมเล็กน้อยเนื่องจากการโต้ตอบที่จำเป็นระหว่างระบบจัดการหน่วยความจำที่ไม่ใช้แล้วกับเทรดของแอป ยิ่งระบบใช้เวลาในการเก็บขยะนานเท่าใด แบตเตอรี่ก็จะหมดเร็วขึ้นเท่านั้น
โดยปกติแล้ว การเสียหน่วยความจำ อาจทำให้เกิดเหตุการณ์ระบบจัดการหน่วยความจำที่ไม่ใช้แล้วจำนวนมาก ในทางปฏิบัติแล้ว การเปลี่ยนแปลงของหน่วยความจำจะอธิบายจำนวนออบเจ็กต์ชั่วคราวที่จัดสรร ซึ่งเกิดขึ้นในช่วงเวลาที่กำหนด
เช่น คุณอาจจัดสรรออบเจ็กต์ชั่วคราวหลายรายการภายในลูป for
หรืออาจสร้างออบเจ็กต์ Paint หรือ
Bitmap ใหม่ภายในฟังก์ชัน
onDraw()
ของมุมมอง ในทั้ง 2 กรณี แอปจะสร้างออบเจ็กต์จำนวนมากอย่างรวดเร็วในปริมาณสูง ซึ่งอาจใช้หน่วยความจำทั้งหมดที่มีในรุ่นใหม่ได้อย่างรวดเร็ว ทำให้เกิดเหตุการณ์ระบบจัดการหน่วยความจำที่ไม่ใช้แล้ว
ใช้ Memory Profiler เพื่อค้นหา ตำแหน่งในโค้ดที่มีการใช้หน่วยความจำสูงก่อนที่จะแก้ไขได้
หลังจากระบุส่วนที่มีปัญหาในโค้ดแล้ว ให้พยายามลดจำนวนการจัดสรรภายในส่วนที่สำคัญต่อประสิทธิภาพ ลองย้ายสิ่งต่างๆ ออกจาก ลูปด้านใน หรืออาจย้ายไปไว้ในโครงสร้างการจัดสรรตามโรงงาน
นอกจากนี้ คุณยังประเมินได้ด้วยว่ากลุ่มออบเจ็กต์เป็นประโยชน์ต่อกรณีการใช้งานหรือไม่ เมื่อใช้ Object Pool คุณจะปล่อยอินสแตนซ์ออบเจ็กต์ลงในพูลแทนที่จะวางไว้บนพื้นหลังจากที่ไม่จำเป็นต้องใช้อีกต่อไป ในครั้งถัดไปที่ต้องการอินสแตนซ์ออบเจ็กต์ประเภทนั้น คุณสามารถรับจากพูลได้แทนที่จะจัดสรร
ประเมินประสิทธิภาพอย่างละเอียดเพื่อพิจารณาว่าพูลออบเจ็กต์เหมาะกับสถานการณ์ที่กำหนดหรือไม่ มีบางกรณีที่พูลออบเจ็กต์อาจทำให้ประสิทธิภาพแย่ลง แม้ว่าพูลจะหลีกเลี่ยงการจัดสรร แต่ก็ทำให้เกิดค่าใช้จ่ายอื่นๆ ตัวอย่างเช่น การดูแลรักษาพูลมักเกี่ยวข้องกับการซิงค์ ซึ่งมีค่าใช้จ่ายที่ไม่ควรมองข้าม นอกจากนี้ การล้างอินสแตนซ์ออบเจ็กต์ที่รวมไว้เพื่อหลีกเลี่ยง หน่วยความจำรั่วระหว่างการเผยแพร่ และการเริ่มต้นระหว่างการได้มาอาจมีค่าใช้จ่ายที่ไม่ใช่ 0
การเก็บอินสแตนซ์ออบเจ็กต์ในพูลไว้มากกว่าที่จำเป็นยังเป็นภาระต่อระบบจัดการหน่วยความจำที่ไม่ใช้แล้วด้วย แม้ว่าพูลออบเจ็กต์จะลดจำนวนการเรียกใช้การเก็บขยะ แต่ก็เพิ่มปริมาณงานที่จำเป็นสำหรับการเรียกใช้ทุกครั้ง เนื่องจากเป็นสัดส่วนกับจำนวนไบต์ที่ใช้งานอยู่ (เข้าถึงได้)