ลำดับชั้นของประสิทธิภาพและมุมมอง

วิธีที่คุณจัดการลําดับชั้นของออบเจ็กต์ 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 อื่นๆ เฟรมเวิร์กจะดำเนินการตามลำดับต่อไปนี้

  1. ดำเนินการผ่านเลย์เอาต์และการวัด ซึ่งเฟรมเวิร์กจะคำนวณตำแหน่งและขนาดของออบเจ็กต์ย่อยแต่ละรายการตามคำขอของออบเจ็กต์ย่อยแต่ละรายการ
  2. ใช้ข้อมูลนี้โดยพิจารณาถึงน้ำหนักของวัตถุเพื่อหาตําแหน่งที่เหมาะสมของมุมมองที่เกี่ยวข้อง
  3. ดำเนินการวางเลย์เอาต์ครั้งที่ 2 เพื่อกำหนดตำแหน่งของวัตถุให้เสร็จสมบูรณ์
  4. ย้ายไปยังขั้นตอนถัดไปของกระบวนการแสดงผล

ยิ่งลำดับชั้นของการแสดงผลหลายระดับมากเท่าไร ก็ยิ่งมีโอกาสที่จะถูกลงโทษมากขึ้นเท่านั้น

ดังที่ได้กล่าวไปก่อนหน้านี้ โดยทั่วไปแล้ว 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

รูปที่ 1 เลือกตรวจสอบโค้ดใน Android Studio

ข้อมูลเกี่ยวกับรายการเลย์เอาต์ต่างๆ จะปรากฏในส่วน Android > Lint > ประสิทธิภาพ หากต้องการดูรายละเอียดเพิ่มเติม ให้คลิกแต่ละรายการเพื่อขยายและแสดงข้อมูลเพิ่มเติมในแผงทางด้านขวาของหน้าจอ รูปที่ 2 แสดงตัวอย่างข้อมูลที่ขยาย

รูปที่ 2 การดูข้อมูลเกี่ยวกับปัญหาที่เฉพาะเจาะจงที่เครื่องมือ Lint ระบุ

การคลิกรายการจะแสดงปัญหาที่เกี่ยวข้องกับรายการนั้นในแผงทางด้านขวา

หากต้องการทําความเข้าใจหัวข้อและปัญหาที่เฉพาะเจาะจงมากขึ้นในเรื่องนี้ โปรดดูเอกสารประกอบของ 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 และยังมีฟีเจอร์เพิ่มเติมเพื่อช่วยสร้างเลย์เอาต์ที่ราบเรียบและมีประสิทธิภาพมากขึ้น