วิธีการ

การให้ความสำคัญกับประสิทธิภาพการใช้หน่วยความจำ: ขั้นตอนสำคัญสำหรับ Android 17

อ่าน 10 นาที
3 ผู้เขียน
Alice Yuan, Ajesh Pai, Fung Lam

แม้ว่าประสิทธิภาพของแอปมักจะเทียบเท่ากับ UI ที่ราบรื่นและเวลาเริ่มต้นที่รวดเร็ว แต่หน่วยความจำก็เป็นรากฐานที่เงียบซึ่งเมตริกที่มองเห็นได้เหล่านี้สร้างขึ้น เราเห็นการเปลี่ยนแปลงที่หน่วยความจำของอุปกรณ์มีความสำคัญมากกว่าที่เคย เราไม่เพียงแต่ก้าวหน้าในการเพิ่มประสิทธิภาพหน่วยความจำของ Android ด้วย Android 17 แต่ยังให้การสนับสนุนเครื่องมือและ API เพื่อช่วยให้คุณพร้อมรับมือกับข้อกำหนดด้านหน่วยความจำที่เข้มงวดมากขึ้นในช่วงปลายปีนี้

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

นอกเหนือจากการสิ้นสุดการทำงานโดยบังคับเหล่านี้แล้ว การใช้งานหน่วยความจำที่ไม่ได้เพิ่มประสิทธิภาพจะทำให้ประสบการณ์ของผู้ใช้แย่ลงอย่างหลีกเลี่ยงไม่ได้ เมื่อแอปใกล้ถึงขีดจำกัดของหน่วยความจำฮีป ระบบจะเรียกใช้ระบบจัดการหน่วยความจำที่ไม่ใช้แล้วบ่อยครั้ง ซึ่งทำให้ UI สะดุดอย่างเห็นได้ชัด นอกจากนี้ เมื่ออุปกรณ์มีหน่วยความจำไม่เพียงพอ ระบบจะพยายามกู้คืนหน้าเว็บ ซึ่งทำให้ CPU ทำงานหนักเกินไป, UI ทำงานช้า และแบตเตอรี่หมดเร็ว หากหน่วยความจำไม่เพียงพออย่างรุนแรง อาจทำให้เกิดเหตุการณ์ Low Memory Killer (LMK) ซึ่งจะสิ้นสุดกระบวนการทำงานเบื้องหลังอย่างกะทันหัน และบังคับให้แอปมี Cold Start ที่ช้าและสูญเสียสถานะของผู้ใช้

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

  1. เพิ่มประสิทธิภาพไบต์โค้ดให้สูงสุดด้วย R8
  2. เพิ่มประสิทธิภาพการโหลดรูปภาพ
  3. ตรวจหาและแก้ไขหน่วยความจำรั่วด้วย Android Studio
  4. ตัดหน่วยความจำเมื่อแอปออกจากสถานะที่มองเห็นได้
  5. การสังเกตการณ์หน่วยความจำขั้นสูงด้วย ProfilingManager

นอกจากนี้ เรายังมีบล็อกโพสต์ฉบับย่อในรูปแบบวิดีโอด้วย อย่าลืมไปดูกันนะ

ทำความเข้าใจการจำกัดการใช้หน่วยความจำของแอปใน Android 17

Android 17 มีการจำกัดหน่วยความจำของแอปเพื่อป้องกันไม่ให้ "ผู้ไม่ประสงค์ดีเพียงคนเดียว" ทำลายประสบการณ์การทำงานแบบมัลติทาสก์และความเสถียรของอุปกรณ์ทั้งหมดของผู้ใช้

