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

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

เปิดใช้การข้ามที่มีประสิทธิภาพ

คุณควรลองเปิดใช้โหมดการข้ามขั้นสูงก่อน โหมดการข้ามแบบเข้มงวดช่วยให้ข้าม 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

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

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

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

@Immutable
data class Snack(
…
)

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

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

พิจารณาคอมโพสิเบิลที่มีพารามิเตอร์ประเภท List<Snack> ดังนี้

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

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

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

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

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

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

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

คอลเล็กชันแบบคงที่

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

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

Wrapper

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

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

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

@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 ของคอมไพเลอร์

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

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

หลายโมดูล

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

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

โซลูชัน

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

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

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

เนื้อหาคอมโพสพอยต์บางรายการไม่ควรข้ามได้

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

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

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

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