การจัดการวงจรด้วยคอมโพเนนต์ที่รับรู้วงจร เป็นส่วนหนึ่งของ Android Jetpack
คอมโพเนนต์ที่รับรู้วงจรจะดำเนินการตามการเปลี่ยนแปลงสถานะวงจรของคอมโพเนนต์อื่น เช่น กิจกรรมและข้อมูลโค้ด คอมโพเนนต์เหล่านี้ช่วยให้คุณสร้างโค้ดที่มีการจัดระเบียบดีขึ้นและมักจะมีน้ำหนักเบากว่า ซึ่งทำให้ดูแลรักษาได้ง่ายขึ้น
รูปแบบที่พบบ่อยคือการใช้การดำเนินการของคอมโพเนนต์ที่ขึ้นต่อกันในเมธอดวงจรชีวิตของกิจกรรมและฟragment อย่างไรก็ตาม รูปแบบนี้ทำให้การจัดระเบียบโค้ดไม่ดีและทำให้เกิดข้อผิดพลาดจำนวนมาก การใช้คอมโพเนนต์ที่รู้เกี่ยวกับวงจรจะย้ายโค้ดของคอมโพเนนต์ที่ขึ้นต่อกันออกจากเมธอดวงจรและย้ายไปไว้ในคอมโพเนนต์นั้นๆ ได้
แพ็กเกจ androidx.lifecycle
มีคลาสและอินเทอร์เฟซที่ช่วยให้คุณสร้างคอมโพเนนต์ที่รับรู้วงจรได้ ซึ่งเป็นคอมโพเนนต์ที่ปรับลักษณะการทำงานโดยอัตโนมัติตามสถานะวงจรปัจจุบันของกิจกรรมหรือฟragment
คอมโพเนนต์แอปส่วนใหญ่ที่กําหนดไว้ในเฟรมเวิร์ก Android จะมีวงจรอยู่ วงจรจัดการโดยระบบปฏิบัติการหรือโค้ดเฟรมเวิร์กที่ใช้ในกระบวนการ นโยบายเหล่านี้เป็นหัวใจสำคัญของวิธีการทำงานของ Android และแอปพลิเคชันของคุณต้องปฏิบัติตามนโยบายดังกล่าว การไม่ทำเช่นนั้นอาจทริกเกอร์ปัญหาหน่วยความจำรั่วหรือแอปพลิเคชันขัดข้อง
สมมติว่าเรามีกิจกรรมที่แสดงตำแหน่งของอุปกรณ์บนหน้าจอ การใช้งานที่พบบ่อยอาจมีลักษณะดังนี้
Kotlin
internal class MyLocationListener( private val context: Context, private val callback: (Location) -> Unit ) { fun start() { // connect to system location service } fun stop() { // disconnect from system location service } } class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } public override fun onStart() { super.onStart() myLocationListener.start() // manage other components that need to respond // to the activity lifecycle } public override fun onStop() { super.onStop() myLocationListener.stop() // manage other components that need to respond // to the activity lifecycle } }
Java
class MyLocationListener { public MyLocationListener(Context context, Callback callback) { // ... } void start() { // connect to system location service } void stop() { // disconnect from system location service } } class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; @Override public void onCreate(...) { myLocationListener = new MyLocationListener(this, (location) -> { // update UI }); } @Override public void onStart() { super.onStart(); myLocationListener.start(); // manage other components that need to respond // to the activity lifecycle } @Override public void onStop() { super.onStop(); myLocationListener.stop(); // manage other components that need to respond // to the activity lifecycle } }
แม้ว่าตัวอย่างนี้ดูดี แต่ในแอปจริง คุณอาจมีการเรียกใช้ที่จัดการ UI และคอมโพเนนต์อื่นๆ มากเกินไปเพื่อตอบสนองต่อสถานะปัจจุบันของวงจร การจัดการคอมโพเนนต์หลายรายการจะเพิ่มโค้ดจำนวนมากลงในเมธอดของวงจร เช่น onStart()
และ onStop()
ซึ่งทำให้ดูแลรักษาได้ยาก
นอกจากนี้ ไม่มีการรับประกันว่าคอมโพเนนต์จะเริ่มทำงานก่อนที่กิจกรรมหรือข้อมูลโค้ดจะหยุดลง โดยเฉพาะอย่างยิ่งในกรณีที่เราต้องดำเนินการที่ใช้เวลานาน เช่น การตรวจสอบการกําหนดค่าบางอย่างใน onStart()
ซึ่งอาจทำให้เกิดเงื่อนไขการแข่งขันที่เมธอด onStop()
ทำงานเสร็จก่อน onStart()
ทำให้คอมโพเนนต์ทำงานนานกว่าที่จำเป็น
Kotlin
class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } public override fun onStart() { super.onStart() Util.checkUserStatus { result -> // what if this callback is invoked AFTER activity is stopped? if (result) { myLocationListener.start() } } } public override fun onStop() { super.onStop() myLocationListener.stop() } }
Java
class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, location -> { // update UI }); } @Override public void onStart() { super.onStart(); Util.checkUserStatus(result -> { // what if this callback is invoked AFTER activity is stopped? if (result) { myLocationListener.start(); } }); } @Override public void onStop() { super.onStop(); myLocationListener.stop(); } }
แพ็กเกจ androidx.lifecycle
มีคลาสและอินเทอร์เฟซที่ช่วยให้คุณจัดการปัญหาเหล่านี้ได้อย่างยืดหยุ่นและแยกส่วน
วงจร
Lifecycle
เป็นคลาสที่มีข้อมูลเกี่ยวกับสถานะวงจรชีวิตของคอมโพเนนต์ (เช่น กิจกรรมหรือฟragment) และอนุญาตให้ออบเจ็กต์อื่นๆ สังเกตสถานะนี้ได้
Lifecycle
ใช้การแจกแจงหลัก 2 รายการเพื่อติดตามสถานะวงจรของลูกค้าสําหรับคอมโพเนนต์ที่เกี่ยวข้อง ดังนี้
- กิจกรรม
- เหตุการณ์ในวงจรที่ส่งมาจากเฟรมเวิร์กและคลาส
Lifecycle
เหตุการณ์เหล่านี้จะแมปกับเหตุการณ์ Callback ในกิจกรรมและข้อมูลโค้ด - รัฐ
- สถานะปัจจุบันของคอมโพเนนต์ที่ออบเจ็กต์
Lifecycle
ติดตาม
ให้คิดว่าสถานะเป็นโหนดของกราฟ และเหตุการณ์เป็นขอบระหว่างโหนดเหล่านี้
คลาสสามารถตรวจสอบสถานะวงจรชีวิตของคอมโพเนนต์ได้โดยการใช้ DefaultLifecycleObserver
และลบล้างเมธอดที่เกี่ยวข้อง เช่น onCreate
, onStart
เป็นต้น จากนั้นคุณสามารถเพิ่มผู้สังเกตการณ์ได้โดยเรียกใช้เมธอด addObserver()
ของคลาส Lifecycle
และส่งอินสแตนซ์ของผู้สังเกตการณ์ ดังที่แสดงในตัวอย่างต่อไปนี้
Kotlin
class MyObserver : DefaultLifecycleObserver { override fun onResume(owner: LifecycleOwner) { connect() } override fun onPause(owner: LifecycleOwner) { disconnect() } } myLifecycleOwner.getLifecycle().addObserver(MyObserver())
Java
public class MyObserver implements DefaultLifecycleObserver { @Override public void onResume(LifecycleOwner owner) { connect() } @Override public void onPause(LifecycleOwner owner) { disconnect() } } myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
ในตัวอย่างข้างต้น ออบเจ็กต์ myLifecycleOwner
ใช้อินเทอร์เฟซ LifecycleOwner
ซึ่งอธิบายไว้ในส่วนถัดไป
LifecycleOwner
LifecycleOwner
เป็นอินเตอร์เฟซเมธอดเดียวที่ระบุว่าคลาสมี Lifecycle
โดยจะมีเมธอดเดียว getLifecycle()
ซึ่งคลาสต้องนำมาใช้งาน
หากต้องการจัดการวงจรของกระบวนการแอปพลิเคชันทั้งหมดแทน โปรดดูProcessLifecycleOwner
อินเทอร์เฟซนี้จะแยกการเป็นเจ้าของ Lifecycle
ออกจากคลาสแต่ละคลาส เช่น Fragment
และ AppCompatActivity
และอนุญาตให้เขียนคอมโพเนนต์ที่ทำงานร่วมกับคลาสเหล่านั้นได้ คลาสแอปพลิเคชันที่กำหนดเองใดๆ สามารถใช้อินเทอร์เฟซ LifecycleOwner
ได้
คอมโพเนนต์ที่ใช้ DefaultLifecycleObserver
จะทํางานร่วมกับคอมโพเนนต์ที่ใช้ LifecycleOwner
ได้อย่างราบรื่น เนื่องจากเจ้าของสามารถระบุวงจรชีวิตของคอมโพเนนต์ ซึ่งผู้สังเกตการณ์สามารถลงทะเบียนเพื่อดูได้
สําหรับตัวอย่างการติดตามตําแหน่ง เราสามารถสร้างคลาส MyLocationListener
ให้ใช้งาน DefaultLifecycleObserver
แล้วเริ่มต้นด้วย Lifecycle
ของกิจกรรมในเมธอด onCreate()
วิธีนี้ช่วยให้คลาส MyLocationListener
ทำงานได้ด้วยตัวเอง ซึ่งหมายความว่าจะมีการประกาศตรรกะในการตอบสนองต่อการเปลี่ยนแปลงสถานะวงจรใน MyLocationListener
แทนกิจกรรม การที่คอมโพเนนต์แต่ละรายการจัดเก็บตรรกะของตนเองจะช่วยให้จัดการตรรกะของกิจกรรมและข้อมูลโค้ดได้ง่ายขึ้น
Kotlin
class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this, lifecycle) { location -> // update UI } Util.checkUserStatus { result -> if (result) { myLocationListener.enable() } } } }
Java
class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, getLifecycle(), location -> { // update UI }); Util.checkUserStatus(result -> { if (result) { myLocationListener.enable(); } }); } }
Use Case ที่พบบ่อยคือหลีกเลี่ยงการเรียกใช้การเรียกกลับบางรายการหาก Lifecycle
ไม่อยู่ในสถานะที่ดีในขณะนี้ ตัวอย่างเช่น หากการเรียกกลับเรียกใช้ธุรกรรมของข้อมูลโค้ดที่แยกส่วนหลังจากบันทึกสถานะกิจกรรมแล้ว ระบบจะทริกเกอร์ข้อขัดข้อง เราจึงไม่ต้องเรียกใช้การเรียกกลับนั้น
คลาส Lifecycle
ช่วยให้ออบเจ็กต์อื่นๆ ค้นหาสถานะปัจจุบันได้ เพื่อให้ Use Case นี้ง่ายขึ้น
Kotlin
internal class MyLocationListener( private val context: Context, private val lifecycle: Lifecycle, private val callback: (Location) -> Unit ): DefaultLifecycleObserver { private var enabled = false override fun onStart(owner: LifecycleOwner) { if (enabled) { // connect } } fun enable() { enabled = true if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { // connect if not connected } } override fun onStop(owner: LifecycleOwner) { // disconnect if connected } }
Java
class MyLocationListener implements DefaultLifecycleObserver { private boolean enabled = false; public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) { ... } @Override public void onStart(LifecycleOwner owner) { if (enabled) { // connect } } public void enable() { enabled = true; if (lifecycle.getCurrentState().isAtLeast(STARTED)) { // connect if not connected } } @Override public void onStop(LifecycleOwner owner) { // disconnect if connected } }
การติดตั้งใช้งานนี้ทำให้คลาส LocationListener
ของเรารองรับวงจรของลูกค้าอย่างสมบูรณ์ หากต้องการใช้ LocationListener
จากกิจกรรมหรือส่วนอื่น ก็เพียงต้องเริ่มต้นใช้งานเท่านั้น การดำเนินการทั้งหมดในการตั้งค่าและการเลิกใช้งานจะจัดการโดยชั้นเรียนเอง
หากไลบรารีมีคลาสที่ต้องทำงานร่วมกับวงจรชีวิตของ Android เราขอแนะนำให้คุณใช้คอมโพเนนต์ที่รับรู้วงจรของลูกค้า ลูกค้าไลบรารีสามารถผสานรวมคอมโพเนนต์เหล่านั้นได้อย่างง่ายดายโดยไม่ต้องจัดการวงจรของลูกค้าด้วยตนเอง
การใช้ LifecycleOwner ที่กําหนดเอง
ส่วนต่างๆ และกิจกรรมใน Support Library 26.1.0 ขึ้นไปใช้LifecycleOwner
อินเตอร์เฟซอยู่แล้ว
หากมีคลาสที่กำหนดเองที่ต้องการสร้าง LifecycleOwner
คุณสามารถใช้คลาส LifecycleRegistry แต่ต้องส่งต่อเหตุการณ์ไปยังคลาสนั้น ดังที่แสดงในตัวอย่างโค้ดต่อไปนี้
Kotlin
class MyActivity : Activity(), LifecycleOwner { private lateinit var lifecycleRegistry: LifecycleRegistry override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleRegistry = LifecycleRegistry(this) lifecycleRegistry.markState(Lifecycle.State.CREATED) } public override fun onStart() { super.onStart() lifecycleRegistry.markState(Lifecycle.State.STARTED) } override fun getLifecycle(): Lifecycle { return lifecycleRegistry } }
Java
public class MyActivity extends Activity implements LifecycleOwner { private LifecycleRegistry lifecycleRegistry; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); lifecycleRegistry = new LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED); } @Override public void onStart() { super.onStart(); lifecycleRegistry.markState(Lifecycle.State.STARTED); } @NonNull @Override public Lifecycle getLifecycle() { return lifecycleRegistry; } }
แนวทางปฏิบัติแนะนำสำหรับคอมโพเนนต์ที่รับรู้วงจรของลูกค้า
- ควบคุม UI (กิจกรรมและส่วนต่างๆ) ให้น้อยที่สุดเท่าที่เป็นไปได้ โดยไม่ควรพยายามรวบรวมข้อมูลของตนเอง แต่ให้ใช้
ViewModel
แทน และสังเกตออบเจ็กต์LiveData
เพื่อแสดงการเปลี่ยนแปลงในมุมมอง - ลองเขียน UI ที่ขับเคลื่อนโดยข้อมูลโดยที่ความรับผิดชอบของเครื่องมือควบคุม UI คือการอัปเดตมุมมองเมื่อข้อมูลมีการเปลี่ยนแปลง หรือแจ้งการดําเนินการของผู้ใช้กลับไปยัง
ViewModel
- ใส่ตรรกะข้อมูลในคลาส
ViewModel
ViewModel
ควรทำหน้าที่เป็นเครื่องมือเชื่อมต่อระหว่างตัวควบคุม UI กับส่วนที่เหลือของแอป แต่โปรดทราบว่าViewModel
ไม่ได้มีหน้าที่ดึงข้อมูล (เช่น จากเครือข่าย) แต่ViewModel
ควรเรียกใช้คอมโพเนนต์ที่เหมาะสมเพื่อดึงข้อมูล แล้วส่งผลลัพธ์กลับไปยังตัวควบคุม UI - ใช้การเชื่อมโยงข้อมูลเพื่อรักษาอินเทอร์เฟซที่เรียบร้อยระหว่างมุมมองกับตัวควบคุม UI ซึ่งช่วยให้คุณทำให้มุมมองเป็นแบบประกาศมากขึ้นและลดโค้ดการอัปเดตที่ต้องเขียนในกิจกรรมและข้อมูลโค้ดได้ หากต้องการดำเนินการนี้ในภาษาโปรแกรม Java ให้ใช้ไลบรารีอย่าง Butter Knife เพื่อหลีกเลี่ยงโค้ดที่ซ้ำกันและมีการสร้างแบบนามธรรมที่ดียิ่งขึ้น
- หาก UI มีความซับซ้อน ให้พิจารณาสร้างคลาส presenter เพื่อจัดการการแก้ไข UI งานนี้อาจทําได้ยาก แต่จะช่วยให้ทดสอบคอมโพเนนต์ UI ได้ง่ายขึ้น
- หลีกเลี่ยงการอ้างอิงบริบท
View
หรือActivity
ในViewModel
หากViewModel
มีอายุมากกว่ากิจกรรม (ในกรณีที่มีการเปลี่ยนแปลงการกําหนดค่า) กิจกรรมจะรั่วไหลและระบบเก็บขยะจะไม่จัดการอย่างถูกต้อง - ใช้ Kotlin coroutine เพื่อจัดการงานที่ทำงานต่อเนื่องเป็นเวลานานและการดำเนินการอื่นๆ ที่ทำงานแบบไม่พร้อมกันได้
Use Case สําหรับคอมโพเนนต์ที่ทราบวงจร
คอมโพเนนต์ที่รับรู้วงจรช่วยให้คุณจัดการวงจรได้ง่ายขึ้นอย่างมากในหลายกรณี ตัวอย่างบางส่วนมีดังนี้
- การสลับระหว่างการอัปเดตตำแหน่งแบบหยาบและแบบละเอียด ใช้คอมโพเนนต์ที่รับรู้วงจรเพื่อเปิดใช้การอัปเดตตำแหน่งแบบละเอียดขณะที่แอปตำแหน่งแสดงอยู่ และเปลี่ยนไปใช้การอัปเดตแบบหยาบเมื่อแอปทำงานอยู่เบื้องหลัง
LiveData
เป็นคอมโพเนนต์ที่รับรู้วงจร ซึ่งช่วยให้แอปอัปเดต UI โดยอัตโนมัติเมื่อผู้ใช้เปลี่ยนตำแหน่ง - การหยุดและเริ่มบัฟเฟอร์วิดีโอ ใช้คอมโพเนนต์ที่รับรู้วงจรเพื่อเริ่มบัฟเฟอร์วิดีโอโดยเร็วที่สุด แต่เลื่อนการเล่นจนกว่าแอปจะเริ่มต้นอย่างสมบูรณ์ นอกจากนี้ คุณยังใช้คอมโพเนนต์ที่รับรู้วงจรเพื่อหยุดบัฟเฟอร์เมื่อแอปถูกทำลายได้ด้วย
- การเริ่มและหยุดการเชื่อมต่อเครือข่าย ใช้คอมโพเนนต์ที่รับรู้วงจรเพื่อเปิดใช้การอัปเดตแบบเรียลไทม์ (สตรีมมิง) ของข้อมูลเครือข่ายขณะที่แอปอยู่เบื้องหน้า และหยุดชั่วคราวโดยอัตโนมัติเมื่อแอปทำงานอยู่ในเบื้องหลัง
- การหยุดองค์ประกอบที่วาดได้แบบเคลื่อนไหวไว้ชั่วคราวและกลับมาแสดงอีกครั้ง ใช้คอมโพเนนต์ที่รับรู้วงจรเพื่อจัดการการหยุดเนื้อหาที่วาดได้แบบเคลื่อนไหวชั่วคราวเมื่อแอปอยู่เบื้องหลัง และแสดงเนื้อหาที่วาดได้ต่อหลังจากแอปอยู่เบื้องหน้า
การจัดการเหตุการณ์หยุด
เมื่อ Lifecycle
เป็นของ AppCompatActivity
หรือ Fragment
สถานะของ Lifecycle
จะเปลี่ยนเป็น CREATED
และระบบจะส่งเหตุการณ์ ON_STOP
เมื่อมีการเรียก onSaveInstanceState()
ของ AppCompatActivity
หรือ Fragment
เมื่อบันทึกสถานะของ Fragment
หรือ AppCompatActivity
ผ่าน onSaveInstanceState()
ระบบจะถือว่า UI ของ Fragment
หรือ AppCompatActivity
นั้นเปลี่ยนแปลงไม่ได้จนกว่าจะเรียกใช้ ON_START
การพยายามแก้ไข UI หลังจากบันทึกสถานะมีแนวโน้มที่จะทําให้สถานะการนําทางของแอปพลิเคชันไม่สอดคล้องกัน ด้วยเหตุนี้ FragmentManager
จึงแสดงข้อยกเว้นหากแอปเรียกใช้ FragmentTransaction
หลังจากบันทึกสถานะ ดูรายละเอียดได้ที่
commit()
LiveData
ป้องกันกรณีขอบเขตนี้ตั้งแต่เริ่มต้นโดยงดเรียกใช้ผู้สังเกตการณ์หาก Lifecycle
ที่เชื่อมโยงของผู้สังเกตการณ์มีค่าไม่น้อยกว่า STARTED
เบื้องหลังคือมีการเรียกใช้ isAtLeast()
ก่อนตัดสินใจเรียกใช้ผู้สังเกตการณ์
ขออภัย วิธีการ onStop()
ของ AppCompatActivity
เรียกว่า after
onSaveInstanceState()
ซึ่งทำให้เกิดช่องว่างที่ไม่อนุญาตให้มีการเปลี่ยนแปลงสถานะของ UI แต่ Lifecycle
ยังไม่ได้ย้ายไปยังสถานะ CREATED
เพื่อป้องกันปัญหานี้ คลาส Lifecycle
ในเวอร์ชัน beta2
และต่ำกว่าจะทําเครื่องหมายสถานะเป็น CREATED
โดยไม่มีการส่งเหตุการณ์เพื่อให้โค้ดที่ตรวจสอบสถานะปัจจุบันได้รับค่าจริง แม้ว่าระบบจะไม่ส่งเหตุการณ์จนกว่าจะมีการเรียกใช้ onStop()
แต่วิธีนี้มีปัญหาสำคัญ 2 ข้อ ได้แก่
- ใน API ระดับ 23 และต่ำกว่า ระบบ Android จะบันทึกสถานะของกิจกรรมแม้ว่ากิจกรรมนั้นจะถูกครอบคลุมบางส่วนโดยกิจกรรมอื่นก็ตาม กล่าวคือ ระบบ Android จะเรียก
onSaveInstanceState()
แต่ไม่จำเป็นต้องเรียกonStop()
ซึ่งอาจทำให้เกิดช่วงเวลาที่นานที่ผู้สังเกตการณ์ยังคงคิดว่าวงจรยังมีการใช้งานอยู่ แม้ว่าจะไม่สามารถแก้ไขสถานะ UI ของวงจรได้ก็ตาม - คลาสที่ต้องการแสดงลักษณะการทำงานที่คล้ายกับคลาส
LiveData
ต้องใช้วิธีแก้ปัญหาที่ระบุโดยLifecycle
เวอร์ชันbeta 2
และต่ำกว่า
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการจัดการวงจรด้วยคอมโพเนนต์ที่รับรู้วงจรได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้
ตัวอย่าง
- Sunflower เป็นแอปสาธิตที่แสดงแนวทางปฏิบัติแนะนำเกี่ยวกับคอมโพเนนต์สถาปัตยกรรม
Codelabs
บล็อก
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- ภาพรวม LiveData
- ใช้โคโริวทีนของ Kotlin กับคอมโพเนนต์ที่รู้เกี่ยวกับวงจร
- โมดูลสถานะที่บันทึกไว้สําหรับ ViewModel