แนวคิดและการติดตั้งใช้งาน Jetpack Compose
คอมโพเนนต์ที่รับรู้ถึงวงจรจะดำเนินการเพื่อตอบสนองต่อการเปลี่ยนแปลงสถานะวงจรของคอมโพเนนต์อื่น เช่น กิจกรรมและ Fragment คอมโพเนนต์เหล่านี้ช่วยให้คุณสร้างโค้ดที่เป็นระเบียบมากขึ้นและมักจะมีขนาดเล็กลง ซึ่งดูแลรักษาได้ง่ายขึ้น
รูปแบบที่ใช้กันทั่วไปคือการติดตั้งใช้งานการทำงานของคอมโพเนนต์ที่ขึ้นอยู่กับทรัพยากร Dependency ในเมธอดวงจรของกิจกรรมและ Fragment อย่างไรก็ตาม รูปแบบนี้ทำให้โค้ดไม่เป็นระเบียบและเกิดข้อผิดพลาดมากขึ้น การใช้คอมโพเนนต์ที่รับรู้ถึงวงจรจะช่วยให้คุณย้ายโค้ดของคอมโพเนนต์ที่ขึ้นอยู่กับคอมโพเนนต์อื่นออกจากเมธอดวงจรและไปไว้ในคอมโพเนนต์เหล่านั้นได้
แพ็กเกจ androidx.lifecycle มีคลาสและอินเทอร์เฟซที่ช่วยให้ คุณสร้างคอมโพเนนต์ ที่รับรู้ถึงวงจร ซึ่งเป็นคอมโพเนนต์ที่ปรับลักษณะการทำงานโดยอัตโนมัติ ตามสถานะวงจรปัจจุบันของกิจกรรมหรือ Fragment
คอมโพเนนต์แอปส่วนใหญ่ที่กำหนดไว้ใน Android Framework จะมีวงจรแนบอยู่ ระบบปฏิบัติการหรือโค้ด Framework ที่ทำงานในกระบวนการของคุณจะเป็นผู้จัดการวงจร วงจรเป็นส่วนสำคัญของวิธีการทำงานของ 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 ซึ่ง
ทำให้ดูแลรักษาได้ยาก
นอกจากนี้ เราไม่สามารถรับประกันได้ว่าคอมโพเนนต์จะเริ่มทำงานก่อนที่กิจกรรมหรือ Fragment จะหยุดทำงาน โดยเฉพาะอย่างยิ่งหากเราต้องดำเนินการที่ใช้เวลานาน เช่น การตรวจสอบการกำหนดค่าบางอย่างใน 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
Lifecycle เป็นคลาสที่เก็บข้อมูลเกี่ยวกับสถานะวงจรของคอมโพเนนต์ (เช่น กิจกรรมหรือ Fragment) และอนุญาตให้ออบเจ็กต์อื่นๆ สังเกตสถานะนี้
Lifecycle ใช้การแจงนับหลัก 2 รายการเพื่อติดตามสถานะวงจรของคอมโพเนนต์ที่เชื่อมโยง
กิจกรรม
กิจกรรมวงจรที่ส่งจาก Framework และคลาส
Lifecycle กิจกรรมเหล่านี้จะแมปกับกิจกรรม Callback ในกิจกรรมและ Fragment
รัฐ
สถานะปัจจุบันของคอมโพเนนต์ที่ติดตามโดยออบเจ็กต์ Lifecycle
ลองนึกภาพว่าสถานะเป็นโหนดของกราฟ และกิจกรรมเป็นขอบระหว่างโหนดเหล่านี้
คลาสสามารถตรวจสอบสถานะวงจรของคอมโพเนนต์ได้โดยการติดตั้งใช้งาน
DefaultLifecycleObserver และลบล้างเมธอดที่เกี่ยวข้อง เช่น
onCreate, onStart, เป็นต้น จากนั้นคุณสามารถเพิ่ม Observer ได้โดยการเรียกเมธอด
addObserver() ของ Lifecycle คลาส และส่ง
อินสแตนซ์ของ Observer ดังที่แสดงในตัวอย่างต่อไปนี้
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 ได้อย่างราบรื่น เนื่องจากเจ้าของสามารถระบุ
วงจรที่ Observer ลงทะเบียนเพื่อดูได้
สำหรับตัวอย่างการติดตามตำแหน่ง เราสามารถทำให้คลาส MyLocationListener ติดตั้งใช้งาน DefaultLifecycleObserver แล้วเริ่มต้นด้วย Lifecycle ของกิจกรรมในเมธอด onCreate() ซึ่งจะช่วยให้คลาส MyLocationListener พึ่งพาตนเองได้ หมายความว่าตรรกะในการตอบสนองต่อการเปลี่ยนแปลงสถานะวงจรจะประกาศไว้ใน MyLocationListener แทนที่จะเป็นกิจกรรม การให้คอมโพเนนต์แต่ละรายการจัดเก็บตรรกะของตนเองจะช่วยให้จัดการตรรกะของกิจกรรมและ Fragment ได้ง่ายขึ้น
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();
}
});
}
}
กรณีการใช้งานที่พบบ่อยคือการหลีกเลี่ยงการเรียกใช้ Callback บางรายการหาก Lifecycle
ไม่ได้อยู่ในสถานะที่ดีในขณะนี้ ตัวอย่างเช่น หาก Callback เรียกใช้ธุรกรรม Fragment หลังจากบันทึกสถานะกิจกรรมแล้ว ก็จะทำให้เกิดข้อขัดข้อง ดังนั้นเราจึงไม่ต้องการเรียกใช้ Callback นั้น
คลาส Lifecycle อนุญาตให้ออบเจ็กต์อื่นๆ
ค้นหาสถานะปัจจุบันเพื่อให้กรณีการใช้งานนี้ง่ายขึ้น
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 จากกิจกรรมหรือ Fragment อื่น เราเพียงแค่ต้องเริ่มต้นเท่านั้น คลาสจะจัดการการดำเนินการตั้งค่าและการล้างข้อมูลทั้งหมดด้วยตัวเอง
หากไลบรารีมีคลาสที่ต้องทำงานร่วมกับวงจร Android เราขอแนะนำให้คุณใช้คอมโพเนนต์ที่รับรู้ถึงวงจร ไคลเอ็นต์ไลบรารีสามารถผสานรวมคอมโพเนนต์เหล่านั้นได้อย่างง่ายดายโดยไม่ต้องจัดการวงจรด้วยตนเองในฝั่งไคลเอ็นต์
การติดตั้งใช้งาน LifecycleOwner ที่กำหนดเอง
Fragment และกิจกรรมในไลบรารีการสนับสนุนเวอร์ชัน 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 (กิจกรรมและ Fragment) มีขนาดเล็กที่สุด
ตัวควบคุม UI ไม่ควรพยายามรับข้อมูลของตัวเอง แต่ควรใช้
ViewModelเพื่อดำเนินการดังกล่าว และสังเกตออบเจ็กต์LiveDataเพื่อ แสดงการเปลี่ยนแปลงกลับไปยังมุมมอง - ลองเขียน UI ที่ขับเคลื่อนด้วยข้อมูล โดยตัวควบคุม UI มีหน้าที่
อัปเดตมุมมองเมื่อข้อมูลเปลี่ยนแปลง หรือแจ้งการดำเนินการของผู้ใช้กลับไปยัง
ViewModel - ใส่ตรรกะข้อมูลไว้ในคลาส
ViewModelViewModelควรทำหน้าที่เป็นตัวเชื่อมต่อระหว่างตัวควบคุม UI กับส่วนอื่นๆ ของ แอป โปรดระวังว่าViewModel's ไม่ได้มีหน้าที่ ดึงข้อมูล (เช่น จากเครือข่าย) แต่ViewModelควร เรียกคอมโพเนนต์ที่เหมาะสมเพื่อดึงข้อมูล จากนั้นส่งผลลัพธ์ กลับไปยังตัวควบคุม UI - ใช้ การผูกข้อมูล เพื่อรักษาอินเทอร์เฟซที่ชัดเจนระหว่างมุมมองกับ ตัวควบคุม UI ซึ่งจะช่วยให้คุณประกาศมุมมองได้มากขึ้นและลดโค้ดอัปเดตที่คุณต้องเขียนในกิจกรรมและ Fragment หากต้องการดำเนินการนี้ในภาษาโปรแกรม Java ให้ใช้ไลบรารี เช่น Butter Knife เพื่อหลีกเลี่ยงโค้ด Boilerplate และมีการแยกส่วนที่ดีขึ้น
- หาก UI ซับซ้อน ให้พิจารณาสร้างคลาส Presenter เพื่อจัดการการแก้ไข UI แม้ว่าอาจเป็นงานที่ต้องใช้ความพยายามมาก แต่ก็ช่วยให้ทดสอบคอมโพเนนต์ UI ได้ง่ายขึ้น
- หลีกเลี่ยงการอ้างอิงบริบท
ViewหรือActivityในViewModelหากViewModelมีอายุการใช้งานนานกว่ากิจกรรม (ในกรณีที่มีการเปลี่ยนแปลงการกำหนดค่า) กิจกรรมจะรั่วไหลและ Garbage Collector จะไม่กำจัดกิจกรรมอย่างเหมาะสม - ใช้ Coroutine ของ Kotlin เพื่อจัดการงานที่ใช้เวลานานและ การดำเนินการอื่นๆ ที่ทำงานแบบไม่พร้อมกันได้
กรณีการใช้งานสำหรับคอมโพเนนต์ที่รับรู้ถึงวงจร
คอมโพเนนต์ที่รับรู้ถึงวงจรช่วยให้คุณจัดการวงจรได้ง่ายขึ้นมากในหลากหลายกรณี ตัวอย่างบางส่วนมีดังนี้
- การสลับระหว่างการอัปเดตตำแหน่งแบบหยาบและการอัปเดตตำแหน่งแบบละเอียด ใช้คอมโพเนนต์ที่รับรู้ถึงวงจรเพื่อเปิดใช้การอัปเดตตำแหน่งแบบละเอียดขณะที่แอปตำแหน่งของคุณแสดงอยู่ และสลับไปใช้การอัปเดตแบบหยาบเมื่อแอปอยู่ในเบื้องหลัง
LiveDataซึ่งเป็นคอมโพเนนต์ที่รับรู้ถึงวงจรจะช่วยให้แอปอัปเดต UI โดยอัตโนมัติเมื่อผู้ใช้เปลี่ยนตำแหน่ง - การหยุดและเริ่มการบัฟเฟอร์วิดีโอ ใช้คอมโพเนนต์ที่รับรู้ถึงวงจรเพื่อเริ่มการบัฟเฟอร์วิดีโอโดยเร็วที่สุด แต่เลื่อนการเล่นจนกว่าแอปจะเริ่มทำงานอย่างสมบูรณ์ นอกจากนี้ คุณยังใช้คอมโพเนนต์ที่รับรู้ถึงวงจรเพื่อสิ้นสุดการบัฟเฟอร์เมื่อแอปถูกทำลาย
- การเริ่มและหยุดการเชื่อมต่อเครือข่าย ใช้คอมโพเนนต์ที่รับรู้ถึงวงจรเพื่อเปิดใช้การอัปเดต (การสตรีม) ข้อมูลเครือข่ายแบบเรียลไทม์ขณะที่แอปอยู่ในเบื้องหน้า และหยุดชั่วคราวโดยอัตโนมัติเมื่อแอปเข้าสู่เบื้องหลัง
- การหยุดชั่วคราวและเล่นต่อของ Drawable แบบเคลื่อนไหว ใช้คอมโพเนนต์ที่รับรู้ถึงวงจรเพื่อจัดการการหยุดชั่วคราวของ Drawable แบบเคลื่อนไหวเมื่อแอปอยู่ในเบื้องหลัง และเล่นต่อของ Drawable หลังจากที่แอปอยู่ในเบื้องหน้า
การจัดการเหตุการณ์ onStop
เมื่อ Lifecycle เป็นของ AppCompatActivity หรือ
Fragment สถานะของ Lifecycle's จะเปลี่ยนเป็น CREATED และ
ระบบจะส่งเหตุการณ์ ON_STOP เมื่อมีการเรียก onSaveInstanceState() ของ AppCompatActivity หรือ
Fragment's
เมื่อมีการบันทึกสถานะของ Fragment หรือ AppCompatActivity's โดยใช้
onSaveInstanceState ระบบจะถือว่า UI ของ Fragment หรือ AppCompatActivity นั้นไม่สามารถเปลี่ยนแปลงได้จนกว่าจะมีการเรียก
ON_START การพยายามแก้ไข UI หลังจากบันทึกสถานะแล้วมี
แนวโน้มที่จะทำให้เกิดความไม่สอดคล้องกันในสถานะการนำทางของแอปพลิเคชัน
ซึ่งเป็นเหตุผลที่ FragmentManager จะแสดงข้อยกเว้นหากแอปเรียกใช้
FragmentTransaction หลังจากบันทึกสถานะแล้ว ดูรายละเอียดได้ที่ commit() for
LiveData ป้องกันกรณีขอบนี้ได้ทันทีโดยไม่เรียก Observer หาก Lifecycle ที่เชื่อมโยงของ Observer ไม่ได้อยู่ในสถานะ STARTED เป็นอย่างน้อย เบื้องหลัง จะเรียก isAtLeast() ก่อนที่จะตัดสินใจเรียกใช้ Observer
น่าเสียดายที่ AppCompatActivity's onStop() เมธอดจะถูกเรียก
หลังจาก onSaveInstanceState ซึ่งทำให้เกิดช่องว่างที่ไม่อนุญาตให้มีการเปลี่ยนแปลงสถานะ UI
แต่ Lifecycle ยังไม่ได้ย้ายไปยังสถานะ
CREATED
เพื่อป้องกันปัญหานี้ คลาส Lifecycle ในเวอร์ชัน beta2 และต่ำกว่า
จะทำเครื่องหมายสถานะเป็น CREATED โดยไม่ส่งเหตุการณ์ เพื่อให้โค้ด
ที่ตรวจสอบสถานะปัจจุบันได้รับค่าจริง แม้ว่าจะไม่มีการส่งเหตุการณ์จนกว่าระบบจะเรียก onStop()
น่าเสียดายที่โซลูชันนี้มีปัญหาสำคัญ 2 ประการ
- ในระดับ API 23 และต่ำกว่า ระบบ Android จะบันทึกสถานะของกิจกรรมจริง แม้ว่ากิจกรรมนั้นจะถูกกิจกรรมอื่นบังไว้ บางส่วน กล่าวอีกนัยหนึ่งคือ ระบบ Android จะเรียก
onSaveInstanceState()แต่ไม่จำเป็นต้องเรียกonStopซึ่งจะสร้างช่วงเวลาที่อาจยาวนานซึ่ง Observer ยังคงคิดว่าวงจรทำงานอยู่ แม้ว่าจะแก้ไขสถานะ UI ไม่ได้ก็ตาม - คลาสใดก็ตามที่ต้องการแสดงลักษณะการทำงานที่คล้ายกับคลาส
LiveDataจะต้องติดตั้งใช้งานวิธีแก้ปัญหาที่เวอร์ชันLifecyclebeta 2และต่ำกว่ามีให้
แหล่งข้อมูลเพิ่มเติม
หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับการจัดการวงจรด้วยคอมโพเนนต์ที่รับรู้ถึงวงจร โปรดดูแหล่งข้อมูลเพิ่มเติมต่อไปนี้
ตัวอย่าง
- Sunflower ซึ่งเป็นแอปเดโมที่แสดงแนวทางปฏิบัติแนะนำเกี่ยวกับ คอมโพเนนต์สถาปัตยกรรม