รายละเอียดเหตุผลที่ทำให้เกิดการเปลี่ยนแปลงสถาปัตยกรรมนี้มีดังนี้

  • ป้องกันการปิดแอปแบบต่อเนื่อง: เมื่อแอปมีขนาดใหญ่เกินไปหรือหน่วยความจำรั่วไหลขณะอยู่ในสถานะที่มีสิทธิ์ (เช่น กำลังเรียกใช้บริการที่ทำงานอยู่เบื้องหน้า) ระบบจะป้องกันไม่ให้ Low Memory Killer (LMK) ของระบบปิดแอปในตอนแรก เมื่อแอปเดียวนี้เติบโตขึ้นโดยไม่มีการตรวจสอบและกักตุน RAM ไว้ LMK จึงต้องชดเชยด้วยการปิดแอปที่แคชไว้และงานที่ทำงานอยู่เบื้องหลังขนาดเล็กอื่นๆ อีกหลายสิบรายการที่ทำงานได้ดีเพื่อเรียกคืนพื้นที่สำหรับแอปที่ใช้หน่วยความจำมาก
     
  • การรักษาสถานะมัลติทาสก์และสถานะของผู้ใช้: เมื่อระบบถูกบังคับให้ล้างแอปที่แคชไว้เพื่อรองรับกระบวนการเดียวที่รั่วไหล ประสบการณ์มัลติทาสก์จะลดลงอย่างมาก ผู้ใช้ที่กลับมาใช้แอปพลิเคชันที่แคชไว้ก่อนหน้านี้จะพบว่า Cold Start ทำงานช้าลงแทนที่จะเป็นการกลับมาทำงานต่อแบบ Warm Start ที่รวดเร็ว ความไม่มีประสิทธิภาพนี้ทำให้ CPU ทำงานหนักขึ้นและแบตเตอรี่หมดเร็วขึ้น นอกจากนี้ยังอาจทำลายบริบทของผู้ใช้ในแอปที่ใช้ล่าสุด เช่น ตำแหน่งการเลื่อน สแต็กการนำทาง และความคืบหน้าในเกม

หากต้องการดูว่าเซสชันแอปได้รับผลกระทบจากข้อจำกัดเหล่านี้ในฟิลด์หรือไม่ คุณสามารถเรียก getDescription() ภายใน ApplicationExitInfo หากระบบใช้ขีดจำกัด ระบบจะรายงานเหตุผลที่ออกเป็น REASON_OTHER และสตริงคำอธิบายจะมี "MemoryLimiter:AnonSwap" นอกจากนี้ คุณยังใช้ประโยชน์จากการสร้างโปรไฟล์ตามทริกเกอร์โดยใช้ TRIGGER_TYPE_ANOMALY เพื่อบันทึกฮีปดัมป์โดยอัตโนมัติเมื่อถึงขีดจำกัดหน่วยความจำได้ด้วย นอกจากนี้ Android ยังทำงานอย่างต่อเนื่องเพื่อแสดงเมตริกหน่วยความจำในฟิลด์เพิ่มเติมแก่นักพัฒนาแอปภายใน Google Play Console

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

เพิ่มประสิทธิภาพไบต์โค้ดให้สูงสุดด้วย R8

วิธีที่มีประสิทธิภาพสูงในการลดหน่วยความจำที่ใช้ของแอปคือการเปิดใช้เครื่องมือเพิ่มประสิทธิภาพ R8 การย่อคลาส เมธอด และฟิลด์ให้มีชื่อสั้นลง รวมถึงการนำโค้ดและทรัพยากรที่ไม่ได้ใช้ออกจะช่วยให้ R8 ลดการใช้หน่วยความจำของแอปได้อย่างมากด้วยการลดจำนวนโค้ดที่ต้องใช้ในระหว่างการดำเนินการ 

R8 จะลดโค้ดที่อยู่ในหน่วยความจำ ซึ่งจะช่วยลดการใช้หน่วยความจำและลดความเสี่ยงในการสิ้นสุด LMK ซึ่งจะส่งผลให้เกิด Warm Start บ่อยขึ้นแทนที่จะเป็น Cold Start ช้า นอกจากนี้ ไบต์โค้ดที่ได้รับการเพิ่มประสิทธิภาพยังช่วยลดค่าใช้จ่ายของ CPU ในเทรดหลัก ซึ่งจะช่วยลดอัตรา ANR โดยตรงเพื่อให้ผู้ใช้ได้รับประสบการณ์ที่ราบรื่นยิ่งขึ้น ตัวอย่างเช่น ธนาคารดิจิทัล Monzo เปิดใช้การเพิ่มประสิทธิภาพ R8 แบบเต็มและพบว่าอัตรา ANR ลดลง 35%, อัตรา Cold Start ดีขึ้น 30% และขนาดแอปโดยรวมลดลง 9%

pic1-IO26_113_TSV-monzo-casestudy.jpg
ธนาคารดิจิทัล Monzo เปิดใช้การเพิ่มประสิทธิภาพ R8 แบบเต็มและเพิ่มเมตริกประสิทธิภาพได้สูงสุด 35%

