แก้ปัญหาความเสถียร

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

เปิดใช้การข้ามที่รวดเร็ว

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

ดูข้อมูลเพิ่มเติมได้ที่การข้ามอย่างรวดเร็ว

ทำให้ชั้นเรียนเปลี่ยนแปลงไม่ได้

นอกจากนี้ คุณยังลองทำให้คลาสที่ไม่เสถียรเปลี่ยนแปลงไม่ได้โดยสมบูรณ์ได้ด้วย

  • เปลี่ยนแปลงไม่ได้: ระบุประเภทที่ค่าของพร็อพเพอร์ตี้ใดๆ จะเปลี่ยนแปลงไม่ได้ หลังจากสร้างอินสแตนซ์ของประเภทนั้น และเมธอดทั้งหมดจะ โปร่งใสโดยอ้างอิง
    • ตรวจสอบว่าพร็อพเพอร์ตี้ทั้งหมดของคลาสเป็นทั้ง val แทนที่จะเป็น var และเป็นประเภทที่เปลี่ยนแปลงไม่ได้
    • ประเภทดั้งเดิม เช่น String, Int และ Float จะเปลี่ยนแปลงไม่ได้เสมอ
    • หากทำไม่ได้ คุณต้องใช้สถานะ Compose สำหรับพร็อพเพอร์ตี้ที่เปลี่ยนแปลงได้
  • เสถียร: ระบุประเภทที่เปลี่ยนแปลงได้ รันไทม์ของ Compose จะไม่ทราบว่าเมื่อใดที่พร็อพเพอร์ตี้สาธารณะหรือลักษณะการทำงานของเมธอดของประเภทใดๆ จะให้ผลลัพธ์ที่แตกต่างจากการเรียกใช้ก่อนหน้า

คอลเล็กชันที่เปลี่ยนแปลงไม่ได้

สาเหตุที่พบบ่อยที่ทำให้ Compose ถือว่าคลาสไม่เสถียรคือคอลเล็กชัน ดังที่ระบุไว้ในหน้าวินิจฉัยปัญหาด้านความเสถียร คอมไพเลอร์ Compose ไม่สามารถมั่นใจได้อย่างเต็มที่ว่าคอลเล็กชัน เช่น List, Map และ Set จะ เปลี่ยนแปลงไม่ได้จริงๆ จึงทําเครื่องหมายว่าไม่เสถียร

คุณสามารถใช้คอลเล็กชันที่ไม่เปลี่ยนแปลงเพื่อแก้ไขปัญหานี้ได้ คอมไพเลอร์ Compose รองรับ Kotlinx Immutable Collections รับประกันได้ว่าคอลเล็กชันเหล่านี้ จะเปลี่ยนแปลงไม่ได้ และคอมไพเลอร์ Compose จะถือว่าคอลเล็กชันเหล่านี้ เป็นเช่นนั้น ไลบรารียังอยู่ในเวอร์ชันอัลฟ่า ดังนั้นโปรดเตรียมพร้อมรับการเปลี่ยนแปลงที่อาจเกิดขึ้นกับ API

พิจารณาคลาสที่ไม่เสถียรนี้อีกครั้งจากคำแนะนำวินิจฉัยปัญหา ความเสถียร

unstable class Snack {
  
  unstable val tags: Set<String>
  
}

คุณทำให้ tags เสถียรได้โดยใช้คอลเล็กชันที่ไม่เปลี่ยนแปลง ในชั้นเรียน ให้เปลี่ยน ประเภทของ tags เป็น ImmutableSet<String> โดยทำดังนี้

data class Snack{
    
    val tags: ImmutableSet<String> = persistentSetOf()
    
}

หลังจากนั้น พารามิเตอร์ทั้งหมดของคลาสจะเปลี่ยนแปลงไม่ได้ และคอมไพเลอร์ Compose จะทําเครื่องหมายคลาสเป็นแบบเสถียร

