การแสดงผล UI คือการสร้างเฟรมจากแอปและแสดงเฟรมนั้นๆ หน้าจอ เพื่อให้มั่นใจว่าการโต้ตอบของผู้ใช้กับแอปจะเป็นไปอย่างราบรื่น แอปของคุณต้องแสดงผลเฟรมที่น้อยกว่า 16 มิลลิวินาทีเพื่อให้ได้ 60 เฟรมต่อวินาที (FPS) หากต้องการทำความเข้าใจถึงเหตุผลที่ควรใช้ 60 FPS โปรดดูรูปแบบประสิทธิภาพของ Android ทำไมต้องใช้ 60 FPS หากคุณกำลังพยายามที่จะ 90 fps หน้าต่างนี้จะลดลงเหลือ 11 มิลลิวินาที และสําหรับ 120 เฟรมต่อวินาที 8 มิลลิวินาที
หากคุณใช้หน้าต่างนี้เกิน 1 มิลลิวินาที ไม่ได้หมายความว่าเฟรมนั้นจะแสดง
ล่าช้า 1 มิลลิวินาที แต่เหลือเวลา Choreographer
วางเฟรมทั้งหมด หากแอปได้รับผลกระทบจากการแสดงผล UI ที่ช้า
ระบบถูกบังคับให้ข้ามเฟรม และผู้ใช้รับรู้ถึงการกระตุกในแอปของคุณ
วิธีนี้เรียกว่า jank หน้านี้แสดงวิธีวินิจฉัยและแก้ไขการกระตุก
หากคุณกำลังพัฒนาเกมที่ไม่ได้ใช้
View
จากนั้นให้คุณข้าม
Choreographer
ในกรณีนี้คือ Frame Pacing
ความช่วยเหลือเกี่ยวกับคลัง
OpenGL และ
เกม Vulkan สามารถแสดงผลได้อย่างราบรื่นและ
ความเร็วของเฟรมใน Android ถูกต้อง
Android จะตรวจสอบแอปเพื่อหาการกระตุกโดยอัตโนมัติเพื่อช่วยปรับปรุงคุณภาพแอป และแสดงข้อมูลในหน้าแดชบอร์ดของ Android Vitals สำหรับข้อมูล เกี่ยวกับวิธีการรวบรวมข้อมูล โปรดดูตรวจสอบคุณภาพทางเทคนิคของแอปด้วย แอนดรอยด์ Vitals
ระบุการกระตุก
การค้นหาโค้ดในแอปที่ทำให้เกิดการกระตุกอาจเป็นเรื่องยาก ส่วนนี้ อธิบายวิธีระบุการกระตุก 3 วิธี ดังนี้
การตรวจสอบด้วยภาพช่วยให้คุณตรวจสอบกรณีการใช้งานทั้งหมดในแอปได้ใน 2-3 ที่ นาที แต่ไม่ได้ให้รายละเอียดมากเท่ากับ Systrace Systrace จะระบุ รายละเอียดเพิ่มเติม แต่ถ้าคุณเรียกใช้ Systrace สำหรับ Use Case ทั้งหมดในแอป คุณสามารถ ด้วยข้อมูลมากมายที่วิเคราะห์ได้ยาก ทั้งภาพ check และ Systrace ตรวจพบการกระตุกในอุปกรณ์ หากทำซ้ำไม่ได้ ความยุ่งยากในอุปกรณ์ภายใน คุณสามารถสร้างการตรวจสอบประสิทธิภาพที่กำหนดเองเพื่อวัดผล ส่วนใดส่วนหนึ่งของแอปในอุปกรณ์ที่ทำงานภาคสนามได้
การตรวจสอบการมองเห็น
การตรวจสอบด้วยภาพจะช่วยคุณระบุกรณีการใช้งานที่ก่อให้เกิดการกระตุก ถึง ดำเนินการตรวจสอบด้วยภาพ เปิดแอปของคุณ และดำเนินการตามขั้นตอน และมองหาการกระตุกใน UI ของคุณ
เคล็ดลับบางส่วนสำหรับการตรวจสอบด้วยภาพมีดังนี้
- เรียกใช้แอปรุ่น (หรือเวอร์ชันที่แก้ไขข้อบกพร่องไม่ได้) เป็นอย่างน้อย ศิลปะ รันไทม์จะปิดใช้การเพิ่มประสิทธิภาพที่สำคัญหลายอย่างเพื่อรองรับการแก้ไขข้อบกพร่อง เพื่อให้แน่ใจว่าคุณกำลังดูสิ่งที่คล้ายกับสิ่งที่ผู้ใช้ เห็น
- เปิดใช้ GPU โปรไฟล์ การแสดงภาพ การแสดงผล GPU ของโปรไฟล์จะแสดงแถบบนหน้าจอที่ช่วยให้คุณเห็นภาพ ระยะเวลาที่ใช้ในการแสดงผลเฟรมของหน้าต่าง UI ซึ่งสัมพันธ์กับการเปรียบเทียบ 16 มิลลิวินาทีต่อเฟรม แต่ละแท่งมีส่วนประกอบที่เป็นสี นั้นแมปกับขั้นตอนในไปป์ไลน์การแสดงผล เพื่อให้คุณดูได้ว่าส่วนใด ใช้เวลานานที่สุด เช่น หากเฟรมใช้เวลานาน วิธีจัดการอินพุต ดูโค้ดของแอปที่จัดการข้อมูลจากผู้ใช้
- เรียกใช้ผ่านคอมโพเนนต์ที่เป็นแหล่งที่มาทั่วไปของความไม่สม่ำเสมอ เช่น
ด้วย
RecyclerView
- เริ่มต้นแอปพลิเคชันจาก Cold เริ่มต้น
- เรียกใช้แอปในอุปกรณ์ที่ช้ากว่าเพื่อทำให้ปัญหาร้ายแรงยิ่งขึ้น
เมื่อพบกรณีการใช้งานที่ทำให้เกิดการติดขัด คุณอาจพอจะทราบว่า ซึ่งก่อให้เกิดการกระตุกในแอปของคุณ หากต้องการข้อมูลเพิ่มเติม คุณสามารถใช้ Systrace เพื่อค้นหาสาเหตุเพิ่มเติม
ซิสเตรซ
แม้ว่า Systrace จะเป็นเครื่องมือที่แสดงให้เห็นสิ่งที่อุปกรณ์ทั้งเครื่องกำลังทำอยู่ แต่ก็สามารถ มีประโยชน์ในการระบุการกระตุกในแอปของคุณ Systrace มีระบบน้อยที่สุด เหนือศีรษะ คุณจึงสัมผัสกับความคมชัดที่สมจริงระหว่างการใช้เครื่องมือได้
บันทึกการติดตามด้วย Systrace ขณะดำเนินการ กรณีการใช้งานที่คุณภาพไม่ดีบนอุปกรณ์ของคุณ สำหรับคำแนะนำเกี่ยวกับวิธีใช้ Systrace โปรดดู บันทึกการติดตามของระบบในบรรทัดคำสั่ง Systrace จะแยกตามกระบวนการและ ชุดข้อความ มองหากระบวนการของแอปใน Systrace ซึ่งจะมีลักษณะดังนี้ รูปที่ 1
![ตัวอย่าง Systrace](https://developer.android.com/static/topic/performance/vitals/images/systrace.png?authuser=19&hl=th)
ตัวอย่าง Systrace ในรูปที่ 1 มีข้อมูลต่อไปนี้ การระบุการกระตุก:
- Systrace จะแสดงเวลาที่วาดแต่ละเฟรมและแสดงรหัสสีให้แต่ละเฟรม ไฮไลต์เวลาในการแสดงผลช้า ซึ่งจะช่วยให้คุณพบเฟรมที่มีคุณภาพต่ำมากขึ้น ที่แม่นยำกว่าการตรวจสอบด้วยภาพ สำหรับข้อมูลเพิ่มเติม โปรดดู ตรวจสอบ UI เฟรมและการแจ้งเตือน
- Systrace จะตรวจหาปัญหาในแอปและแสดงการแจ้งเตือนทั้งใน แต่ละเฟรม การแจ้งเตือน แผง ขอแนะนำให้ทำตามคำแนะนำในการแจ้งเตือน
- ส่วนต่างๆ ในเฟรมเวิร์กและไลบรารีของ Android เช่น
RecyclerView
มีเครื่องหมายการติดตาม ดังนั้น ไทม์ไลน์ systrace จะแสดงเมื่อมีการเรียกใช้เมธอดเหล่านั้นบน UI และระยะเวลาในการดำเนินการ
หลังจากดูเอาต์พุต Systrace แล้ว อาจมีเมธอดในแอปของคุณที่
ที่คุณสงสัยว่าทำให้เกิดการกระตุก เช่น หากไทม์ไลน์แสดงว่า
เฟรมนี้เกิดจากการที่ RecyclerView
ใช้เวลานาน คุณสามารถ เพิ่มการติดตามที่กำหนดเอง
เหตุการณ์กับโค้ดที่เกี่ยวข้องและ
เรียกใช้ Systrace อีกครั้งเพื่อดูข้อมูลเพิ่มเติม ใน Systrace ใหม่ ไทม์ไลน์จะแสดง
เมื่อมีการเรียกเมธอดของแอป และใช้เวลานานเท่าใดในการดำเนินการ
หาก Systrace ไม่แสดงรายละเอียดเกี่ยวกับสาเหตุที่การทำงานของเทรด UI ใช้เวลานาน แล้วใช้ CPU ของ Android เครื่องมือสร้างโปรไฟล์เพื่อบันทึก การติดตามเมธอดแบบสุ่มตัวอย่างหรือมีการวัดคุม โดยทั่วไปแล้ว การติดตามเมธอดไม่เหมาะกับประเภท การระบุความยุ่งยากเพราะสร้างความแตกต่างที่ผิดพลาดเนื่องจากปริมาณที่มากเกินไป จากด้านบน และจะไม่เห็นว่าชุดข้อความกำลังทำงานอยู่หรือถูกบล็อก แต่ การติดตามเมธอดสามารถช่วยคุณระบุเมธอดในแอปของคุณที่ใช้ เป็นส่วนใหญ่ หลังจากระบุวิธีการเหล่านี้แล้ว ให้เพิ่ม Trace เครื่องหมายและเรียกใช้ Systrace อีกครั้งเพื่อดู วิธีการเหล่านี้ทำให้เกิดการกระตุกหรือไม่
สำหรับข้อมูลเพิ่มเติม โปรดดู ทำความเข้าใจ Systrace
การตรวจสอบประสิทธิภาพที่กำหนดเอง
หากสร้างข้อบกพร่องซ้ำในอุปกรณ์ภายในไม่ได้ คุณก็สร้างประสิทธิภาพที่กำหนดเองได้ การตรวจสอบในแอปของคุณเพื่อช่วยระบุแหล่งที่มาของการกระตุกในอุปกรณ์ใน ด้วย
วิธีการคือรวบรวมเวลาในการแสดงผลเฟรมจากส่วนใดส่วนหนึ่งของแอปด้วย
FrameMetricsAggregator
แล้วบันทึกและวิเคราะห์ข้อมูลโดยใช้ประสิทธิภาพ Firebase
การตรวจสอบ
โปรดดูข้อมูลเพิ่มเติมที่หัวข้อเริ่มต้นใช้งานการตรวจสอบประสิทธิภาพสำหรับ Android
เฟรมที่หยุดทำงาน
เฟรมที่ค้างคือเฟรม UI ที่ใช้เวลาในการแสดงผลนานกว่า 700 มิลลิวินาที นี่คือ เพราะดูเหมือนว่าแอปของคุณค้างและไม่ตอบสนองข้อมูลจากผู้ใช้ เป็นเวลาเกือบ 1 วินาทีระหว่างที่เฟรมกำลังแสดงผล เราขอแนะนำให้เพิ่มประสิทธิภาพ แอปเพื่อแสดงผลเฟรมภายใน 16 มิลลิวินาทีเพื่อดูแลให้ UI ราบรื่น แต่ระหว่างใช้แอป เริ่มต้นหรือขณะที่เปลี่ยนไปใช้หน้าจออื่น เป็นเรื่องปกติที่ เฟรมเริ่มต้นที่จะใช้เวลาในการวาดนานกว่า 16 มิลลิวินาที เนื่องจากแอปต้องสูงเกินจริง มุมมอง จัดวางหน้าจอ และเริ่มต้นวาดใหม่ทั้งหมด จึงเป็นเหตุผลที่ Android ติดตามเฟรมที่ค้างแยกจากการแสดงผลช้า ยังไม่ได้ติดต่อ เฟรมในแอปควรใช้เวลานานกว่า 700 มิลลิวินาทีในการแสดงผล
เพื่อช่วยคุณปรับปรุงคุณภาพแอป Android จะตรวจสอบแอปของคุณโดยอัตโนมัติเพื่อหา เฟรมที่ค้างและแสดงข้อมูลในแดชบอร์ด Android Vitals สำหรับข้อมูลเกี่ยวกับการรวบรวมข้อมูล โปรดดูที่ตรวจสอบด้านเทคนิคของแอป คุณภาพด้วย Android Vitals
เฟรมที่ค้างคือรูปแบบของการแสดงผลช้าอย่างมาก ดังนั้น ในการวินิจฉัยและแก้ไขปัญหานั้นเหมือนกัน
การติดตามการกระตุก
ไทม์ไลน์ของเฟรม ใน Perfetto สามารถช่วยในการติดตามได้ช้าหรือ เฟรมที่ค้าง
ความสัมพันธ์ระหว่างเฟรมที่ช้า เฟรมที่ค้าง และ ANR
เฟรมที่ช้า เฟรมที่ค้าง และ ANR ล้วนเป็นข้อขัดข้องที่ อาจพบเจอ โปรดดูตารางด้านล่างเพื่อทําความเข้าใจความแตกต่าง
เฟรมที่ช้า | เฟรมที่หยุดทำงาน | ANR | |
---|---|---|---|
เวลาในการแสดงผล | ระหว่าง 16 ถึง 700 มิลลิวินาที | ระหว่าง 700 ถึง 5 วินาที | มากกว่า 5 วินาที |
บริเวณที่เกี่ยวข้องกับผู้ใช้ที่มองเห็นได้ |
|
|
|
ติดตามเฟรมที่ช้าและเฟรมที่ค้างแยกกัน
ระหว่างที่แอปเริ่มทำงานหรือขณะเปลี่ยนไปใช้หน้าจออื่น เป็นเรื่องปกติ เฟรมเริ่มต้นที่จะใช้เวลาในการวาดนานกว่า 16 มิลลิวินาที เนื่องจากแอปต้อง ขยายมุมมอง จัดพื้นที่หน้าจอ และเริ่มวาดรูปแรกตั้งแต่ต้น
แนวทางปฏิบัติแนะนำในการจัดลำดับความสำคัญและแก้ไขปัญหาการกระตุก
โปรดคํานึงถึงแนวทางปฏิบัติแนะนําต่อไปนี้เมื่อต้องการแก้ไขการกระตุกใน แอป:
- ระบุและแก้ไขอินสแตนซ์ที่เกิดปัญหาซึ่งจำลองซ้ำได้ง่ายที่สุด
- จัดลำดับความสำคัญของ ANR แม้ว่าเฟรมที่ช้าหรือค้างอาจทำให้แอปปรากฏขึ้น ช้า ANR ทำให้แอปหยุดตอบสนอง
- การแสดงผลช้าจะทำซ้ำได้ยาก แต่คุณสามารถเริ่มโดยการหยุดที่ 700 มิลลิวินาทีที่ค้าง ของเฟรม กรณีนี้พบได้บ่อยที่สุดขณะที่แอปกำลังเริ่มต้นหรือเปลี่ยนหน้าจอ
กำลังแก้ไขการกระตุก
ถ้าจะแก้ไขการกระตุก ให้ตรวจสอบว่าเฟรมใดทำงานไม่เสร็จใน 16 มิลลิวินาที และดูว่า
ผิด ตรวจสอบว่า Record View#draw
หรือ Layout
ใช้เวลานานผิดปกติหรือไม่
เฟรมบางส่วน โปรดดูแหล่งที่มาทั่วไปของความไม่สม่ำเสมอสำหรับปัญหาเหล่านี้ และ
อื่นๆ
เรียกใช้งานที่ใช้เวลานานแบบอะซิงโครนัสนอกชุดข้อความ UI เพื่อหลีกเลี่ยงการกระตุก พึงระวังอยู่เสมอว่าโค้ดของคุณทำงานอยู่โดยใช้เทรดใดและใช้ความระมัดระวังเมื่อ การโพสต์งานที่ไม่สำคัญลงในชุดข้อความหลัก
หากคุณมี UI หลักที่ซับซ้อนและสำคัญ เช่น อินเทอร์เฟซหลัก รายการแบบเลื่อน - ลองใช้การใช้เครื่องมือในการเขียน การทดสอบ ที่สามารถทดสอบ ตรวจจับเวลาในการแสดงผลช้าและทำการทดสอบบ่อยๆ เพื่อป้องกันการเกิดปัญหาซ้ำ
แหล่งที่มาของการกระตุกที่พบบ่อย
ส่วนต่อไปนี้จะอธิบายสาเหตุทั่วไปของการกระตุกในแอปโดยใช้ View
ที่เป็นระบบและแนวทางปฏิบัติแนะนำในการจัดการกับปัญหา สำหรับข้อมูลเกี่ยวกับการแก้ไข
ปัญหาเกี่ยวกับประสิทธิภาพของ Jetpack Compose โปรดดู Jetpack
ประสิทธิภาพของการเขียน
รายการแบบเลื่อนได้
ListView
และที่สำคัญ
RecyclerView
- มักใช้สำหรับรายการแบบเลื่อนที่ซับซ้อนซึ่งส่วนใหญ่
มีแนวโน้มที่จะกระตุกได้ ทั้งคู่มีเครื่องหมาย Systrace คุณจึงสามารถใช้ Systrace ได้
เพื่อดูว่าส่วนขยายส่งผลต่อการกระตุกในแอปของคุณหรือไม่ ส่งบรรทัดคำสั่ง
อาร์กิวเมนต์ -a
<your-package-name>
เพื่อรับส่วนการติดตามใน RecyclerView
รวมถึงส่วนการติดตาม
เครื่องหมายการติดตามที่คุณเพิ่มไว้จะปรากฏขึ้น ทำตามคำแนะนำของ
ที่สร้างขึ้นในเอาต์พุต Systrace ภายใน Systrace คุณสามารถคลิก
ส่วนที่ติดตาม RecyclerView
เพื่อดูคำอธิบายงาน RecyclerView
กำลังทำอะไรอยู่
RecyclerView: AlertDataSetChanged()
หากคุณเห็นรายการทั้งหมดใน RecyclerView
ได้รับการย้อนกลับ และมีการวางตำแหน่งอีกครั้ง
และวาดใหม่ในเฟรมเดียว ดูให้แน่ใจว่าคุณไม่ได้เรียกใช้
notifyDataSetChanged()
,
setAdapter(Adapter)
,
หรือ swapAdapter(Adapter,
boolean)
สำหรับการอัปเดตเล็กๆ น้อยๆ วิธีการเหล่านี้จะส่งสัญญาณว่า มีการเปลี่ยนแปลงกับ
แสดงเนื้อหาและแสดงใน Systrace เป็น RV Fullต่อยอด ให้ใช้
SortedList
หรือ
DiffUtil
เพื่อสร้าง
มีการอัปเดตเพียงเล็กน้อยเมื่อมีการเปลี่ยนแปลงหรือเพิ่มเนื้อหา
ตัวอย่างเช่น ลองพิจารณาแอปที่ได้รับรายการข่าวเวอร์ชันใหม่
เนื้อหาจากเซิร์ฟเวอร์ เมื่อคุณโพสต์ข้อมูลนี้ไปยังอะแดปเตอร์
ที่สามารถเรียก notifyDataSetChanged()
ได้ ดังที่ปรากฏในตัวอย่างต่อไปนี้:
Kotlin
fun onNewDataArrived(news: List<News>) { myAdapter.news = news myAdapter.notifyDataSetChanged() }
Java
void onNewDataArrived(List<News> news) { myAdapter.setNews(news); myAdapter.notifyDataSetChanged(); }
ข้อเสียก็คือ ถ้ามีการเปลี่ยนแปลงเล็กๆ น้อยๆ เช่น รายการเดียว
เพิ่มไว้ด้านบนแล้ว RecyclerView
จะไม่ทราบ ดังนั้นจึงบอกให้เลิก
สถานะรายการที่แคชไว้ทั้งหมด ดังนั้นจึงต้องเชื่อมโยงทุกอย่างใหม่อีกครั้ง
เราขอแนะนำให้คุณใช้ DiffUtil
ซึ่งคำนวณและส่งการอัปเดตเพียงเล็กน้อย
สำหรับคุณ:
Kotlin
fun onNewDataArrived(news: List<News>) { val oldNews = myAdapter.items val result = DiffUtil.calculateDiff(MyCallback(oldNews, news)) myAdapter.news = news result.dispatchUpdatesTo(myAdapter) }
Java
void onNewDataArrived(List<News> news) { List<News> oldNews = myAdapter.getItems(); DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news)); myAdapter.setNews(news); result.dispatchUpdatesTo(myAdapter); }
หากต้องการแจ้ง DiffUtil
เกี่ยวกับวิธีตรวจสอบรายการ ให้กำหนด MyCallback
เป็น
Callback
การใช้งานของคุณ
RecyclerView: RecyclerView แบบซ้อน
เป็นเรื่องปกติที่จะฝัง RecyclerView
หลายอินสแตนซ์ โดยเฉพาะที่มี
รายการแนวตั้งของรายการที่เลื่อนในแนวนอน ตัวอย่างนี้คือตารางกริด
ในหน้าหลักของ Play Store วิธีนี้อาจได้ผลดี แต่ก็ต้อง
มุมมองที่ย้ายไปมา
หากคุณเห็นเนื้อหาด้านในจำนวนมากพองออกเมื่อคุณเลื่อนหน้าเว็บลงมาเป็นครั้งแรก
คุณก็ควรตรวจสอบว่ากำลังแชร์
RecyclerView.RecycledViewPool
ระหว่างอินสแตนซ์ด้านใน (แนวนอน) ของ RecyclerView
โดยค่าเริ่มต้น แต่ละ
RecyclerView
สร้างกลุ่มรายการของตัวเอง แต่ในกรณีของสิบครั้ง
itemViews
บนหน้าจอในเวลาเดียวกัน จะเกิดปัญหาเมื่อ itemViews
เข้าถึงไม่ได้
รายการแนวนอนที่ต่างกัน หากทุกแถวแสดงคล้ายกัน
ประเภทต่างๆ
Kotlin
class OuterAdapter : RecyclerView.Adapter<OuterAdapter.ViewHolder>() { ... override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { // Inflate inner item, find innerRecyclerView by ID. val innerLLM = LinearLayoutManager(parent.context, LinearLayoutManager.HORIZONTAL, false) innerRv.apply { layoutManager = innerLLM recycledViewPool = sharedPool } return OuterAdapter.ViewHolder(innerRv) } ...
Java
class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> { RecyclerView.RecycledViewPool sharedPool = new RecyclerView.RecycledViewPool(); ... @Override public void onCreateViewHolder(ViewGroup parent, int viewType) { // Inflate inner item, find innerRecyclerView by ID. LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(), LinearLayoutManager.HORIZONTAL); innerRv.setLayoutManager(innerLLM); innerRv.setRecycledViewPool(sharedPool); return new OuterAdapter.ViewHolder(innerRv); } ...
หากต้องการเพิ่มประสิทธิภาพเพิ่มเติม คุณยังเรียกใช้
setInitialPrefetchItemCount(int)
ใน
LinearLayoutManager
ภายใน RecyclerView
ตัวอย่างเช่น หากคุณแสดง 3.5 รายการอยู่เสมอ
เรียก innerLLM.setInitialItemPrefetchCount(4)
ติดต่อกัน ซึ่งจะส่งสัญญาณถึง
RecyclerView
ว่าเมื่อแถวแนวนอนกำลังจะแสดงให้เห็นหน้าจอ แถวแนวนอน
พยายามดึงข้อมูลรายการที่อยู่ข้างในล่วงหน้าหากมีเวลาว่างในเธรด UI
RecyclerView: เงินเฟ้อมากเกินไปหรือการสร้างใช้เวลานานเกินไป
ในกรณีส่วนใหญ่ ฟีเจอร์การดึงข้อมูลล่วงหน้าใน RecyclerView
สามารถช่วยแก้ไข
จากเงินเฟ้อด้วยการดำเนินการล่วงหน้าขณะที่เทรด UI ไม่มีการใช้งาน
หากคุณเห็นเงินเฟ้อขณะอยู่ในเฟรมและไม่ได้อยู่ในส่วนที่มีป้ายกำกับว่า RV
ดึงข้อมูลล่วงหน้า อย่าลืมทดสอบในอุปกรณ์ที่รองรับและใช้
เวอร์ชันไลบรารีการสนับสนุน
การดึงข้อมูลล่วงหน้าทำได้เฉพาะใน Android 5.0 API ระดับ 21 ขึ้นไปเท่านั้น
หากคุณเห็นเงินเฟ้อที่ทำให้เกิดความวุ่นวายอยู่บ่อยๆ เมื่อมีรายการใหม่บนหน้าจอ ให้ตรวจสอบ
ไม่เกินประเภทข้อมูลพร็อพเพอร์ตี้ที่ต้องการ ยิ่งประเภทการดูใน
เนื้อหาใน RecyclerView
เงินเฟ้อก็จะต้องดำเนินการน้อยลงเมื่อมีการเพิ่ม
ปรากฏบนหน้าจอ หากเป็นไปได้ ให้ผสานประเภทข้อมูลพร็อพเพอร์ตี้ตามความเหมาะสม ถ้า
เมื่อมีเพียงไอคอน สี หรือข้อความประเภทต่างๆ มีการเปลี่ยนแปลง
ในช่วงเวลาที่เชื่อมโยงและหลีกเลี่ยงเงินเฟ้อ ซึ่งจะลดหน่วยความจำของแอป
รอยเท้าไปพร้อมกัน
หากประเภทการดูของคุณดูดี ให้ดูการลดค่าใช้จ่ายจากเงินเฟ้อ
การลดมุมมองคอนเทนเนอร์และโครงสร้างที่ไม่จำเป็นอาจช่วยได้ พิจารณาสร้าง
itemViews
ร่วมกับ ConstraintLayout
ซึ่งช่วยลดมุมมองเชิงโครงสร้างได้
หากต้องการเพิ่มประสิทธิภาพให้มากยิ่งขึ้น และลําดับชั้นของรายการ เรียบง่ายโดยไม่จำเป็นต้องมีธีมและสไตล์ที่ซับซ้อน ให้ลองโทร ของผู้สร้างด้วยตัวเอง แต่ก็ไม่ได้คุ้มค่าที่จะแลกกับการสูญเสีย ความเรียบง่ายและคุณลักษณะของ XML
RecyclerView: การเชื่อมโยงใช้เวลานานเกินไป
การเชื่อมโยง ซึ่งก็คือ onBindViewHolder(VH,
int)
จะต้องตรงไปตรงมาและใช้เวลาน้อยกว่า 1 มิลลิวินาทีอย่างมาก
ยกเว้นรายการที่ซับซ้อนที่สุด ต้องใช้ออบเจ็กต์ Java (POJO) แบบเก่าธรรมดา
รายการต่างๆ จากข้อมูลรายการภายในของอะแดปเตอร์และตัวตั้งค่าการเรียกในมุมมองใน
ViewHolder
หาก RV OnBindView ใช้เวลานาน ให้ตรวจสอบว่าคุณ
ทำงานน้อยที่สุดในโค้ดการเชื่อมโยง
หากคุณใช้ออบเจ็กต์ POJO พื้นฐานเพื่อเก็บข้อมูลในอะแดปเตอร์ คุณจะทําสิ่งต่อไปนี้ได้
หลีกเลี่ยงการเขียนโค้ดการเชื่อมโยงใน onBindViewHolder
อย่างสิ้นเชิงด้วยการใช้
ไลบรารีการเชื่อมโยงข้อมูล
RecyclerView หรือ ListView: การออกแบบหรือการวาดใช้เวลานานเกินไป
สำหรับปัญหาเกี่ยวกับการวาดและเลย์เอาต์ โปรดดูประสิทธิภาพของเลย์เอาต์และ ประสิทธิภาพการแสดงผล
มุมมองรายการ: เงินเฟ้อ
คุณปิดใช้การรีไซเคิลในListView
โดยไม่ได้ตั้งใจได้หากไม่ระมัดระวัง ถ้า
คุณจะเห็นเงินเฟ้อทุกครั้งที่มีสินค้าแสดงขึ้นบนหน้าจอ ให้ตรวจสอบว่า
การใช้งาน
Adapter.getView()
กำลังคิด เชื่อมโยงใหม่ และแสดงผลพารามิเตอร์ convertView
หาก
การใช้งาน getView()
สูงกว่าความเป็นจริงเสมอ แอปของคุณไม่ได้รับประโยชน์จาก
การรีไซเคิลใน ListView
โครงสร้างของ getView()
ของคุณจะต้อง
ซึ่งคล้ายกับการดำเนินการต่อไปนี้
Kotlin
fun getView(position: Int, convertView: View?, parent: ViewGroup): View { return (convertView ?: layoutInflater.inflate(R.layout.my_layout, parent, false)).apply { // Bind content from position to convertView. } }
Java
View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { // Only inflate if no convertView passed. convertView = layoutInflater.inflate(R.layout.my_layout, parent, false) } // Bind content from position to convertView. return convertView; }
ประสิทธิภาพของเลย์เอาต์
หาก Systrace แสดงว่าส่วนเลย์เอาต์ของ Choreographer#doFrame
ทำงานมากเกินไปหรือบ่อยเกินไป ซึ่งนั่นหมายความว่าคุณกำลังเลือกใช้งาน
ปัญหาด้านประสิทธิภาพ ประสิทธิภาพเลย์เอาต์ของแอปขึ้นอยู่กับส่วนใด
ของลำดับชั้นการแสดงผลได้เปลี่ยนพารามิเตอร์ของเลย์เอาต์หรืออินพุต
ประสิทธิภาพของเลย์เอาต์: ต้นทุน
หากกลุ่มมีความยาวมากกว่า 2-3 มิลลิวินาที เป็นไปได้ว่า
ประสิทธิภาพการซ้อนในกรณีที่แย่ที่สุดสำหรับ
RelativeLayouts
หรือ
weighted-LinearLayouts
แต่ละ
เลย์เอาต์เหล่านี้จะทริกเกอร์การวัดผลและเลย์เอาต์แบบต่างๆ ของบุตรหลานได้ ดังนั้น
การซ้อนกันอาจทำให้เกิดลักษณะการทำงาน O(n^2)
ในระดับความลึกของการซ้อน
พยายามหลีกเลี่ยง RelativeLayout
หรือคุณลักษณะน้ำหนักของ LinearLayout
ในทั้งหมดยกเว้น
โหนดใบไม้ที่ต่ำที่สุดในลำดับชั้น ซึ่งทำได้หลายวิธี ดังนี้
- จัดระเบียบมุมมองโครงสร้างใหม่
- กำหนดตรรกะเลย์เอาต์ที่กำหนดเอง โปรดดู
เพิ่มประสิทธิภาพลำดับชั้นของเลย์เอาต์
สำหรับตัวอย่างที่เจาะจง คุณสามารถลองแปลงเป็น
ConstraintLayout
ซึ่งให้ คุณลักษณะที่คล้ายกัน โดยไม่มีข้อเสียด้านประสิทธิภาพ
ประสิทธิภาพของการออกแบบ: ความถี่
เลย์เอาต์จะเกิดขึ้นเมื่อเนื้อหาใหม่ปรากฏบนหน้าจอ
มีรายการใหม่เลื่อนเข้ามาในมุมมองใน RecyclerView
หากเลย์เอาต์ที่สำคัญคือ
ที่เกิดขึ้นในแต่ละเฟรม เป็นไปได้ว่าคุณกำลังทำให้เลย์เอาต์เคลื่อนไหว
ก็อาจทำให้เฟรมลดลงได้
โดยทั่วไป ภาพเคลื่อนไหวต้องทำงานโดยใช้คุณสมบัติการวาดของ View
เช่น
ดังต่อไปนี้:
คุณสามารถเปลี่ยนการตั้งค่าทั้งหมดเหล่านี้ได้ในราคาที่ถูกกว่าคุณสมบัติของเลย์เอาต์มาก เช่น
ระยะห่างจากขอบ หรือขอบ โดยทั่วไป การเปลี่ยนภาพวาดจะมีราคาถูกกว่ามาก
ของมุมมองโดยการเรียกตัวตั้งค่าซึ่งทริกเกอร์
invalidate()
ตามด้วย
draw(Canvas)
ในเฟรมถัดไป การดำเนินการนี้จะบันทึกการดำเนินการวาดใหม่สำหรับมุมมองที่
และโดยทั่วไปจะมีราคาถูกกว่าการจัดวางเป็นอย่างมาก
ประสิทธิภาพการแสดงผล
UI ของ Android แบ่งเป็น 2 ระยะ ดังนี้
- Record View#draw ในเธรด UI ซึ่งทำงาน
draw(Canvas)
ในทุก มุมมองที่ไม่ถูกต้อง และสามารถเรียกใช้การเรียกไปยังมุมมองที่กำหนดเองหรือเข้าสู่โค้ดของคุณ - DrawFrame ใน
RenderThread
ซึ่งทำงานบนRenderThread
ของระบบ แต่จะทำงานตามงานที่สร้างขึ้นในระยะ Record View#draw
ประสิทธิภาพการแสดงผล: ชุดข้อความ UI
หาก Record View#draw ใช้เวลานาน ถือเป็นเรื่องปกติที่บิตแมป บนชุดข้อความ UI การวาดภาพเป็นบิตแมปจะใช้การแสดงผล CPU ให้หลีกเลี่ยงเหตุการณ์นี้เมื่อทำได้ คุณสามารถใช้การติดตามเมธอดด้วย Android เครื่องมือสร้างโปรไฟล์ CPU เพื่อดูว่าใช่ ปัญหาที่เกิดขึ้น
การลงสีเป็นบิตแมปมักจะทำเมื่อแอปต้องการตกแต่งบิตแมปก่อน สำหรับการแสดงรูปภาพ บางครั้งก็จะเป็นการตกแต่ง เช่น การใส่มุมโค้งมน
Kotlin
val paint = Paint().apply { isAntiAlias = true } Canvas(roundedOutputBitmap).apply { // Draw a round rect to define the shape: drawRoundRect( 0f, 0f, roundedOutputBitmap.width.toFloat(), roundedOutputBitmap.height.toFloat(), 20f, 20f, paint ) paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY) // Multiply content on top to make it rounded. drawBitmap(sourceBitmap, 0f, 0f, paint) setBitmap(null) // Now roundedOutputBitmap has sourceBitmap inside, but as a circle. }
Java
Canvas bitmapCanvas = new Canvas(roundedOutputBitmap); Paint paint = new Paint(); paint.setAntiAlias(true); // Draw a round rect to define the shape: bitmapCanvas.drawRoundRect(0, 0, roundedOutputBitmap.getWidth(), roundedOutputBitmap.getHeight(), 20, 20, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); // Multiply content on top to make it rounded. bitmapCanvas.drawBitmap(sourceBitmap, 0, 0, paint); bitmapCanvas.setBitmap(null); // Now roundedOutputBitmap has sourceBitmap inside, but as a circle.
หากนี่เป็นงานประเภทที่คุณกำลังทำบนเธรด UI คุณสามารถทำ
ในเธรดการถอดรหัสในเบื้องหลัง ในบางกรณี เช่น
ตัวอย่างเช่น คุณสามารถทำงาน
ในช่วงเวลาที่ได้ด้วย ดังนั้นถ้า
Drawable
หรือ
โค้ด View
มีลักษณะดังนี้
Kotlin
fun setBitmap(bitmap: Bitmap) { mBitmap = bitmap invalidate() } override fun onDraw(canvas: Canvas) { canvas.drawBitmap(mBitmap, null, paint) }
Java
void setBitmap(Bitmap bitmap) { mBitmap = bitmap; invalidate(); } void onDraw(Canvas canvas) { canvas.drawBitmap(mBitmap, null, paint); }
โดยใช้คำสั่งต่อไปนี้แทน
Kotlin
fun setBitmap(bitmap: Bitmap) { shaderPaint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) invalidate() } override fun onDraw(canvas: Canvas) { canvas.drawRoundRect(0f, 0f, width, height, 20f, 20f, shaderPaint) }
Java
void setBitmap(Bitmap bitmap) { shaderPaint.setShader( new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP)); invalidate(); } void onDraw(Canvas canvas) { canvas.drawRoundRect(0, 0, width, height, 20, 20, shaderPaint); }
คุณยังตั้งค่านี้สำหรับการป้องกันพื้นหลังได้ด้วย เช่น เมื่อวาดการไล่ระดับสี
ที่ด้านบนของบิตแมป และการกรองรูปภาพด้วย
ColorMatrixColorFilter
- 2
การดำเนินการอื่นๆ ทั่วไปที่กระทำ การแก้ไขบิตแมป
หากคุณกำลังวาดไปยังบิตแมปด้วยเหตุผลอื่น อาจเป็นไปได้ว่าการใช้บิตแมปเป็น
แคช - พยายามวาดไปยัง Canvas
ที่เร่งการแสดงผลด้วยฮาร์ดแวร์ที่ส่งไปยัง View
หรือ
Drawable
โดยตรง หากจำเป็น ขอแนะนำให้โทร
setLayerType()
กับ
LAYER_TYPE_HARDWARE
เพื่อแคชเอาต์พุตการแสดงผลที่ซับซ้อนและยังคงใช้ประโยชน์จากการแสดงภาพของ GPU
ประสิทธิภาพการแสดงผล: RenderThread
การดำเนินการ Canvas
บางรายการมีราคาถูก แต่ทริกเกอร์การประมวลผลที่มีราคาแพง
ในวันที่ RenderThread
โดยทั่วไปแล้ว Systrace จะแจ้งเตือนให้ทราบ
การสร้างภาพเคลื่อนไหวเส้นทางขนาดใหญ่
วันและเวลา
มีการเรียกใช้ Canvas.drawPath()
เมื่อผ่าน Canvas
ที่เร่งการแสดงผลด้วยฮาร์ดแวร์
ถึงวันที่ View
Android จะวาดเส้นทางเหล่านี้บน CPU ก่อนแล้วอัปโหลดไปยัง GPU
หากคุณมีเส้นทางขนาดใหญ่ ให้หลีกเลี่ยงการแก้ไขจากเฟรมหนึ่งไปยังอีกเฟรม เพื่อให้
สามารถแคชและวาดได้อย่างมีประสิทธิภาพ
drawPoints()
drawLines()
และ drawRect/Circle/Oval/RoundRect()
มีประสิทธิภาพมากกว่าและ
ใช้งานได้ดียิ่งขึ้น แม้ว่าคุณจะใช้การเรียกโฆษณามากกว่าก็ตาม
Canvas.clipPath
clipPath(Path)
ทำให้เกิดพฤติกรรมการตัดค่าใช้จ่ายที่มีราคาสูง และโดยทั่วไปแล้วต้องหลีกเลี่ยง วันและเวลา
หากเป็นไปได้ ให้เลือกใช้การวาดรูปร่างแทนการตัดให้เป็นรูปสี่เหลี่ยมผืนผ้า ทั้งนี้
ทำงานได้ดีกว่าและรองรับการลบรอยหยัก ตัวอย่างเช่น URL ต่อไปนี้
การเรียก clipPath
อาจแสดงต่างออกไป ดังนี้
Kotlin
canvas.apply { save() clipPath(circlePath) drawBitmap(bitmap, 0f, 0f, paint) restore() }
Java
canvas.save(); canvas.clipPath(circlePath); canvas.drawBitmap(bitmap, 0f, 0f, paint); canvas.restore();
แต่ให้แสดงตัวอย่างก่อนหน้านี้ดังนี้
Kotlin
paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) // At draw time: canvas.drawPath(circlePath, mPaint)
Java
// One time init: paint.setShader(new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP)); // At draw time: canvas.drawPath(circlePath, mPaint);
การอัปโหลดบิตแมป
Android แสดงบิตแมปเป็นพื้นผิว OpenGL และครั้งแรกที่บิตแมปคือ แสดงในเฟรมแล้วจึงอัปโหลดไปยัง GPU คุณสามารถดูข้อมูลนี้ใน Systrace เป็น การอัปโหลดพื้นผิว(รหัส) กว้าง x สูง ซึ่งอาจใช้เวลาหลายมิลลิวินาที แสดงในรูปที่ 2 แต่ต้องแสดงภาพด้วย GPU
หากใช้เวลานาน ให้ตรวจสอบค่าความกว้างและความสูงใน การติดตาม ตรวจสอบให้แน่ใจว่าบิตแมปที่แสดงไม่ได้มีขนาดใหญ่กว่า บริเวณหน้าจอที่กำลังแสดง เพราะจะทำให้เสียเวลาอัปโหลด ความทรงจำ โดยทั่วไปแล้ว ไลบรารีการโหลดบิตแมปจะให้วิธีการในการส่งคำขอ บิตแมปที่มีขนาดเหมาะสม
ใน Android 7.0 โค้ดที่โหลดบิตแมปซึ่งมักดำเนินการโดยไลบรารีสามารถเรียกใช้
prepareToDraw()
ถึง
ทริกเกอร์การอัปโหลดล่วงหน้าก่อนที่จะจำเป็นต้องใช้ วิธีนี้จะทำให้การอัปโหลดเกิดขึ้นตั้งแต่เนิ่นๆ
ขณะที่ RenderThread
ไม่มีการใช้งาน ซึ่งทำได้หลังจากถอดรหัสหรือเมื่อเชื่อมโยง
บิตแมปไปยังมุมมอง ตราบใดที่คุณทราบบิตแมป ตามหลักการแล้ว การโหลดบิตแมป
ไลบรารีนี้ช่วยคุณได้ แต่ถ้าคุณจัดการ หรือต้องการดูแล
อย่ากดการอัปโหลดบนอุปกรณ์ที่ใหม่กว่า คุณสามารถโทรหา prepareToDraw()
ด้วยตัวเองได้
โค้ด
![แอปหนึ่งใช้เวลาจำนวนมากใน
เฟรมการอัปโหลดบิตแมปขนาดใหญ่](https://developer.android.com/static/topic/performance/vitals/images/texture-upload.png?authuser=19&hl=th)
prepareToDraw()
ความล่าช้าในการกำหนดเวลาของชุดข้อความ
เครื่องจัดตารางเวลาเทรดเป็นส่วนหนึ่งของระบบปฏิบัติการ Android ที่รับผิดชอบ ซึ่งกำหนดว่าเทรดใดในระบบจะต้องทำงาน เมื่อใด และใช้เวลาเท่าไร
บางครั้งการกระตุกอาจเกิดขึ้นเนื่องจากเทรด UI ของแอปถูกบล็อกหรือไม่ทำงาน Systrace ใช้สีต่างๆ ดังที่แสดงในรูปที่ 3 เพื่อระบุเมื่อชุดข้อความ นอนหลับ (สีเทา) เรียกใช้ได้ (สีฟ้า: ทำงานได้ แต่ไม่ได้เลือกโดย เครื่องจัดตารางเวลาที่จะเรียกใช้) กำลังทำงานอยู่ (สีเขียว) หรืออยู่ในการนอนหลับอย่างไม่ขาดตอน (สีแดงหรือสีส้ม) วิธีนี้เป็นประโยชน์อย่างยิ่งในการแก้ปัญหาการกระตุกที่ เกิดจากความล่าช้าในการจัดตารางเวลาของชุดข้อความ
![ไฮไลต์ช่วงเวลาที่ชุดข้อความ UI
นอนหลับ](https://developer.android.com/static/topic/performance/vitals/images/thread-sleep.png?authuser=19&hl=th)
บ่อยครั้งที่การเรียกใช้ Binder คือกลไกการสื่อสารระหว่างโปรเซส (IPC) ใน Android - ทำให้การดำเนินการของแอปหยุดชั่วคราวเป็นเวลานาน ใน Android เวอร์ชันหลังๆ เป็นหนึ่งในสาเหตุที่พบบ่อยที่สุดที่ทำให้เธรด UI หยุดทำงาน โดยทั่วไป วิธีแก้ไขคือให้หลีกเลี่ยงการเรียกฟังก์ชันที่เรียกไฟล์ Binder หากเป็น ให้แคชค่าหรือย้ายงานไปยังชุดข้อความเบื้องหลัง เป็นฐานของโค้ด ใหญ่ขึ้น คุณอาจเพิ่มการเรียก Binder โดยไม่ตั้งใจด้วยการเรียกใช้ Binder ระดับต่ำ ถ้าคุณไม่ระวังตัว แต่คุณสามารถค้นหาและแก้ไขได้ด้วยการติดตาม
หากคุณมีธุรกรรม Binder คุณสามารถบันทึกสแต็กการโทรด้วย
คำสั่ง adb
ต่อไปนี้
$ adb shell am trace-ipc start
… use the app - scroll/animate ...
$ adb shell am trace-ipc stop --dump-file /data/local/tmp/ipc-trace.txt
$ adb pull /data/local/tmp/ipc-trace.txt
บางครั้งการโทรที่ดูเหมือนไม่เป็นอันตราย เช่น
getRefreshRate()
สามารถ
ทริกเกอร์ธุรกรรม Binder และทำให้เกิดปัญหาใหญ่เมื่อถูกเรียก
เป็นประจำ การติดตามเป็นระยะๆ จะช่วยให้คุณพบและแก้ไขปัญหาเหล่านี้ได้
ปรากฏขึ้น
![แสดงเธรด UI ที่เข้าสู่โหมดสลีปเนื่องจากแฟ้ม
การทำธุรกรรมในการขนย้ายรถบ้าน โฟกัสที่ตรรกะการเชื่อมโยงและใช้การติดตาม IP เพื่อ
ติดตามและลบการเรียก Binder](https://developer.android.com/static/topic/performance/vitals/images/rv-fling.png?authuser=19&hl=th)
trace-ipc
เพื่อติดตามและนำการเรียก Binder ออกหากไม่เห็นกิจกรรม Binder แต่ยังไม่เห็นเธรด UI ที่ทำงานอยู่ ตรวจสอบว่าคุณไม่ได้กำลังรอการล็อกหรือการดำเนินการอื่นจากเทรดอื่น โดยปกติแล้ว เทรด UI ไม่จำเป็นต้องรอผลลัพธ์จากเทรดอื่นๆ ส่วนชุดข้อความอื่นๆ จะต้องโพสต์ข้อมูลไว้ในชุดข้อความ
การจัดสรรออบเจ็กต์และการรวบรวมขยะ
การจัดสรรออบเจ็กต์และการรวบรวมขยะ (GC) เป็นปัญหาน้อยกว่าอย่างมาก ตั้งแต่ ART เปิดตัว ART เป็นรันไทม์เริ่มต้นใน Android 5.0 ในการลดน้ำหนักชุดข้อความด้วยงานเพิ่มเติมนี้ คุณสามารถจัดสรรได้ เพื่อตอบสนองต่อเหตุการณ์ที่เกิดขึ้นไม่บ่อยนัก ซึ่งไม่ได้เกิดขึ้นหลายครั้งต่อวินาที เช่น ผู้ใช้แตะปุ่ม แต่โปรดทราบว่าการจัดสรรแต่ละครั้งจะมีค่าใช้จ่าย ถ้า อยู่ในลูปที่เรียกใช้บ่อยๆ ดังนั้น โปรดหลีกเลี่ยงการจัดสรร เพื่อลดภาระของ GC
Systrace จะแสดงให้คุณทราบว่า GC ทำงานบ่อยหรือไม่ และหน่วยความจำของ Android เครื่องมือสร้างโปรไฟล์จะแสดงให้เห็นว่าการจัดสรร มาจากไหน หากคุณหลีกเลี่ยงการจัดสรรเมื่อเป็นไปได้ โดยเฉพาะในจำนวนที่จำกัด การวนซ้ำ คุณก็มีแนวโน้มที่จะพบปัญหาน้อยลง
![แสดง GC 94 มิลลิวินาทีบน HeapTaskDaemon](https://developer.android.com/static/topic/performance/vitals/images/gc.png?authuser=19&hl=th)
ใน Android เวอร์ชันล่าสุด โดยทั่วไปแล้ว GC จะทำงานบนเทรดเบื้องหลังที่ชื่อ HeapTaskDaemon การจัดสรรปริมาณมากอาจหมายความว่ามี CPU มากขึ้น ทรัพยากรที่ใช้ใน GC ดังที่แสดงในรูปที่ 5
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- เปรียบเทียบแอป
- ภาพรวมของการวัดประสิทธิภาพแอป
- แนวทางปฏิบัติแนะนำสำหรับการเพิ่มประสิทธิภาพแอป