วิธีกำหนดค่า R8 ในไฟล์ build.gradle อย่างถูกต้อง

  • ตั้งค่า isShrinkResources = true และ isMinifyEnabled = true
  • ใช้ proguard-android-optimize.txt แทน proguard-android.txt แบบเดิม ซึ่งจะป้องกันการเพิ่มประสิทธิภาพและไม่รองรับในปลั๊กอิน Android Gradle 9 อีกต่อไป
  • นำ android.enableR8.fullMode = false ออกจาก gradle.properties

หากคุณใช้การสะท้อนในฐานของโค้ด ให้เพิ่มกฎ Keep เพื่อป้องกันไม่ให้ R8 เพิ่มประสิทธิภาพโค้ดส่วนดังกล่าว อย่าลืมกำหนดขอบเขตของกฎการเก็บรักษาให้แคบลงเพื่อเพิ่มประสิทธิภาพสูงสุด 

หากต้องการเพิ่มประสิทธิภาพสูงสุด โปรดทําตามแนวทางปฏิบัติแนะนําต่อไปนี้ในไฟล์กฎการเก็บ

  • นำตัวเลือกส่วนกลาง เช่น -dontoptimize, -dontshrink และ -dontobfuscate ที่ป้องกันไม่ให้ R8 เพิ่มประสิทธิภาพฐานของโค้ดทั้งหมดออก
  • นำกฎการเก็บที่ป้องกันการเพิ่มประสิทธิภาพคอมโพเนนต์ Android เช่น กิจกรรม บริการ มุมมอง หรือตัวรับสัญญาณออกอากาศออก
  • ปรับแต่งกฎการเก็บรักษาแบบกว้างของทั้งแพ็กเกจเพื่อกำหนดเป้าหมายเฉพาะคลาสหรือเมธอดที่ต้องการ 

ดูแนวทางปฏิบัติแนะนำเพิ่มเติมได้ในเอกสารประกอบเกี่ยวกับกฎการเก็บ

แนวทางปฏิบัติแนะนำสำหรับนักพัฒนาแอปไลบรารี R8

หากคุณเป็นนักพัฒนาไลบรารี ให้วางกฎที่ผู้บริโภคต้องการไว้ใน consumer-rules file อย่างเคร่งครัด และเก็บกฎการป้องกันภายในของไลบรารีไว้ในไฟล์ proguard-rules.pro ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีเพิ่มประสิทธิภาพไลบรารีได้ที่การเพิ่มประสิทธิภาพสำหรับผู้เขียนไลบรารี

ตัววิเคราะห์การกำหนดค่า R8

หากต้องการตรวจสอบการเพิ่มประสิทธิภาพ R8 ให้ใช้เครื่องมือวิเคราะห์การกำหนดค่า เครื่องมือวิเคราะห์การกำหนดค่าจะแสดงสถานะปัจจุบันของการเพิ่มประสิทธิภาพพร้อม คะแนนการปรับให้ยากต่อการอ่าน การเพิ่มประสิทธิภาพ และการลดขนาด นอกจากนี้ เครื่องมือวิเคราะห์การกำหนดค่ายังช่วยให้คุณทราบจำนวนคลาส เมธอด หรือฟิลด์ที่กฎ Keep แต่ละรายการป้องกันไม่ให้มีการเพิ่มประสิทธิภาพได้ด้วย ปรับแต่งกฎการเก็บรักษาแบบกว้างระดับแพ็กเกจเหล่านี้เพื่อปลดล็อกการเพิ่มประสิทธิภาพสูงสุด 

การใช้เครื่องมือวิเคราะห์การกำหนดค่าจะช่วยให้คุณระบุกฎการเก็บที่ครอบคลุมกฎการเก็บอื่นๆ กฎการเก็บที่ซ้ำซ้อน และกฎการเก็บที่ไม่ได้ใช้ได้ด้วย

pic2-r8-config-analyzer.png
เครื่องมือวิเคราะห์การกำหนดค่าจะแสดงสถานะปัจจุบันของการเพิ่มประสิทธิภาพด้วยคะแนนการปกปิด การเพิ่มประสิทธิภาพ และการลดขนาด

ทักษะของตัวแทน R8 

นอกจากนี้ คุณยังใช้ประโยชน์จากทักษะของ R8 Agent กับเอเจนต์ Android Studio หรือเครื่องมือ AI อื่นๆ เพื่อแก้ไขการกำหนดค่าที่ไม่ถูกต้องและปรับแต่งกฎเพื่อปรับปรุงประสิทธิภาพของแอปได้ด้วย (ข้อมูลเชิงลึกจากทักษะที่ทำงานด้วยระบบ AI จะต้องมีการยืนยันทางเทคนิค)

