เขียนโค้ดเรียกกลับของสถานะด้วย RememberObserver และ RetainObserver

ใน Jetpack Compose ออบเจ็กต์สามารถใช้ RememberObserver เพื่อรับ การเรียกกลับเมื่อใช้กับ remember เพื่อให้ทราบเวลาที่เริ่มและหยุด การจดจำในลำดับชั้นการจัดองค์ประกอบ ในทำนองเดียวกัน คุณสามารถใช้ RetainObserver เพื่อรับข้อมูลเกี่ยวกับสถานะของออบเจ็กต์ที่ใช้ กับ retain

สำหรับออบเจ็กต์ที่ใช้ข้อมูลวงจรนี้จากลำดับชั้นขององค์ประกอบ เราขอแนะนำแนวทางปฏิบัติแนะนำ 2-3 ข้อเพื่อยืนยันว่าออบเจ็กต์ของคุณทำหน้าที่เป็น พลเมืองที่ดีในแพลตฟอร์มและป้องกันการใช้งานในทางที่ผิด โดยเฉพาะอย่างยิ่ง ให้ใช้การเรียกกลับ onRemembered (หรือ onRetained) เพื่อเปิดใช้งานแทนตัวสร้าง ยกเลิกงานทั้งหมดเมื่อระบบหยุดจดจำหรือเก็บออบเจ็กต์ไว้ และ หลีกเลี่ยงการรั่วไหลของการใช้งาน RememberObserver และ RetainObserver เพื่อ หลีกเลี่ยงการเรียกโดยไม่ตั้งใจ ส่วนถัดไปจะอธิบายคำแนะนำเหล่านี้อย่างละเอียดมากขึ้น

การเริ่มต้นและการล้างข้อมูลด้วย RememberObserver และ RetainObserver

คู่มือการคิดใน Compose อธิบายโมเดลความคิดเบื้องหลัง การเขียน เมื่อทำงานกับ RememberObserver และ RetainObserver คุณควรคำนึงถึงลักษณะการทำงาน 2 อย่างของการคอมโพส ดังนี้

  • การเขียนใหม่เป็นการดำเนินการแบบมองโลกในแง่ดีและอาจถูกยกเลิก
  • ฟังก์ชันที่ใช้ร่วมกันได้ทั้งหมดไม่ควรมีผลข้างเคียง

เรียกใช้ผลข้างเคียงของการเริ่มต้นระหว่าง onRemembered หรือ onRetained ไม่ใช่การสร้าง

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

แต่เมื่อใช้ RememberObserver หรือ RetainObserver ให้ตรวจสอบว่า มีการเรียกใช้เอฟเฟกต์และงานที่เปิดตัวทั้งหมดในแฮนเดิล onRemembered ซึ่งมีเวลาเดียวกันกับ SideEffect API นอกจากนี้ยังรับประกันว่า เอฟเฟกต์เหล่านี้จะทำงานเมื่อใช้การจัดองค์ประกอบเท่านั้น ซึ่งจะป้องกัน งานที่ไม่มีการอ้างอิงและหน่วยความจำรั่วหากมีการละทิ้งหรือเลื่อนการจัดองค์ประกอบใหม่

class MyComposeObject : RememberObserver {
    private val job = Job()
    private val coroutineScope = CoroutineScope(Dispatchers.Main + job)

    init {
        // Not recommended: This will cause work to begin during composition instead of
        // with other effects. Move this into onRemembered().
        coroutineScope.launch { loadData() }
    }

    override fun onRemembered() {
        // Recommended: Move any cancellable or effect-driven work into the onRemembered
        // callback. If implementing RetainObserver, this should go in onRetained.
        coroutineScope.launch { loadData() }
    }

    private suspend fun loadData() { /* ... */ }

    // ...
}

การถอดแยกชิ้นส่วนเมื่อลืม เลิกใช้ หรือทิ้ง

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

เนื่องจากยกเลิกการคอมโพสได้ ออบเจ็กต์ที่ใช้ RememberObserver ต้องล้างข้อมูลของตัวเองด้วยหากถูกทิ้งไว้ในคอมโพส ระบบจะทิ้ง ออบเจ็กต์เมื่อ remember ส่งคืนออบเจ็กต์ในองค์ประกอบที่ถูก ยกเลิกหรือล้มเหลว (ปัญหานี้มักเกิดขึ้นเมื่อใช้ PausableComposition และอาจเกิดขึ้นเมื่อใช้การโหลดซ้ำด่วนกับเครื่องมือแสดงตัวอย่างที่ใช้ได้ของ Android Studio ด้วย)

เมื่อทิ้งออบเจ็กต์ที่จดจำไว้ ออบเจ็กต์จะได้รับการเรียกใช้เฉพาะ onAbandoned เท่านั้น (และไม่มีการเรียกใช้ onRemembered) หากต้องการใช้เมธอดทิ้ง ให้ทิ้งทุกอย่างที่สร้างขึ้นระหว่างเวลาที่เริ่มต้นออบเจ็กต์กับเวลาที่ออบเจ็กต์จะได้รับโค้ดเรียกกลับ onRemembered

