วิธีที่คุณจัดการลําดับชั้นของออบเจ็กต์ View
อาจส่งผลต่อประสิทธิภาพของแอปอย่างมาก หน้านี้จะอธิบายวิธีประเมินว่าลําดับชั้นของมุมมองทําให้แอปช้าลงหรือไม่ และเสนอกลยุทธ์บางอย่างในการแก้ไขปัญหาที่อาจเกิดขึ้น
หน้านี้มุ่งเน้นที่การปรับปรุงเลย์เอาต์แบบView
โปรดดูข้อมูลเกี่ยวกับการปรับปรุงประสิทธิภาพของ Jetpack Compose ในประสิทธิภาพของ Jetpack Compose
เลย์เอาต์และวัดประสิทธิภาพ
ไปป์ไลน์การแสดงผลประกอบด้วยระยะเลย์เอาต์และวัด ซึ่งในระหว่างนี้ระบบจะจัดวางรายการที่เกี่ยวข้องในลําดับชั้นของมุมมองอย่างเหมาะสม ส่วนการวัดของระยะนี้จะเป็นตัวกำหนดขนาดและขอบเขตของออบเจ็กต์ View
ส่วนเลย์เอาต์จะกำหนดตำแหน่งบนหน้าจอเพื่อกำหนดตำแหน่งออบเจ็กต์ View
ระยะของไปป์ไลน์ทั้ง 2 ระยะนี้จะมีต้นทุนเล็กน้อยต่อยอดดูหรือเลย์เอาต์ที่ประมวลผล ส่วนใหญ่แล้ว ค่าใช้จ่ายนี้จะน้อยมากและไม่ส่งผลต่อประสิทธิภาพอย่างเห็นได้ชัด อย่างไรก็ตาม อาจมีจำนวนมากกว่านี้เมื่อแอปเพิ่มหรือนำออบเจ็กต์ View
ออก เช่น เมื่อออบเจ็กต์ RecyclerView
รีไซเคิลหรือนำกลับมาใช้ใหม่ ต้นทุนอาจสูงขึ้นหากออบเจ็กต์ View
ต้องพิจารณาปรับขนาดเพื่อให้เป็นไปตามข้อจำกัด เช่น หากแอปเรียกใช้ SetText()
ในออบเจ็กต์ View
ที่ตัดข้อความ View
อาจต้องปรับขนาด
หากกรณีเช่นนี้ใช้เวลานานเกินไป อาจทำให้เฟรมแสดงผลภายใน 16 มิลลิวินาทีที่อนุญาตไม่ได้ ซึ่งอาจทำให้เฟรมตกหล่นและทำให้ภาพเคลื่อนไหวกระตุก
เนื่องจากคุณไม่สามารถย้ายการดำเนินการเหล่านี้ไปยังเธรดงานได้ แอปของคุณจึงต้องประมวลผลการดำเนินการเหล่านั้นในเธรดหลัก คุณจึงควรเพิ่มประสิทธิภาพการดำเนินการดังกล่าวเพื่อให้ใช้เวลาน้อยที่สุด
จัดการเลย์เอาต์ที่ซับซ้อน
เลย์เอาต์ของ Android ช่วยให้คุณฝังออบเจ็กต์ UI ในลําดับชั้นของมุมมองได้ การซ้อนนี้ยังอาจทำให้เสียค่าใช้จ่ายในเลย์เอาต์ด้วย เมื่อแอปประมวลผลออบเจ็กต์สำหรับเลย์เอาต์ แอปก็จะดำเนินการแบบเดียวกันกับออบเจ็กต์ย่อยทั้งหมดของเลย์เอาต์ด้วย
สำหรับเลย์เอาต์ที่ซับซ้อน บางครั้งระบบอาจเรียกเก็บเงินเฉพาะเมื่อคำนวณเลย์เอาต์เป็นครั้งแรกเท่านั้น ตัวอย่างเช่น เมื่อแอปนำรายการที่ซับซ้อนในออบเจ็กต์ RecyclerView
กลับมาใช้ใหม่ ระบบจะต้องจัดวางออบเจ็กต์ทั้งหมด อีกตัวอย่างหนึ่งคือ การเปลี่ยนแปลงเล็กน้อยอาจส่งผลต่อออบเจ็กต์ระดับบนสุดจนกว่าจะถึงออบเจ็กต์ที่ไม่ส่งผลต่อขนาดของออบเจ็กต์หลัก
สาเหตุที่พบบ่อยที่ทำให้เลย์เอาต์ใช้เวลานานคือเมื่อลําดับชั้นของออบเจ็กต์ View
ซ้อนกันอยู่ ออบเจ็กต์เลย์เอาต์ที่ฝังอยู่แต่ละรายการจะเพิ่มต้นทุนให้กับระยะการวางเลย์เอาต์ ยิ่งคุณทำให้ลำดับชั้นเรียบง่ายขึ้นเท่าไหร่ ขั้นตอนการเลย์เอาต์ก็จะใช้เวลาน้อยลงเท่านั้น
เราขอแนะนำให้ใช้เครื่องมือแก้ไขเลย์เอาต์เพื่อสร้าง ConstraintLayout
แทน RelativeLayout
หรือ LinearLayout
เนื่องจากโดยทั่วไปแล้วเครื่องมือนี้มีประสิทธิภาพมากกว่าและลดการฝังเลย์เอาต์ อย่างไรก็ตาม สำหรับเลย์เอาต์แบบง่ายที่ทำได้โดยใช้ FrameLayout
เราขอแนะนำให้ใช้ FrameLayout
หากใช้คลาส RelativeLayout
คุณอาจได้ผลลัพธ์เดียวกันนี้ในต้นทุนที่ต่ำลงได้โดยใช้มุมมอง LinearLayout
แบบฝังซึ่งไม่มีน้ำหนักแทน อย่างไรก็ตาม หากใช้มุมมอง LinearLayout
แบบถ่วงน้ำหนักที่ฝังอยู่ ต้นทุนเลย์เอาต์จะสูงกว่ามาก เนื่องจากต้องใช้การผ่านเลย์เอาต์หลายครั้งตามที่อธิบายไว้ในส่วนถัดไป
นอกจากนี้เราขอแนะนำให้ใช้ RecyclerView
แทน ListView
เนื่องจากวิธีนี้นำเลย์เอาต์ของแต่ละรายการในลิสต์มาใช้ใหม่ ซึ่งทั้ง 2 อย่างมีประสิทธิภาพมากกว่าและช่วยปรับปรุงประสิทธิภาพการเลื่อนได้
การเก็บภาษีซ้อน
โดยทั่วไปแล้ว เฟรมเวิร์กจะเรียกใช้ระยะการวางเลย์เอาต์หรือระยะการวัดในรอบเดียว อย่างไรก็ตาม ในบางกรณีที่เลย์เอาต์มีความซับซ้อน เฟรมเวิร์กอาจต้องทําซ้ำหลายครั้งในส่วนลําดับชั้นที่ต้องใช้หลายรอบในการแก้ไขก่อนที่จะวางองค์ประกอบในท้ายที่สุด การที่คุณต้องทำการวนซ้ำเลย์เอาต์และการวัดมากกว่า 1 ครั้งเรียกว่าการเรียกเก็บภาษีซ้ำ
ตัวอย่างเช่น เมื่อคุณใช้คอนเทนเนอร์ RelativeLayout
ซึ่งให้คุณจัดตำแหน่งออบเจ็กต์ View
โดยอิงตามตำแหน่งของออบเจ็กต์ View
อื่นๆ เฟรมเวิร์กจะดำเนินการตามลำดับต่อไปนี้
- ดำเนินการผ่านเลย์เอาต์และการวัด ซึ่งเฟรมเวิร์กจะคำนวณตำแหน่งและขนาดของออบเจ็กต์ย่อยแต่ละรายการตามคำขอของออบเจ็กต์ย่อยแต่ละรายการ
- ใช้ข้อมูลนี้โดยพิจารณาถึงน้ำหนักของวัตถุเพื่อหาตําแหน่งที่เหมาะสมของมุมมองที่เกี่ยวข้อง
- ดำเนินการวางเลย์เอาต์ครั้งที่ 2 เพื่อกำหนดตำแหน่งของวัตถุให้เสร็จสมบูรณ์
- ย้ายไปยังขั้นตอนถัดไปของกระบวนการแสดงผล
ยิ่งลำดับชั้นของการแสดงผลหลายระดับมากเท่าไร ก็ยิ่งมีโอกาสที่จะถูกลงโทษมากขึ้นเท่านั้น
ดังที่ได้กล่าวไปก่อนหน้านี้ โดยทั่วไปแล้ว ConstraintLayout
จะมีประสิทธิภาพมากกว่าเลย์เอาต์อื่นๆ ยกเว้น FrameLayout
มีโอกาสน้อยที่จะมีการผ่านเลย์เอาต์หลายครั้ง และในหลายกรณีทำให้ไม่จำเป็นต้องซ้อนเลย์เอาต์ออก
คอนเทนเนอร์ที่ไม่ใช่ RelativeLayout
อาจเพิ่มการเสียภาษีซ้ำด้วย เช่น
- มุมมอง
LinearLayout
อาจส่งผลให้มีการจัดวางและวัดผล 2 ครั้งหากคุณทำให้เป็นแบบแนวนอน การวางเลย์เอาต์และการวัดผล 2 ครั้งอาจเกิดขึ้นในแนวตั้งด้วยหากคุณเพิ่มmeasureWithLargestChild
ซึ่งในกรณีนี้เฟรมเวิร์กอาจต้องทำการวัดผลครั้งที่ 2 เพื่อแก้ไขขนาดที่เหมาะสมของออบเจ็กต์ GridLayout
ยังอนุญาตการกำหนดตำแหน่งแบบสัมพัทธ์ แต่โดยปกติจะหลีกเลี่ยงการเก็บภาษีซ้อนด้วยการประมวลผลความสัมพันธ์ตามตำแหน่งระหว่างมุมมองย่อยล่วงหน้า อย่างไรก็ตาม หากเลย์เอาต์ใช้น้ำหนักหรือกรอกข้อมูลด้วยคลาสGravity
ประโยชน์ของการเตรียมข้อมูลล่วงหน้าจะหายไป และเฟรมเวิร์กอาจต้องทําหลายรอบหากคอนเทนเนอร์เป็นRelativeLayout
การแสดงผลและวัดผลหลายรอบไม่ได้เป็นภาระต่อประสิทธิภาพเสมอไป อย่างไรก็ตาม สิ่งเหล่านี้อาจกลายเป็นภาระหากอยู่ในตำแหน่งที่ไม่ถูกต้อง โปรดระมัดระวังสถานการณ์ที่เงื่อนไขข้อใดข้อหนึ่งต่อไปนี้มีผลกับคอนเทนเนอร์
- ซึ่งเป็นองค์ประกอบรูทในลําดับชั้นของมุมมอง
- โดยมีลําดับชั้นมุมมองแบบลําลึกอยู่ด้านล่าง
- อินสแตนซ์ขององค์ประกอบนี้ปรากฏบนหน้าจอหลายรายการ ซึ่งคล้ายกับองค์ประกอบย่อยในออบเจ็กต์
ListView
วิเคราะห์ปัญหาเกี่ยวกับลําดับชั้นของมุมมอง
ประสิทธิภาพของเลย์เอาต์เป็นปัญหาที่ซับซ้อนซึ่งพบได้หลายแง่มุม เครื่องมือต่อไปนี้จะช่วยคุณระบุจุดคอขวดด้านประสิทธิภาพ เครื่องมือบางอย่างให้ข้อมูลที่แน่นอนน้อยกว่า แต่ก็ให้คำแนะนำที่เป็นประโยชน์ได้
Perfetto
Perfetto เป็นเครื่องมือที่ให้ข้อมูลเกี่ยวกับประสิทธิภาพ คุณเปิดการติดตาม Android ใน UI ของ Perfetto ได้
การแสดงผล GPU ตามโปรไฟล์
เครื่องมือการแสดงผล GPU ของโปรไฟล์ในอุปกรณ์ซึ่งใช้งานได้ในอุปกรณ์ที่ใช้ Android 6.0 (API ระดับ 23) ขึ้นไปจะช่วยให้ข้อมูลที่เป็นรูปธรรมเกี่ยวกับจุดคอขวดด้านประสิทธิภาพได้ เครื่องมือนี้ช่วยให้คุณเห็นระยะเวลาที่ขั้นตอนการจัดวางและวัดผลเฟรมการแสดงผลแต่ละเฟรม ข้อมูลนี้จะช่วยคุณวิเคราะห์ปัญหาด้านประสิทธิภาพรันไทม์และช่วยคุณระบุปัญหาเกี่ยวกับเลย์เอาต์และการวัดผลที่คุณต้องแก้ไข
ในการแสดงข้อมูลที่บันทึกในรูปแบบกราฟิก การแสดงผล GPU ของโปรไฟล์จะใช้สีน้ำเงินเพื่อแสดงเวลาของเลย์เอาต์ ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้เครื่องมือนี้ได้ที่ความเร็วในการแสดงผล GPU ของโปรไฟล์
ขุยผ้า
เครื่องมือ Lint ของ Android Studio จะช่วยให้คุณทราบว่าลำดับชั้นการแสดงผลนั้นไร้ประสิทธิภาพ หากต้องการใช้เครื่องมือนี้ ให้เลือกวิเคราะห์ > ตรวจสอบโค้ด ดังที่แสดงในรูปที่ 1
ข้อมูลเกี่ยวกับรายการเลย์เอาต์ต่างๆ จะปรากฏในส่วน Android > Lint > ประสิทธิภาพ หากต้องการดูรายละเอียดเพิ่มเติม ให้คลิกแต่ละรายการเพื่อขยายและแสดงข้อมูลเพิ่มเติมในแผงทางด้านขวาของหน้าจอ รูปที่ 2 แสดงตัวอย่างข้อมูลที่ขยาย
การคลิกรายการจะแสดงปัญหาที่เกี่ยวข้องกับรายการนั้นในแผงทางด้านขวา
หากต้องการทําความเข้าใจหัวข้อและปัญหาที่เฉพาะเจาะจงมากขึ้นในเรื่องนี้ โปรดดูเอกสารประกอบของ Lint
เครื่องมือตรวจสอบเลย์เอาต์
เครื่องมือผู้ตรวจสอบเลย์เอาต์ของ Android Studio จะแสดงภาพลําดับชั้นของมุมมองแอป ซึ่งถือเป็นวิธีที่ดีในการไปยังส่วนต่างๆ ของลําดับชั้นของแอป เนื่องจากแสดงภาพเชนหลักของมุมมองหนึ่งๆ อย่างชัดเจน และให้คุณตรวจสอบเลย์เอาต์ที่แอปสร้างขึ้นได้
มุมมองที่เครื่องมือตรวจสอบเลย์เอาต์แสดงยังช่วยระบุปัญหาด้านประสิทธิภาพที่เกิดจากภาษีซ้อนได้ด้วย นอกจากนี้ยังช่วยให้ระบุเครือข่ายแบบ Deep Link ของเลย์เอาต์ที่ซ้อนกัน หรือพื้นที่เลย์เอาต์ที่มีรายการย่อยๆ ซ้อนกันอยู่เป็นจำนวนมาก ซึ่งอาจเป็นแหล่งที่มาของต้นทุนด้านประสิทธิภาพ ในกรณีเหล่านี้ ขั้นตอนการเลย์เอาต์และการวัดอาจมีค่าใช้จ่ายสูงและทำให้เกิดปัญหาด้านประสิทธิภาพ
ดูข้อมูลเพิ่มเติมได้ที่แก้ไขข้อบกพร่องของเลย์เอาต์ด้วยเครื่องมือตรวจสอบเลย์เอาต์และการตรวจสอบเลย์เอาต์
แก้ปัญหาเกี่ยวกับลําดับชั้นของมุมมอง
แนวคิดพื้นฐานในการแก้ปัญหาด้านประสิทธิภาพที่เกิดจากลําดับชั้นของมุมมองอาจทําได้ยาก การป้องกันไม่ให้ลำดับชั้นของมุมมองมีการลงโทษด้านประสิทธิภาพประกอบด้วยการทำให้ลำดับชั้นของมุมมองแบนราบและลดการเก็บภาษีซ้ำ ส่วนนี้จะกล่าวถึงกลยุทธ์ในการบรรลุเป้าหมายเหล่านี้
นำเลย์เอาต์ที่ซ้อนกันที่ซ้ำซ้อนออก
ConstraintLayout
เป็นไลบรารี Jetpack ที่มีกลไกต่างๆ มากมายสำหรับการจัดตำแหน่งมุมมองภายในเลย์เอาต์ วิธีนี้ช่วยลดความจำเป็นในการฝัง ConstaintLayout
รายการหนึ่งและช่วยลดความซับซ้อนของลําดับชั้นมุมมอง โดยทั่วไปแล้ว การยุบลําดับชั้นโดยใช้ ConstraintLayout
จะง่ายกว่าเมื่อเทียบกับเลย์เอาต์ประเภทอื่นๆ
นักพัฒนาแอปมักใช้เลย์เอาต์ที่ฝังมากกว่าที่จำเป็น เช่น คอนเทนเนอร์ RelativeLayout
อาจมีรายการย่อยรายการเดียวที่เป็นคอนเทนเนอร์ RelativeLayout
ด้วย การซ้อนนี้ซ้ำซ้อนและเพิ่มต้นทุนที่ไม่จำเป็นให้กับลําดับชั้นของมุมมอง Lint จะแจ้งปัญหานี้ให้คุณได้ ซึ่งจะลดเวลาในการแก้ไขข้อบกพร่อง
ใช้การผสานหรือรวม
สาเหตุที่พบบ่อยของเลย์เอาต์ที่ฝังซ้อนกันซ้ำซ้อนคือแท็ก
<include>
เช่น คุณอาจกําหนดเลย์เอาต์ที่นํามาใช้ซ้ำได้ดังนี้
<LinearLayout> <!-- some stuff here --> </LinearLayout>
จากนั้นคุณอาจเพิ่มแท็ก <include>
เพื่อเพิ่มรายการต่อไปนี้ลงในคอนเทนเนอร์หลัก
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/app_bg" android:gravity="center_horizontal"> <include layout="@layout/titlebar"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hello" android:padding="10dp" /> ... </LinearLayout>
การรวมก่อนหน้านี้จะฝังเลย์เอาต์แรกไว้ในเลย์เอาต์ที่ 2 โดยไม่จำเป็น
แท็ก
<merge>
จะช่วยป้องกันปัญหานี้ได้ ดูข้อมูลเกี่ยวกับแท็กนี้ได้ที่หัวข้อใช้แท็ก <merge>
ใช้เลย์เอาต์ที่ถูกกว่า
คุณอาจปรับรูปแบบเลย์เอาต์ที่มีอยู่เพื่อไม่ให้มีเลย์เอาต์ที่ซ้ำกันไม่ได้ ในบางกรณี วิธีแก้ไขเพียงอย่างเดียวคือการปรับลำดับชั้นโดยการเปลี่ยนไปใช้เลย์เอาต์ประเภทที่ต่างออกไปโดยสิ้นเชิง
เช่น คุณอาจพบว่า
TableLayout
มีฟังก์ชันการทำงานเหมือนกับเลย์เอาต์ที่ซับซ้อนขึ้นโดยมีทรัพยากร Dependency ของตำแหน่งหลายรายการ ไลบรารี Jetpack
ConstraintLayout
มีฟังก์ชันการทำงานคล้ายกับ RelativeLayout
และยังมีฟีเจอร์เพิ่มเติมเพื่อช่วยสร้างเลย์เอาต์ที่ราบเรียบและมีประสิทธิภาพมากขึ้น