เพิ่มประสิทธิภาพการโหลดรูปภาพ

โดยปกติแล้ว บิตแมปจะเป็นออบเจ็กต์ทั่วไปที่ใหญ่ที่สุดซึ่งอยู่ในหน่วยความจำของแอป ซึ่งแสดงถึงขั้นตอนสุดท้ายของกระบวนการโหลดรูปภาพที่ไฟล์ที่บีบอัด เช่น JPEG หรือ PNG จะได้รับการถอดรหัสเป็นข้อมูลพิกเซลดิบเพื่อแสดง ซึ่งหมายความว่ารูปภาพที่บีบอัดขนาด 100 KB อาจขยายเป็น RAM หลายเมกะไบต์ได้ เนื่องจากระบบจะกำหนดการใช้หน่วยความจำตามขนาดพิกเซลและความลึกของสีของรูปภาพ เนื่องจากการดำเนินการบิตแมปมักอยู่ในเส้นทางวิกฤตในการวาดเฟรม รูปภาพที่ไม่ได้เพิ่มประสิทธิภาพจึงทำให้หน่วยความจำบวมอย่างรุนแรงและ UI กระตุก

Google ขอแนะนำให้ใช้ประโยชน์จากไลบรารีการโหลดรูปภาพ Coil สำหรับโปรเจ็กต์ที่ใช้ Kotlin เป็นหลัก โดยเฉพาะอย่างยิ่งเมื่อพัฒนาด้วย Jetpack Compose และ Glide สำหรับแอปพลิเคชันที่ใช้ Java

ใช้แนวทางปฏิบัติแนะนำ 5 ข้อต่อไปนี้

  1. ลดขนาดความละเอียดของรูปภาพ: หากโหลดบิตแมปด้วยตนเอง ให้หลีกเลี่ยงการโหลดรูปภาพขนาดใหญ่ลงในมุมมองภาพขนาดย่อขนาดเล็ก ใช้ inSampleSize เพื่อโหลดเวอร์ชันที่เล็กกว่า Glide และ Coil จะลดขนาดรูปภาพโดยค่าเริ่มต้น และคุณสามารถกำหนดค่ากลยุทธ์การลดขนาดนี้ได้โดยใช้ DownsampleStrategy และ ImageLoader ตามลำดับ
  2. การครอบตัด: หลีกเลี่ยงการฝังระยะขอบลงในไฟล์รูปภาพโดยตรงเพื่อวัตถุประสงค์ในการสร้างแถบดำบนและล่าง (เช่น การสร้างเส้นขอบโปร่งใสเพื่อขยายขนาดรูปภาพ) แทนที่จะฝังขอบเหล่านี้ ให้ใช้ InsetDrawable หรือใช้ระยะห่างจากขอบโดยตรงภายใน View หรือ Composable ที่มีบิตแมป
  3. กำหนดค่า: ปรับสมดุลหน่วยความจำและคุณภาพโดยเลือกรูปแบบพิกเซลที่เหมาะสม ใช้ RGB_565 เมื่อไม่จำเป็นต้องใช้ความโปร่งใส ซึ่งใช้หน่วยความจำเพียงครึ่งหนึ่งของรูปแบบ ARGB_8888 เริ่มต้น ใน Glide คุณสามารถกำหนดค่านี้ได้โดยใช้พร็อพเพอร์ตี้ DecodeFormat และใน Coil คุณสามารถใช้พร็อพเพอร์ตี้ bitmapConfig ได้
  4. จัดลำดับความสำคัญของ Vector Drawable: สำหรับชิ้นงานเรขาคณิตพื้นฐาน ให้ใช้ ShapeDrawable เป็นทางเลือกที่มีขนาดเล็กแทนการถอดรหัสบิตแมปแบบแรสเตอร์ การกำหนดชิ้นงานเหล่านี้เพียงครั้งเดียวผ่าน XML จะช่วยให้มั่นใจได้ว่าชิ้นงานจะปรับขนาดได้อย่างราบรื่นในความหนาแน่นของจอแสดงผลทั้งหมด พร้อมทั้งช่วยลดการใช้หน่วยความจำที่เกิดจากทรัพยากรได้อย่างมีประสิทธิภาพ
  5. การนำกลับมาใช้ใหม่: หากแอปพลิเคชันจัดการบิตแมปด้วยตนเอง เมื่อไม่จำเป็นต้องใช้บิตแมปแล้ว แอปควรเรียกใช้ bitmap.recycle() และทิ้งการอ้างอิง Bitmap ทันทีเพื่อลดการเสียหน่วยความจำ หากใช้ไลบรารีการโหลดรูปภาพ เช่น Glide หรือ Coil ให้ส่งคืนบิตแมปไปยังพูลที่มีการจัดการของไลบรารี การจัดสรรบัฟเฟอร์ที่มีอยู่สำหรับความต้องการหน่วยความจำในอนาคตจะช่วยให้พูลหลีกเลี่ยงค่าใช้จ่ายในการจัดสรรใหม่ได้อย่างมีประสิทธิภาพ

