เมื่อพบชั้นเรียนที่ไม่เสถียรซึ่งทำให้เกิดปัญหาด้านประสิทธิภาพ คุณควรทำให้ชั้นเรียนนั้นเสถียร เอกสารนี้จะอธิบายเทคนิคต่างๆ ที่คุณใช้ได้
เปิดใช้การข้ามที่รัดกุม
คุณควรลองเปิดใช้โหมดการข้ามที่รัดกุมก่อน โหมดการข้ามที่รัดกุมช่วยให้ข้าม Composable ที่มีพารามิเตอร์ไม่เสถียรได้ และเป็นวิธีที่ง่ายที่สุดในการแก้ไขปัญหาด้านประสิทธิภาพที่เกิดจากความไม่เสถียร
ดูข้อมูลเพิ่มเติมได้ที่การข้ามที่รัดกุม
ทำให้คลาสเปลี่ยนแปลงไม่ได้
นอกจากนี้ คุณยังลองทำให้คลาสที่ไม่เสถียรเปลี่ยนแปลงไม่ได้โดยสมบูรณ์ได้ด้วย
- เปลี่ยนแปลงไม่ได้: ระบุประเภทที่ค่าของพร็อพเพอร์ตี้ใดๆ จะเปลี่ยนแปลงไม่ได้
หลังจากสร้างอินสแตนซ์ของประเภทนั้นแล้ว และเมธอดทั้งหมดจะ
โปร่งใสเชิงอ้างอิง
- ตรวจสอบว่าพร็อพเพอร์ตี้ทั้งหมดของคลาสเป็นทั้ง
valแทนที่จะเป็นvarและเป็นประเภทที่เปลี่ยนแปลงไม่ได้ - ประเภทข้อมูลพื้นฐาน เช่น
String, IntและFloatจะเปลี่ยนแปลงไม่ได้เสมอ - หากทำไม่ได้ คุณต้องใช้สถานะ Compose สำหรับพร็อพเพอร์ตี้ที่เปลี่ยนแปลงได้
- ตรวจสอบว่าพร็อพเพอร์ตี้ทั้งหมดของคลาสเป็นทั้ง
- เสถียร: ระบุประเภทที่เปลี่ยนแปลงได้ รันไทม์ Compose จะไม่ทราบว่าพร็อพเพอร์ตี้สาธารณะหรือลักษณะการทำงานของเมธอดของประเภทใดประเภทหนึ่งจะให้ผลลัพธ์ที่แตกต่างจากการเรียกใช้ก่อนหน้านี้หรือไม่
คอลเล็กชันที่เปลี่ยนแปลงไม่ได้
เหตุผลที่พบบ่อยที่ทำให้ Compose พิจารณาว่าคลาสไม่เสถียรคือคอลเล็กชัน ดังที่ระบุไว้
ในหน้าวิเคราะห์ปัญหาความเสถียร คอมไพเลอร์ Compose
ไม่สามารถแน่ใจได้ว่าคอลเล็กชัน เช่น List, Map และ Set จะ
เปลี่ยนแปลงไม่ได้โดยสมบูรณ์ จึงทำเครื่องหมายว่าไม่เสถียร
หากต้องการแก้ไขปัญหานี้ คุณสามารถใช้คอลเล็กชันที่เปลี่ยนแปลงไม่ได้ คอมไพเลอร์ Compose มีการรองรับคอลเล็กชันที่เปลี่ยนแปลงไม่ได้ของ Kotlinx คอลเล็กชันเหล่านี้ออกแบบมาให้เปลี่ยนแปลงไม่ได้ และคอมไพเลอร์ 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 ว่าเสถียร
คลาสที่ใส่คำอธิบายประกอบในคอลเล็กชัน
ลองพิจารณา 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 แทน 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 จะทำเครื่องหมาย Composable
HighlightedSnacks ว่าทั้ง 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 ซึ่งเป็นแนวทางที่แนะนำ คุณอาจพบปัญหานี้
โซลูชัน
หากต้องการแก้ปัญหานี้ คุณสามารถใช้แนวทางใดแนวทางหนึ่งต่อไปนี้
- เพิ่มคลาสลงในไฟล์การกำหนดค่าคอมไพเลอร์
- เปิดใช้คอมไพเลอร์ Compose ในโมดูลเลเยอร์ข้อมูล หรือใส่คำอธิบายประกอบคลาสด้วย
@Stableหรือ@Immutableหากเหมาะสม- ซึ่งเกี่ยวข้องกับการเพิ่มทรัพยากร Dependency ของ Compose ลงในชั้นข้อมูล อย่างไรก็ตาม การพึ่งพานี้มีไว้สำหรับรันไทม์ Compose เท่านั้น ไม่ใช่สำหรับ
Compose-UI
- ซึ่งเกี่ยวข้องกับการเพิ่มทรัพยากร Dependency ของ Compose ลงในชั้นข้อมูล อย่างไรก็ตาม การพึ่งพานี้มีไว้สำหรับรันไทม์ Compose เท่านั้น ไม่ใช่สำหรับ
- ในโมดูล UI ให้ใส่คลาสเลเยอร์ข้อมูลไว้ในคลาส Wrapper ที่เฉพาะเจาะจงกับ UI
ปัญหาเดียวกันนี้จะเกิดขึ้นเมื่อใช้ไลบรารีภายนอกหากไลบรารีเหล่านั้นไม่ได้ใช้คอมไพเลอร์ Compose
Composable ไม่ควรข้ามได้ทุกรายการ
เมื่อพยายามแก้ไขปัญหาความเสถียร คุณไม่ควรพยายามทำให้ Composable ทุกรายการข้ามได้ การพยายามทำเช่นนั้นอาจนำไปสู่การเพิ่มประสิทธิภาพก่อนเวลาอันควร ซึ่งทำให้เกิดปัญหามากกว่าที่แก้ไข
มีหลายกรณีที่การข้ามได้ไม่ได้ให้ประโยชน์ที่แท้จริงและอาจทำให้โค้ดดูแลรักษายาก เช่น
- Composable ที่ไม่ประกอบใหม่บ่อยนักหรือไม่ประกอบใหม่เลย
- Composable ที่เรียกใช้ Composable ที่ข้ามได้เท่านั้น
- Composable ที่มีพารามิเตอร์จำนวนมากซึ่งมีการใช้งาน equals ที่ซับซ้อน ในกรณีนี้ ต้นทุนในการตรวจสอบว่าพารามิเตอร์มีการเปลี่ยนแปลงหรือไม่ อาจมากกว่าต้นทุนของการจัดองค์ประกอบใหม่ที่เรียบง่าย
เมื่อ Composable ข้ามได้ จะมีการเพิ่มค่าใช้จ่ายเล็กน้อยซึ่งอาจไม่คุ้มค่า คุณยังใส่คำอธิบายประกอบ Composable ให้รีสตาร์ทไม่ได้ในกรณี ที่คุณพิจารณาว่าการรีสตาร์ทได้ทำให้เกิดค่าใช้จ่ายมากกว่าคุณค่าที่ได้รับ