ใส่คำอธิบายประกอบด้วย Stable หรือ Immutable

วิธีหนึ่งในการแก้ปัญหาความเสถียรคือการใส่คำอธิบายประกอบคลาสที่ไม่เสถียรด้วย @Stable หรือ @Immutable

การใส่คำอธิบายประกอบในคลาสเป็นการลบล้างสิ่งที่คอมไพเลอร์จะอนุมานเกี่ยวกับคลาสของคุณ ซึ่งคล้ายกับ!! Operator ใน Kotlin คุณควรระมัดระวังเป็นอย่างยิ่ง เกี่ยวกับวิธีใช้คำอธิบายประกอบเหล่านี้ การลบล้างลักษณะการทำงานของคอมไพเลอร์ อาจทำให้คุณพบข้อบกพร่องที่ไม่คาดคิด เช่น Composable ไม่ทำการรวมกันใหม่เมื่อ คุณคาดหวังให้เป็นเช่นนั้น

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

ข้อมูลโค้ดต่อไปนี้เป็นตัวอย่างขั้นต่ำของคลาสข้อมูลที่มีคำอธิบายประกอบเป็น เปลี่ยนแปลงไม่ได้

@Immutable
data class Snack(

)

ไม่ว่าคุณจะใช้คำอธิบายประกอบ @Immutable หรือ @Stable คอมไพเลอร์ Compose จะทำเครื่องหมายคลาส Snack ว่าเป็นคลาสที่เสถียร

ชั้นเรียนที่มีคำอธิบายประกอบในคอลเล็กชัน

พิจารณา Composable ที่มีพารามิเตอร์ประเภท List<Snack>

restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  
  unstable snacks: List<Snack>
  
)

แม้ว่าคุณจะใส่คำอธิบายประกอบ Snack ด้วย @Immutable แต่คอมไพเลอร์ Compose จะยังคงทำเครื่องหมายพารามิเตอร์ snacks ใน HighlightedSnacks ว่าไม่เสถียร

พารามิเตอร์ประสบปัญหาเดียวกันกับคลาสเมื่อพูดถึงประเภทคอลเล็กชัน คอมไพเลอร์ Compose จะทำเครื่องหมายพารามิเตอร์ประเภท List เป็นไม่เสถียรเสมอ แม้ว่าจะเป็นคอลเล็กชันของประเภทที่เสถียรก็ตาม

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

คุณแก้ปัญหาคอลเล็กชันที่ไม่เสถียรได้หลายวิธี ส่วนย่อยต่อไปนี้จะอธิบายแนวทางที่แตกต่างกันเหล่านี้

ไฟล์การกำหนดค่า

หากคุณยินดีที่จะปฏิบัติตามสัญญาความเสถียรในโค้ดเบส คุณก็เลือกใช้คอลเล็กชัน Kotlin เป็นคอลเล็กชันที่เสถียรได้โดยเพิ่ม kotlin.collections.* ลงในไฟล์การกำหนดค่าความเสถียร

คอลเล็กชันที่เปลี่ยนแปลงไม่ได้

หากต้องการความปลอดภัยของความไม่เปลี่ยนแปลงในเวลาคอมไพล์ คุณสามารถใช้คอลเล็กชัน kotlinx immutable แทน List ได้

@Composable
private fun HighlightedSnacks(
    
    snacks: ImmutableList<Snack>,
    
)

Wrapper

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

@Immutable
data class SnackCollection(
   val snacks: List<Snack>
)

จากนั้นคุณจะใช้ค่านี้เป็นประเภทของพารามิเตอร์ใน Composable ได้

@Composable
private fun HighlightedSnacks(
    index: Int,
    snacks: SnackCollection,
    onSnackClick: (Long) -> Unit,
    modifier: Modifier = Modifier
)

โซลูชัน