ดูข้อมูลเพิ่มเติมได้ในเอกสารประกอบเกี่ยวกับการเพิ่มประสิทธิภาพรูปภาพ

เครื่องมือ Android Studio

นอกจากนี้ คุณยังกำจัดบิตแมปที่ซ้ำกันได้โดยใช้ Android Studio Narwhal 4 วิธีค้นหาไฟล์ที่ซ่อนมีดังนี้

  1. เปิดแท็บ Profiler ใน Android Studio
  2. คลิก Heap Dump (หรือ "วิเคราะห์การใช้หน่วยความจำ") แล้วกดบันทึกเพื่อถ่ายภาพรวมสถานะหน่วยความจำปัจจุบันของแอป
  3. สแกนผลการวิเคราะห์เพื่อหาสามเหลี่ยมคำเตือนสีเหลือง ⚠️ ซึ่ง Android Studio ใช้เพื่อแจ้งว่ามีการจัดเก็บบิตแมปที่ซ้ำกันหลายครั้ง หรือไปที่ส่วนหัวของโปรไฟล์เลอร์ เลือก "กรองตาม:" แล้วเลือกการตั้งค่า "บิตแมปที่ซ้ำกัน"
  4. คลิกรายการที่ถูกแจ้งเพื่อเปิดแผงตัวอย่างบิตแมป ซึ่งจะช่วยให้คุณเห็นภาพที่ทำผิดซ้ำได้อย่างชัดเจน
  5. ใช้การยืนยันด้วยภาพดังกล่าวเพื่อติดตามตรรกะการโหลดที่ซ้ำซ้อนในโค้ด และใช้กลยุทธ์การแคชที่ดีขึ้น
pic3-IO26_113_TSV -dup-bitmaps-cropped.jpg
มองหาสามเหลี่ยมประกาศเตือนสีเหลือง ⚠️ ในฮีปดัมป์เมื่อใช้ Android Studio เครื่องมือสร้างโปรไฟล์

ตรวจหาและแก้ไขหน่วยความจำรั่วด้วย Android Studio

หน่วยความจำรั่วใน Android เกิดขึ้นเมื่อโค้ดเก็บการอ้างอิงของออบเจ็กต์ไว้นานหลังจากวงจรของออบเจ็กต์สิ้นสุดลง ซึ่งจะป้องกันไม่ให้ตัวเก็บขยะ (GC) เรียกคืนหน่วยความจำดังกล่าว ซึ่งในที่สุดจะทำให้ประสิทธิภาพการทำงานช้าลงหรือเกิดข้อผิดพลาด OutOfMemoryError (OOM)

Android Studio Panda 3 มีงานโปรไฟล์ LeakCanary โดยเฉพาะ ซึ่งช่วยให้นักพัฒนาแอปวิเคราะห์หน่วยความจำรั่วแบบเรียลไทม์และแมปการติดตามได้โดยตรงภายใน IDE

งาน Profiler ของ LeakCanary ใน Android Studio จะย้ายการวิเคราะห์หน่วยความจำรั่วไหลจากอุปกรณ์ไปยังคอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์อย่างต่อเนื่อง ซึ่งจะช่วยเพิ่มประสิทธิภาพอย่างมากในระยะการวิเคราะห์การรั่วไหลเมื่อเทียบกับการวิเคราะห์การรั่วไหลในอุปกรณ์

pic4-android-studio-leaks.png
 การวิเคราะห์หน่วยความจำรั่วไหลของ LeakCanary ที่เชื่อมโยงกับไปที่การประกาศเพื่อการแก้ไขข้อบกพร่อง

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

ตัวอย่างการรั่วไหลของหน่วยความจำที่พบบ่อย 