class MyComposeObject : RememberObserver {
    private val job = Job()
    private val coroutineScope = CoroutineScope(Dispatchers.Main + job)

    // ...

    override fun onForgotten() {
        // Cancel work launched from onRemembered. If implementing RetainObserver, onRetired
        // should cancel work launched from onRetained.
        job.cancel()
    }

    override fun onAbandoned() {
        // If any work was launched by the constructor as part of remembering the object,
        // you must cancel that work in this callback. For work done as part of the construction
        // during retain, this code should will appear in onUnused.
        job.cancel()
    }
}

เก็บการติดตั้งใช้งาน RememberObserver และ RetainObserver ไว้เป็นส่วนตัว

เมื่อเขียน API สาธารณะ โปรดใช้ความระมัดระวังเมื่อขยาย RememberObserver และ RetainObserver ในการสร้างคลาสที่ส่งคืนแบบสาธารณะ ผู้ใช้อาจไม่ จดจำออบเจ็กต์ของคุณเมื่อคุณคาดหวัง หรืออาจจดจำออบเจ็กต์ของคุณใน ลักษณะที่แตกต่างจากที่คุณตั้งใจไว้ ด้วยเหตุนี้ เราจึงขอแนะนำว่าอย่าแสดงตัวสร้างหรือฟังก์ชัน Factory สำหรับออบเจ็กต์ที่ใช้ RememberObserver หรือ RetainObserver โปรดทราบว่าการดำเนินการนี้ขึ้นอยู่กับประเภทรันไทม์ของคลาส ไม่ใช่ประเภทที่ประกาศไว้ โดยการจดจำออบเจ็กต์ที่ใช้ RememberObserver หรือ RetainObserver แต่มีการแคสต์เป็น Any จะยังคงทำให้ออบเจ็กต์ได้รับ การเรียกกลับ

ไม่แนะนำ

abstract class MyManager

// Not Recommended: Exposing a public constructor (even implicitly) for an object implementing
// RememberObserver can cause unexpected invocations if it is remembered multiple times.
class MyComposeManager : MyManager(), RememberObserver { ... }

// Not Recommended: The return type may be an implementation of RememberObserver and should be
// remembered explicitly.
fun createFoo(): MyManager = MyComposeManager()

แนะนำ

abstract class MyManager

class MyComposeManager : MyManager() {
    // Callers that construct this object must manually call initialize and teardown
    fun initialize() { /*...*/ }
    fun teardown() { /*...*/ }
}

@Composable
fun rememberMyManager(): MyManager {
    // Protect the RememberObserver implementation by never exposing it outside the library
    return remember {
        object : RememberObserver {
            val manager = MyComposeManager()
            override fun onRemembered() = manager.initialize()
            override fun onForgotten() = manager.teardown()
            override fun onAbandoned() { /* Nothing to do if manager hasn't initialized */ }
        }
    }.manager
}

สิ่งที่ควรพิจารณาเมื่อจดจำออบเจ็กต์

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

จดจำออบเจ็กต์เพียงครั้งเดียว

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

val first: RememberObserver = rememberFoo()

// Not Recommended: Re-remembered `Foo` now gets double callbacks
val second = remember { first }

คำแนะนำนี้ไม่มีผลกับออบเจ็กต์ที่ระบบจดจำอีกครั้งแบบทรานซิทีฟ (เช่น ออบเจ็กต์ที่จดจำซึ่งใช้ออบเจ็กต์ที่จดจำอีกรายการ) โดยปกติแล้ว จะมีการเขียนโค้ดที่มีลักษณะดังนี้ ซึ่งทำได้เนื่องจากระบบจะจดจำออบเจ็กต์อื่น และจึงไม่ทำให้เกิดการเรียกกลับซ้ำโดยไม่คาดคิด

val foo: Foo = rememberFoo()

// Acceptable:
val bar: Bar = remember { Bar(foo) }

// Recommended key usage:
val barWithKey: Bar = remember(foo) { Bar(foo) }

ถือว่าระบบจดจำอาร์กิวเมนต์ของฟังก์ชันแล้ว

ฟังก์ชันไม่ควรจดจำพารามิเตอร์ใดๆ ทั้งนี้เนื่องจากอาจทำให้เกิด การเรียกใช้ Callback ซ้ำสำหรับ RememberObserver และเนื่องจากไม่จำเป็น หากต้องจดจำพารามิเตอร์อินพุต ให้ตรวจสอบว่าพารามิเตอร์นั้นไม่ได้ใช้ RememberObserver หรือกำหนดให้ผู้โทรจดจำอาร์กิวเมนต์ของตน

@Composable
fun MyComposable(
    parameter: Foo
) {
    // Not Recommended: Input should be remembered by the caller.
    val rememberedParameter = remember { parameter }
}

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

@Composable
fun MyComposable(
    parameter: Foo
) {
    // Acceptable:
    val derivedValue = remember { Bar(parameter) }

    // Also Acceptable:
    val derivedValueWithKey = remember(parameter) { Bar(parameter) }
}

อย่าเก็บออบเจ็กต์ที่จำได้อยู่แล้ว

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