หลังจากใช้วิธีใดวิธีหนึ่งแล้ว ตอนนี้คอมไพเลอร์ Compose จะทําเครื่องหมาย HighlightedSnacks Composable เป็นทั้ง skippable และ restartable

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  stable index: Int
  stable snacks: ImmutableList<Snack>
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
)

ในระหว่างการจัดองค์ประกอบใหม่ ตอนนี้ Compose สามารถข้ามHighlightedSnacksได้หากไม่มีการเปลี่ยนแปลงอินพุต

ไฟล์การกำหนดค่าความเสถียร

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

ไฟล์การกำหนดค่าคือไฟล์ข้อความธรรมดาที่มี 1 คลาสต่อแถว ระบบรองรับความคิดเห็น ไวลด์การ์ดเดี่ยว และไวลด์การ์ดคู่

ตัวอย่างการกำหนดค่า

// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider my datalayer stable
com.datalayer.*
// Consider my datalayer and all submodules stable
com.datalayer.**
// Consider my generic type stable based off it's first type parameter only
com.example.GenericClass<*,_>

หากต้องการเปิดใช้ฟีเจอร์นี้ ให้ส่งเส้นทางของไฟล์กำหนดค่าไปยัง composeCompiler บล็อกตัวเลือกของปลั๊กอิน Gradle ของคอมไพเลอร์ Compose

composeCompiler {
  stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}

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

โมดูลหลายรายการ

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

หากชั้นข้อมูลอยู่ในโมดูลแยกต่างหากจากชั้น UI ซึ่งเป็นแนวทางที่แนะนํา คุณอาจพบปัญหานี้

โซลูชัน

หากต้องการแก้ปัญหานี้ คุณสามารถใช้วิธีใดวิธีหนึ่งต่อไปนี้

  1. เพิ่มคลาสลงในไฟล์การกำหนดค่าคอมไพเลอร์
  2. เปิดใช้คอมไพเลอร์ Compose ในโมดูลเลเยอร์ข้อมูล หรือติดแท็กคลาสด้วย @Stable หรือ @Immutable ตามความเหมาะสม
    • ซึ่งเกี่ยวข้องกับการเพิ่มการอ้างอิง Compose ลงในเลเยอร์ข้อมูล อย่างไรก็ตาม เป็นเพียงการขึ้นต่อกันสำหรับรันไทม์ของ Compose เท่านั้น ไม่ใช่สำหรับ Compose-UI
  3. ในโมดูล UI ให้รวมคลาสเลเยอร์ข้อมูลไว้ในคลาส Wrapper เฉพาะ UI

ปัญหาเดียวกันนี้จะเกิดขึ้นเมื่อใช้ไลบรารีภายนอกด้วย หากไม่ได้ใช้ คอมไพเลอร์ Compose

Composable บางรายการไม่ควรข้ามได้

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

มีหลายกรณีที่การข้ามไม่ได้ไม่ได้มีประโยชน์ใดๆ และอาจทำให้โค้ดดูแลรักษายาก เช่น

  • Composable ที่ไม่ได้รับการ Recompose บ่อยๆ หรือไม่ได้รับการ Recompose เลย
  • Composable ที่เรียกใช้เฉพาะ Composable ที่ข้ามได้
  • Composable ที่มีพารามิเตอร์จํานวนมากซึ่งมีการติดตั้งใช้งานเท่ากับที่ใช้ทรัพยากรมาก ในกรณีนี้ ต้นทุนในการตรวจสอบว่าพารามิเตอร์มีการเปลี่ยนแปลงหรือไม่ อาจสูงกว่าต้นทุนในการจัดองค์ประกอบใหม่ราคาถูก

เมื่อ Composable ข้ามได้ จะมีการเพิ่มค่าใช้จ่ายเล็กน้อยซึ่งอาจไม่คุ้มค่า คุณยังใส่คำอธิบายประกอบให้ Composable เป็น non-restartable ในกรณีที่คุณพิจารณาแล้วว่าการเป็น restartable มีค่าใช้จ่ายสูงกว่าคุณค่าที่ได้รับ