หน่วยความจำรั่วเกิดขึ้นเมื่อออบเจ็กต์ยังคงอยู่ในหน่วยความจำนานเกินกว่าอายุการใช้งานที่ตั้งใจไว้ โดยมักมีสาเหตุต่อไปนี้

  • เก็บการอ้างอิงถึง Fragment, Activity หรือ View ที่ไม่ได้ใช้งานแล้ว
  • การจัดการการอ้างอิงบริบทอย่างไม่เหมาะสม
  • การยกเลิกการลงทะเบียน Observer, Listener และ Receiver ไม่ถูกต้อง
  • การสร้างการอ้างอิงแบบคงที่ไปยังออบเจ็กต์ที่เชื่อมโยงกับคอมโพเนนต์ที่มีวงจรที่สั้นกว่า

ลองดูสถานการณ์ตัวอย่างต่อไปนี้

สถานการณ์ตัวอย่างที่อิงตาม Compose ตัวอย่างที่อิงตามการดู
การรั่วไหลของบริบท

ตัวอย่าง:
ส่ง LocalContext.current ไปยัง ViewModel

แก้ไข:
เก็บตรรกะที่ขึ้นอยู่กับบริบทไว้ในเลเยอร์ UI สำหรับเลเยอร์ที่ไม่ใช่ UI ให้ปรับโครงสร้างเพื่อใช้การแทรกทรัพยากร Dependency หรือสังเกตสถานะ UI โดยใช้ Kotlin Flow

ตัวอย่าง: 
การจัดเก็บ Activity ในออบเจ็กต์คู่หรือตัวแปรแบบคงที่

แก้ไข:
อย่าเก็บการอ้างอิงแบบคงที่ไปยังคอมโพเนนต์ UI ปรับโครงสร้างใหม่เพื่อใช้การแทรกทรัพยากร Dependencyหรือสังเกตสถานะ UI โดยใช้ Kotlin Flow

การรั่วไหลของ Listener

ตัวอย่าง:
ใช้ DisposableEffect เพื่อเริ่ม Listener แต่ปล่อยให้ onDispose ว่าง

แก้ไข:
ตรรกะการยกเลิกการลงทะเบียนและการล้างข้อมูลภายในบล็อก onDispose

ตัวอย่าง:
ลงทะเบียนเพื่อรับการอัปเดต SensorManager แล้วลืมยกเลิกการลงทะเบียน

แก้ไข:
เรียกใช้ unregisterListener() ในวงจร onStop() หรือ onDestroy() ด้วยตนเอง

การรั่วไหลของยอดดู

ตัวอย่าง:
การอ้างอิงถึงViewเดิมภายในAndroidViewโดยไม่มีกลยุทธ์การเปิดตัว


แก้ไข:
ใช้บล็อก release ของ AndroidView ที่ใช้ร่วมกันได้เพื่อล้างข้อมูล View เดิม

ตัวอย่าง:
การเก็บการอ้างอิงไปยังออบเจ็กต์การเชื่อมโยงมุมมองหลังจากที่ทำลาย Fragment แล้ว

 

แก้ไข:
ตั้งค่าตัวแปรการเชื่อมโยงเป็น null ภายในเมธอดวงจร onDestroyView()

ตัดหน่วยความจำเมื่อแอปออกจากสถานะที่มองเห็นได้

Android สามารถเรียกคืนหน่วยความจำจากแอปหรือหยุดแอปทั้งหมดได้หากจำเป็นเพื่อเพิ่มหน่วยความจำสำหรับงานที่สำคัญ ดังที่อธิบายไว้ในภาพรวมของการจัดการหน่วยความจำ โดยปกติแล้ว Android จะเรียกคืนหน่วยความจำจากแอปของคุณเมื่อผู้ใช้มองไม่เห็น เช่น โดยการทิ้งโค้ดและหน้าข้อมูลบางส่วนของแอปในหน่วยความจำ หรือบีบอัดการจัดสรรฮีป เมื่อผู้ใช้กลับมาใช้แอปของคุณอีกครั้งและแอปพยายามเข้าถึงหน่วยความจำบางส่วนที่ระบบเรียกคืนไปแล้ว ระบบปฏิบัติการจะสลับหน่วยความจำนั้นกลับมาตามคำขอ การสลับลักษณะการทำงานนี้อาจช้า และทำให้เกิดอาการกระตุกหรือหยุดชะงักที่ไม่คาดคิดในแอป

หากปล่อยให้ระบบปฏิบัติการตัดสินใจว่าจะเรียกคืนหน่วยความจำใดจากแอป คุณอาจพบว่าระบบปฏิบัติการเรียกคืนหน่วยความจำที่คุณจะต้องใช้ในไม่ช้าหลังจากกลับมาใช้แอปอีกครั้ง ในทางกลับกัน แอปสามารถทิ้งการจัดสรรหน่วยความจำที่สามารถสร้างใหม่ได้ในภายหลังตามต้องการและในราคาต่ำ โดยคุณสามารถใช้อินเทอร์เฟซ ComponentCallbacks2 เพื่อดำเนินการดังกล่าวได้ คุณสามารถใช้ onTrimMemory ในคลาส Activity, Fragment, Service หรือแม้แต่คลาส Application ที่กำหนดเอง การใช้ในApplicationคลาสมีประสิทธิภาพสูงสำหรับการจัดการแคชทั่วโลก

เมธอด Callback onTrimMemory() ที่ระบุจะแจ้งให้แอปทราบถึงเหตุการณ์ที่เกี่ยวข้องกับวงจรหรือหน่วยความจำ ซึ่งเป็นโอกาสที่ดีที่แอปจะลดการใช้งานหน่วยความจำโดยสมัครใจ

ในแง่ของการจัดการวงจรหน่วยความจำ การติดตั้งใช้งานควรเน้นที่ TRIM_MEMORY_UI_HIDDEN และ TRIM_MEMORY_BACKGROUND โดยเฉพาะ ตั้งแต่ Android 14 เป็นต้นมา ระบบได้หยุดส่งการแจ้งเตือนสำหรับค่าคงที่เดิมอื่นๆ ซึ่งเลิกใช้งานอย่างเป็นทางการใน Android 15

TRIM_MEMORY_UI_HIDDEN: สัญญาณนี้บ่งชี้ว่า UI ของแอปพลิเคชันได้เปลี่ยนออกจากมุมมองของผู้ใช้แล้ว ซึ่งจะช่วยให้คุณมีโอกาสที่จะเผยแพร่การจัดสรรหน่วยความจำจำนวนมากที่เชื่อมโยงกับอินเทอร์เฟซอย่างเคร่งครัด เช่น บิตแมป บัฟเฟอร์การเล่นวิดีโอ หรือทรัพยากรภาพเคลื่อนไหวที่ซับซ้อน

TRIM_MEMORY_BACKGROUND: ที่ระดับนี้ กระบวนการของคุณจะอยู่ในเบื้องหลังและตอนนี้เป็นกระบวนการที่อาจถูกสิ้นสุดเพื่อตอบสนองความต้องการหน่วยความจำส่วนกลางของระบบ หากต้องการขยายระยะเวลาที่กระบวนการยังคงอยู่ในสถานะแคช และลดจำนวน Cold Start ของแอป คุณควรปล่อยทรัพยากรทั้งหมดที่สร้างขึ้นใหม่ได้ง่ายเมื่อผู้ใช้กลับมาใช้เซสชันต่อ

import android.content.ComponentCallbacks2
// Other import statements.

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {

    /**
     * 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.
        }
    }
}

หมายเหตุ: onTrimMemory การผสานรวมอาจขึ้นอยู่กับการรองรับ SDK เช่น เกมบางเกมจะใช้เกมเอนจินเพื่อเปิดใช้ความสามารถนี้ โปรดดูเอกสารการเพิ่มประสิทธิภาพหน่วยความจำของเกม

การสังเกตการณ์หน่วยความจำขั้นสูงด้วย ProfilingManager

หากต้องการตรวจหาและวินิจฉัยปัญหาเกี่ยวกับหน่วยความจำในฟิลด์ที่จำลองในเครื่องไม่ได้ คุณควรใช้ประโยชน์จาก ProfilingManager API API ความสามารถในการสังเกตขั้นสูงนี้เปิดตัวใน Android 15 และช่วยให้คุณรวบรวมโปรไฟล์ Perfetto ของผู้ใช้จริงได้แบบเป็นโปรแกรม

สำหรับทีมที่ไม่มีโครงสร้างพื้นฐานเฉพาะเพื่อจัดการและโฮสต์อาร์ติแฟกต์ประสิทธิภาพ Crashlytics กำลังพิจารณาโซลูชันเฉพาะทางเพื่อเพิ่มประสิทธิภาพเวิร์กโฟลว์นี้ โดยขอเชิญนักพัฒนาแอปแสดงความคิดเห็น

Android 17 เปิดตัวทริกเกอร์ที่ขับเคลื่อนด้วยเหตุการณ์ใหม่ โดยเฉพาะอย่างยิ่ง TRIGGER_TYPE_OOM และ TRIGGER_TYPE_ANOMALY

  • ทริกเกอร์ OOM จะรวบรวมฮีปดัมป์ของ Java โดยอัตโนมัติในขณะที่เกิดข้อขัดข้อง OutOfMemoryError ซึ่งจะให้สถานะการจัดสรรที่แม่นยำ ระบบจะระบุโปรไฟล์ OOM ที่รวบรวมไว้ในครั้งถัดไปที่แอปเริ่มต้นและลงทะเบียนการเรียกกลับ registerForAllProfilingResults
  • ทริกเกอร์ความผิดปกติจะตรวจหาปัญหาด้านประสิทธิภาพที่ร้ายแรง เช่น สแปม Binder มากเกินไปหรือการละเมิดเกณฑ์หน่วยความจำ ความผิดปกติของหน่วยความจำจะส่งการทิ้งหน่วยความจำฮีปก่อนที่ระบบจะสิ้นสุดแอป
    val profilingManager = 
applicationContext.getSystemService(ProfilingManager::class.java)
    val triggers = ArrayList<ProfilingTrigger>()  


    triggers.add(ProfilingTrigger.Builder(
                 ProfilingTrigger.TRIGGER_TYPE_ANOMALY))
    val mainExecutor: Executor = Executors.newSingleThreadExecutor()
    val resultCallback = Consumer<ProfilingResult> { profilingResult ->
        if (profilingResult.errorCode != ProfilingResult.ERROR_NONE) {
            // upload profile result to server for further analysis          
            setupProfileUploadWorker(profilingResult.resultFilePath)
        } 

    profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

เมื่อรวบรวมฮีปดัมป์แล้ว คุณจะดาวน์โหลดโปรไฟล์จากเซิร์ฟเวอร์หรือในเครื่องผ่าน adb pull และลากและวางไฟล์ลงใน UI ของ Perfetto ได้ หากต้องการเพิ่มประสิทธิภาพเวิร์กโฟลว์การแก้ไขข้อบกพร่องของหน่วยความจำ ให้ใช้เครื่องมือสำรวจฮีปดัมป์ ซึ่งเป็นมุมมองเริ่มต้นใหม่สำหรับฮีปดัมป์ใน UI ของ Perfetto เครื่องมือนี้มีอินเทอร์เฟซที่ใช้งานง่ายสำหรับการตรวจสอบฮีปดัมป์ของ Java ซึ่งช่วยให้คุณเห็นภาพลำดับชั้นการจัดสรรออบเจ็กต์ คำนวณขนาดหน่วยความจำที่เก็บไว้ และระบุเส้นทางที่สั้นที่สุดจากรูทของระบบจัดการหน่วยความจำที่ไม่ใช้แล้ว การใช้ประโยชน์จาก Heap Dump Explorer จะช่วยให้คุณระบุหน่วยความจำรั่วไหล ออบเจ็กต์ที่เก็บไว้ซึ่งมีขนาดใหญ่ เช่น การจัดสรรบิตแมปมากเกินไป และวิเคราะห์การจัดสรรออบเจ็กต์ฮีปได้ทั้งหมดในที่เดียวอย่างรวดเร็ว

pic5-perfettoheapdump-analyzer.png
ใช้ Flamegraph แบบฝังของ Heap Dump Explorer เพื่อตรวจสอบและไปยังออบเจ็กต์ที่มีการจัดสรรฮีปสูงสุด

บทสรุป

การเพิ่มประสิทธิภาพไบต์โค้ดด้วย R8 การนำแนวทางปฏิบัติแนะนำในการโหลดรูปภาพมาใช้ และการแก้ไขการรั่วไหลของหน่วยความจำเป็นขั้นตอนสำคัญในการมอบประสบการณ์ของผู้ใช้คุณภาพสูงในขณะที่จัดการทรัพยากรอย่างมีประสิทธิภาพภายใต้แรงกดดัน การใช้มาตรการเชิงรุกเหล่านี้จะช่วยรักษาความเสถียรและประสิทธิภาพของแอป ป้องกันการสิ้นสุดการทำงานโดยไม่คาดคิด และปกป้องบริบทของผู้ใช้ ดูคำแนะนำเกี่ยวกับหน่วยความจำที่ปรับปรุงแล้วเพื่อเพิ่มความเชี่ยวชาญด้านประสิทธิภาพ

เขียนโดย
อ่านต่อ