หลักพื้นฐานของ Android Kotlin 06.2: Coroutines และ Room

Codelab นี้เป็นส่วนหนึ่งของหลักสูตรหลักพื้นฐานของ Android Kotlin คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ Codelab ของหลักสูตรทั้งหมดแสดงอยู่ในหน้า Landing Page ของ Codelab หลักพื้นฐานของ Android Kotlin

บทนำ

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

ใน Codelab นี้ คุณจะใช้ส่วนที่ผู้ใช้มองเห็นของแอป TrackMySleepQuality โดยใช้ Kotlin Coroutines เพื่อดำเนินการกับฐานข้อมูลนอกเทรดหลัก

สิ่งที่คุณควรทราบอยู่แล้ว

คุณควรคุ้นเคยกับสิ่งต่อไปนี้

  • การสร้างอินเทอร์เฟซผู้ใช้ (UI) พื้นฐานโดยใช้กิจกรรม Fragment มุมมอง และตัวแฮนเดิลการคลิก
  • การไปยังส่วนย่อยต่างๆ และการใช้ safeArgs เพื่อส่งข้อมูลอย่างง่ายระหว่างส่วนย่อย
  • ดูโมเดล ดูโรงงานโมเดล การเปลี่ยนรูปแบบ และ LiveData
  • วิธีสร้างRoomฐานข้อมูล สร้าง DAO และกําหนดเอนทิตี
  • ซึ่งจะมีประโยชน์หากคุณคุ้นเคยกับแนวคิดการแยกเธรดและการประมวลผลแบบหลายกระบวนการ

สิ่งที่คุณจะได้เรียนรู้

  • วิธีการทำงานของเธรดใน Android
  • วิธีใช้โคโรทีน Kotlin เพื่อย้ายการดำเนินการฐานข้อมูลออกจากเทรดหลัก
  • วิธีแสดงข้อมูลที่จัดรูปแบบใน TextView

สิ่งที่คุณต้องดำเนินการ

  • ขยายแอป TrackMySleepQuality เพื่อรวบรวม จัดเก็บ และแสดงข้อมูลในและจากฐานข้อมูล
  • ใช้โครูทีนเพื่อเรียกใช้การดำเนินการฐานข้อมูลที่ใช้เวลานานในเบื้องหลัง
  • ใช้ LiveData เพื่อทริกเกอร์การนำทางและการแสดงแถบแสดงข้อความ
  • ใช้ LiveData เพื่อเปิดและปิดใช้ปุ่ม

ในโค้ดแล็บนี้ คุณจะได้สร้าง ViewModel, Coroutine และส่วนการแสดงข้อมูลของแอป TrackMySleepQuality

แอปมี 2 หน้าจอซึ่งแสดงด้วย Fragment ดังที่แสดงในรูปภาพด้านล่าง

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

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

โฟลว์ของผู้ใช้มีดังนี้

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

แอปนี้ใช้สถาปัตยกรรมที่เรียบง่าย ดังที่แสดงด้านล่างในบริบทของสถาปัตยกรรมแบบเต็ม แอปใช้เฉพาะคอมโพเนนต์ต่อไปนี้

  • ตัวควบคุม UI
  • ดูโมเดลและ LiveData
  • ฐานข้อมูล Room

ในงานนี้ คุณจะใช้ TextView เพื่อแสดงข้อมูลการติดตามการนอนหลับที่จัดรูปแบบแล้ว (นี่ไม่ใช่อินเทอร์เฟซสุดท้าย คุณจะได้เรียนรู้วิธีที่ดีกว่านี้ใน Codelab อื่น)

คุณสามารถใช้แอป TrackMySleepQuality ที่สร้างใน Codelab ก่อนหน้าต่อไป หรือดาวน์โหลดแอปเริ่มต้นสำหรับ Codelab นี้

ขั้นตอนที่ 1: ดาวน์โหลดและเรียกใช้แอปเริ่มต้น

  1. ดาวน์โหลดแอป TrackMySleepQuality-Coroutines-Starter จาก GitHub
  2. สร้างและเรียกใช้แอป แอปจะแสดง UI สำหรับ SleepTrackerFragment Fragment แต่ไม่มีข้อมูล ปุ่มไม่ตอบสนองต่อการแตะ

ขั้นตอนที่ 2: ตรวจสอบโค้ด

โค้ดเริ่มต้นสำหรับ Codelab นี้เหมือนกับโค้ดโซลูชันสำหรับ Codelab 6.1 สร้างฐานข้อมูล Room

  1. เปิด res/layout/activity_main.xml เลย์เอาต์นี้มี Fragment nav_host_fragment นอกจากนี้ ให้สังเกตแท็ก <merge> ด้วย

    คุณใช้แท็ก merge เพื่อกำจัดเลย์เอาต์ที่ซ้ำซ้อนเมื่อรวมเลย์เอาต์ได้ และควรใช้แท็กนี้ ตัวอย่างเลย์เอาต์ที่ซ้ำซ้อนคือ ConstraintLayout > LinearLayout > TextView ซึ่งระบบอาจกำจัด LinearLayout ได้ การเพิ่มประสิทธิภาพประเภทนี้จะช่วยลดความซับซ้อนของลำดับชั้นของมุมมองและปรับปรุงประสิทธิภาพของแอป
  2. ในโฟลเดอร์ navigation ให้เปิด navigation.xml คุณจะเห็น 2 Fragment และการดำเนินการนำทางที่เชื่อมต่อ Fragment ทั้ง 2
  3. ในโฟลเดอร์เลย์เอาต์ ให้ดับเบิลคลิกที่ Fragment ของเครื่องมือติดตามการนอนหลับเพื่อดูเลย์เอาต์ XML โปรดสังเกตสิ่งต่อไปนี้
  • ระบบจะรวมข้อมูลเลย์เอาต์ไว้ในองค์ประกอบ <layout> เพื่อเปิดใช้การเชื่อมโยงข้อมูล
  • ConstraintLayout และมุมมองอื่นๆ จะจัดเรียงอยู่ภายในองค์ประกอบ <layout>
  • ไฟล์มีแท็กตัวยึดตำแหน่ง <data>

นอกจากนี้ แอปเริ่มต้นยังมีขนาด สี และการจัดรูปแบบสำหรับ UI ด้วย แอปมีRoomฐานข้อมูล, DAO และเอนทิตี SleepNight หากคุณไม่ได้ทำ Codelab ก่อนหน้า โปรดสำรวจลักษณะเหล่านี้ของโค้ดด้วยตนเอง

ตอนนี้คุณมีฐานข้อมูลและ UI แล้ว สิ่งที่คุณต้องทำคือรวบรวมข้อมูล เพิ่มข้อมูลลงในฐานข้อมูล และแสดงข้อมูล การดำเนินการทั้งหมดนี้จะทำใน ViewModel โมเดลมุมมองเครื่องมือติดตามการนอนหลับจะจัดการการคลิกปุ่ม โต้ตอบกับฐานข้อมูลผ่าน DAO และให้ข้อมูลแก่ UI ผ่าน LiveData การดำเนินการฐานข้อมูลทั้งหมดจะต้องทำงานแยกจากเทรด UI หลัก และคุณจะทำเช่นนั้นได้โดยใช้โครูทีน

ขั้นตอนที่ 1: เพิ่ม SleepTrackerViewModel

  1. ในแพ็กเกจ sleeptracker ให้เปิด SleepTrackerViewModel.kt
  2. ตรวจสอบคลาส SleepTrackerViewModel ซึ่งมีให้คุณในแอปเริ่มต้นและแสดงไว้ด้านล่างด้วย โปรดทราบว่าชั้นเรียนจะขยายเวลาเป็น AndroidViewModel() คลาสนี้เหมือนกับ ViewModel แต่จะใช้บริบทของแอปพลิเคชันเป็นพารามิเตอร์และทำให้พร้อมใช้งานเป็นพร็อพเพอร์ตี้ เพราะคุณจะต้องใช้ในภายหลัง
class SleepTrackerViewModel(
       val database: SleepDatabaseDao,
       application: Application) : AndroidViewModel(application) {
}

ขั้นตอนที่ 2: เพิ่ม SleepTrackerViewModelFactory

  1. ในแพ็กเกจ sleeptracker ให้เปิด SleepTrackerViewModelFactory.kt
  2. ตรวจสอบโค้ดที่ระบุไว้สำหรับโรงงาน ซึ่งแสดงอยู่ด้านล่าง
class SleepTrackerViewModelFactory(
       private val dataSource: SleepDatabaseDao,
       private val application: Application) : ViewModelProvider.Factory {
   @Suppress("unchecked_cast")
   override fun <T : ViewModel?> create(modelClass: Class<T>): T {
       if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
           return SleepTrackerViewModel(dataSource, application) as T
       }
       throw IllegalArgumentException("Unknown ViewModel class")
   }
}

ข้อควรทราบมีดังนี้

  • SleepTrackerViewModelFactory ที่ระบุจะใช้อาร์กิวเมนต์เดียวกับ ViewModel และขยาย ViewModelProvider.Factory
  • ภายในโรงงาน โค้ดจะลบล้าง create() ซึ่งรับประเภทคลาสใดก็ได้เป็นอาร์กิวเมนต์และแสดงผล ViewModel
  • ในเนื้อหาของ create() โค้ดจะตรวจสอบว่ามีคลาส SleepTrackerViewModel หรือไม่ หากมี โค้ดจะแสดงผลอินสแตนซ์ของคลาสดังกล่าว มิเช่นนั้นโค้ดจะส่งข้อยกเว้น

ขั้นตอนที่ 3: อัปเดต SleepTrackerFragment

  1. ใน SleepTrackerFragment ให้รับข้อมูลอ้างอิงไปยังบริบทของแอปพลิเคชัน ใส่การอ้างอิงใน onCreateView() ด้านล่าง binding คุณต้องมีการอ้างอิงถึงแอปที่แนบ Fragment นี้ไว้เพื่อส่งไปยังผู้ให้บริการโรงงาน ViewModel

    ฟังก์ชัน requireNotNull Kotlin จะส่ง IllegalArgumentException หาก value เป็น null
val application = requireNotNull(this.activity).application
  1. คุณต้องมีการอ้างอิงถึงแหล่งข้อมูลผ่านการอ้างอิงถึง DAO ใน onCreateView() ก่อน return ให้กำหนด dataSource หากต้องการรับการอ้างอิงไปยัง DAO ของฐานข้อมูล ให้ใช้ SleepDatabase.getInstance(application).sleepDatabaseDao
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. ใน onCreateView() ให้สร้างอินสแตนซ์ของ viewModelFactory ก่อน return คุณต้องส่ง dataSource และ application
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. ตอนนี้คุณมีโรงงานแล้ว ให้รับการอ้างอิงถึง SleepTrackerViewModel พารามิเตอร์ SleepTrackerViewModel::class.java หมายถึงคลาส Java รันไทม์ของออบเจ็กต์นี้
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. โค้ดที่เสร็จสมบูรณ์แล้วควรมีลักษณะดังนี้
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)

onCreateView()วิธีการที่ใช้จนถึงตอนนี้

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Get a reference to the binding object and inflate the fragment views.
        val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_sleep_tracker, container, false)

        val application = requireNotNull(this.activity).application

        val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao

        val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

        val sleepTrackerViewModel =
                ViewModelProviders.of(
                        this, viewModelFactory).get(SleepTrackerViewModel::class.java)

        return binding.root
    }

ขั้นตอนที่ 4: เพิ่มการเชื่อมโยงข้อมูลสำหรับ ViewModel

เมื่อมี ViewModel พื้นฐานแล้ว คุณต้องตั้งค่าการเชื่อมโยงข้อมูลใน SleepTrackerFragment ให้เสร็จสมบูรณ์เพื่อเชื่อมต่อ ViewModel กับ UI


ในfragment_sleep_tracker.xmlไฟล์เลย์เอาต์

  1. ภายในบล็อก <data> ให้สร้าง <variable> ที่อ้างอิงคลาส SleepTrackerViewModel
<data>
   <variable
       name="sleepTrackerViewModel"
       type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>

ใน SleepTrackerFragment

  1. ตั้งค่ากิจกรรมปัจจุบันเป็นเจ้าของวงจรของ Binding เพิ่มโค้ดนี้ภายในเมธอด onCreateView() ก่อนคำสั่ง return
binding.setLifecycleOwner(this)
  1. กำหนดตัวแปรการเชื่อมโยง sleepTrackerViewModel ให้กับ sleepTrackerViewModel วางโค้ดนี้ไว้ใน onCreateView() ใต้โค้ดที่สร้าง SleepTrackerViewModel
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. คุณอาจเห็นข้อผิดพลาดเนื่องจากต้องสร้างออบเจ็กต์การเชื่อมโยงใหม่ ล้างและสร้างโปรเจ็กต์ใหม่เพื่อกำจัดข้อผิดพลาด
  2. สุดท้ายนี้ โปรดตรวจสอบว่าโค้ดของคุณสร้างและทำงานได้โดยไม่มีข้อผิดพลาด

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

โครูทีนมีคุณสมบัติดังนี้

  • โครูทีนเป็นแบบอะซิงโครนัสและไม่บล็อก
  • โครูทีนใช้ฟังก์ชัน suspend เพื่อทำให้โค้ดแบบอะซิงโครนัสเป็นแบบลำดับ

โครูทีนเป็นแบบอะซิงโครนัส

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

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

โครูทีนจะไม่บล็อก

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

โครูทีนใช้ฟังก์ชันระงับเพื่อให้โค้ดแบบอะซิงโครนัสเป็นแบบลำดับ

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

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

คีย์เวิร์ด suspend ไม่ได้ระบุเธรดที่โค้ดทำงาน ฟังก์ชันระงับอาจทำงานในเธรดเบื้องหลังหรือในเธรดหลัก

หากต้องการใช้โครูทีนใน Kotlin คุณต้องมี 3 สิ่งต่อไปนี้

  • งาน
  • ผู้มอบหมายงาน
  • ขอบเขต

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

Dispatcher: Dispatcher จะส่งโครูทีนออกไปเพื่อเรียกใช้ในเธรดต่างๆ เช่น Dispatcher.Main จะเรียกใช้งานในเทรดหลัก และ Dispatcher.IO จะส่งต่อภาระงาน I/O ที่บล็อกไปยังพูลของเทรดที่แชร์

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

คุณต้องการให้ผู้ใช้โต้ตอบกับข้อมูลการนอนหลับได้ด้วยวิธีต่อไปนี้

  • เมื่อผู้ใช้แตะปุ่มเริ่ม แอปจะสร้างการนอนหลับใหม่และจัดเก็บการนอนหลับไว้ในฐานข้อมูล
  • เมื่อผู้ใช้แตะปุ่มหยุด แอปจะอัปเดตเวลากลางคืนด้วยเวลาสิ้นสุด
  • เมื่อผู้ใช้แตะปุ่มล้าง แอปจะลบข้อมูลในฐานข้อมูล

การดำเนินการฐานข้อมูลเหล่านี้อาจใช้เวลานาน ดังนั้นจึงควรเรียกใช้ในเธรดแยกต่างหาก

ขั้นตอนที่ 1: ตั้งค่าโครูทีนสำหรับการดำเนินการกับฐานข้อมูล

เมื่อแตะปุ่มเริ่มในแอปเครื่องมือติดตามการนอนหลับ คุณต้องการเรียกใช้ฟังก์ชันใน SleepTrackerViewModel เพื่อสร้างอินสแตนซ์ใหม่ของ SleepNight และจัดเก็บอินสแตนซ์ในฐานข้อมูล

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

  1. เปิดไฟล์ build.gradle ระดับแอป แล้วค้นหาการขึ้นต่อกันสำหรับโครูทีน หากต้องการใช้โครูทีน คุณต้องมีทรัพยากร Dependency เหล่านี้ ซึ่งเราได้เพิ่มให้คุณแล้ว

    $coroutine_version จะกำหนดไว้ในไฟล์ build.gradle ของโปรเจ็กต์เป็น coroutine_version = '1.0.0'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
  1. เปิดไฟล์ SleepTrackerViewModel
  2. ในเนื้อหาของคลาส ให้กำหนด viewModelJob และกำหนดอินสแตนซ์ของ Job ให้ viewModelJob นี้ช่วยให้คุณยกเลิกโครูทีนทั้งหมดที่ ViewModel นี้เริ่มต้นได้เมื่อไม่ได้ใช้ ViewModel แล้วและถูกทำลาย วิธีนี้จะช่วยให้คุณไม่ต้องมีโครูทีนที่ไม่มีที่ให้กลับ
private var viewModelJob = Job()
  1. ที่ส่วนท้ายของเนื้อหาของคลาส ให้ลบล้าง onCleared() และยกเลิกโครูทีนทั้งหมด เมื่อทำลาย ViewModel แล้ว ระบบจะเรียกใช้ onCleared()
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. กำหนด uiScope สำหรับโครูทีนที่ด้านล่างคำจำกัดความของ viewModelJob ขอบเขตจะกำหนดว่าโครูทีนจะทำงานในเธรดใด และขอบเขตยังต้องทราบเกี่ยวกับงานด้วย หากต้องการรับขอบเขต ให้ขออินสแตนซ์ของ CoroutineScope แล้วส่งตัวจัดสรรและงาน

การใช้ Dispatchers.Main หมายความว่าโครูทีนที่เปิดใช้ใน uiScope จะทำงานในเทรดหลัก ซึ่งเป็นเรื่องที่สมเหตุสมผลสำหรับโครูทีนหลายรายการที่เริ่มต้นโดย ViewModel เนื่องจากหลังจากที่โครูทีนเหล่านี้ทำการประมวลผลบางอย่างแล้ว ก็จะส่งผลให้ UI ได้รับการอัปเดต

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. ใต้คําจํากัดความของ uiScope ให้กําหนดตัวแปรชื่อ tonight เพื่อเก็บคืนปัจจุบัน ทําให้ตัวแปรเป็น MutableLiveData เนื่องจากคุณต้องดูข้อมูลและเปลี่ยนแปลงได้
private var tonight = MutableLiveData<SleepNight?>()
  1. หากต้องการเริ่มต้นตัวแปร tonight โดยเร็วที่สุด ให้สร้างบล็อก init ใต้นิยามของ tonight แล้วเรียกใช้ initializeTonight() คุณกำหนด initializeTonight() ในขั้นตอนถัดไป
init {
   initializeTonight()
}
  1. ใช้ initializeTonight() ใต้ init เปิดใช้โครูทีนใน uiScope ภายใน ให้รับค่าสำหรับ tonight จากฐานข้อมูลโดยการเรียกใช้ getTonightFromDatabase() และกำหนดค่าให้กับ tonight.value คุณกำหนด getTonightFromDatabase() ในขั้นตอนถัดไป
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. นำ getTonightFromDatabase() มาใช้ กำหนดให้เป็นprivate suspendฟังก์ชันที่แสดงผล SleepNight ที่เป็น Null ได้ หากไม่มี SleepNight ที่เริ่มในปัจจุบัน ซึ่งจะทำให้คุณได้รับข้อผิดพลาดเนื่องจากฟังก์ชันต้องแสดงผลบางอย่าง
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. ภายในเนื้อหาฟังก์ชันของ getTonightFromDatabase() ให้ส่งคืนผลลัพธ์จากโครูทีนที่ทำงานในบริบท Dispatchers.IO ใช้ตัวจัดสรร I/O เนื่องจากการรับข้อมูลจากฐานข้อมูลเป็นการดำเนินการ I/O และไม่เกี่ยวข้องกับ UI
  return withContext(Dispatchers.IO) {}
  1. ภายในบล็อกการคืนค่า ให้โคโรทีนรับคืนค่าคืนนี้ (คืนที่ใหม่ที่สุด) จากฐานข้อมูล หากเวลาเริ่มต้นและเวลาสิ้นสุดไม่ตรงกัน แสดงว่าคืนนั้นสิ้นสุดแล้ว ให้ส่งคืน null ไม่เช่นนั้น ให้คืนค่าเป็นกลางคืน
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night

ฟังก์ชันระงับ getTonightFromDatabase() ที่เสร็จสมบูรณ์แล้วควรมีลักษณะดังนี้ ไม่ควรมีข้อผิดพลาดอีก

private suspend fun getTonightFromDatabase(): SleepNight? {
   return withContext(Dispatchers.IO) {
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night
   }
}

ขั้นตอนที่ 2: เพิ่มตัวแฮนเดิลการคลิกสำหรับปุ่มเริ่ม

ตอนนี้คุณสามารถใช้ onStartTracking() ซึ่งเป็นตัวแฮนเดิลการคลิกสำหรับปุ่มเริ่มได้แล้ว คุณต้องสร้าง SleepNight ใหม่ แทรกลงในฐานข้อมูล และกำหนดให้กับ tonight โครงสร้างของ onStartTracking() จะคล้ายกับ initializeTonight() มาก

  1. เริ่มต้นด้วยคำจำกัดความฟังก์ชันสำหรับ onStartTracking() คุณวางตัวแฮนเดิลการคลิกไว้เหนือ onCleared() ในไฟล์ SleepTrackerViewModel ได้
fun onStartTracking() {}
  1. ภายใน onStartTracking() ให้เรียกใช้โครูทีนใน uiScope เนื่องจากคุณต้องใช้ผลลัพธ์นี้เพื่อดำเนินการต่อและอัปเดต UI
uiScope.launch {}
  1. ภายในการเปิดใช้โครูทีน ให้สร้าง SleepNight ใหม่ ซึ่งจะบันทึกเวลาปัจจุบันเป็นเวลาเริ่มต้น
        val newNight = SleepNight()
  1. ใน Coroutine ที่เปิดใช้ ให้เรียกใช้ insert() เพื่อแทรก newNight ลงในฐานข้อมูล คุณจะเห็นข้อผิดพลาดเนื่องจากยังไม่ได้กำหนดฟังก์ชันระงับ insert() นี้ (นี่ไม่ใช่ฟังก์ชัน DAO ที่มีชื่อเดียวกัน)
       insert(newNight)
  1. นอกจากนี้ ให้อัปเดต tonight ภายในการเปิดใช้โครูทีนด้วย
       tonight.value = getTonightFromDatabase()
  1. ด้านล่าง onStartTracking() ให้กำหนด insert() เป็นฟังก์ชัน private suspend ที่รับ SleepNight เป็นอาร์กิวเมนต์
private suspend fun insert(night: SleepNight) {}
  1. สำหรับเนื้อหาของ insert() ให้เปิดใช้โครูทีนในบริบท I/O และแทรกกลางคืนลงในฐานข้อมูลโดยเรียกใช้ insert() จาก DAO
   withContext(Dispatchers.IO) {
       database.insert(night)
   }
  1. ในไฟล์เลย์เอาต์ fragment_sleep_tracker.xml ให้เพิ่มตัวแฮนเดิลการคลิกสำหรับ onStartTracking() ไปยัง start_button โดยใช้ความมหัศจรรย์ของการเชื่อมโยงข้อมูลที่คุณตั้งค่าไว้ก่อนหน้านี้ สัญกรณ์ฟังก์ชัน @{() -> จะสร้างฟังก์ชัน Lambda ที่ไม่มีอาร์กิวเมนต์และเรียกตัวแฮนเดิลการคลิกใน sleepTrackerViewModel
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. สร้างและเรียกใช้แอป แตะปุ่มเริ่ม การดำเนินการนี้จะสร้างข้อมูล แต่คุณจะยังไม่เห็นอะไร คุณจะแก้ไขในภายหลัง
fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

suspend fun suspendFunction() {
   withContext(Dispatchers.IO) {
       longrunningWork()
   }
}

ขั้นตอนที่ 3: แสดงข้อมูล

ใน SleepTrackerViewModel ตัวแปร nights จะอ้างอิง LiveData เนื่องจาก getAllNights() ใน DAO จะแสดงผล LiveData

ซึ่งเป็นRoomฟีเจอร์ที่ทุกครั้งที่ข้อมูลในฐานข้อมูลมีการเปลี่ยนแปลง LiveData nights จะได้รับการอัปเดตเพื่อแสดงข้อมูลล่าสุด คุณไม่จำเป็นต้องตั้งค่า LiveData หรืออัปเดตอย่างชัดเจน Room อัปเดตข้อมูลให้ตรงกับฐานข้อมูล

อย่างไรก็ตาม หากคุณแสดง nights ในมุมมองข้อความ ระบบจะแสดงการอ้างอิงออบเจ็กต์ หากต้องการดูเนื้อหาของออบเจ็กต์ ให้เปลี่ยนข้อมูลเป็นสตริงที่จัดรูปแบบแล้ว ใช้Transformationแมปที่เรียกใช้ทุกครั้งที่ nights ได้รับข้อมูลใหม่จากฐานข้อมูล

  1. เปิดUtil.ktไฟล์และยกเลิกการแสดงความคิดเห็นในโค้ดสำหรับคำจำกัดความของ formatNights()และคำสั่ง import ที่เกี่ยวข้อง หากต้องการยกเลิกการแสดงความคิดเห็นในโค้ดใน Android Studio ให้เลือกโค้ดทั้งหมดที่ทำเครื่องหมายด้วย // แล้วกด Cmd+/ หรือ Control+/
  2. โปรดทราบว่า formatNights() จะแสดงผลประเภท Spanned ซึ่งเป็นสตริงที่จัดรูปแบบ HTML
  3. เปิด strings.xml โปรดสังเกตการใช้ CDATA เพื่อจัดรูปแบบทรัพยากรสตริงสำหรับการแสดงข้อมูลการนอนหลับ
  4. เปิด SleepTrackerViewModel ในคลาส SleepTrackerViewModel ให้กำหนดตัวแปรชื่อ nights ใต้คำจำกัดความของ uiScope รับคืนทุกคืนจากฐานข้อมูลและกำหนดให้กับตัวแปร nights
private val nights = database.getAllNights()
  1. เพิ่มโค้ดเพื่อเปลี่ยน nights เป็น nightsString ใต้นิยามของ nights ใช้ฟังก์ชัน formatNights() จาก Util.kt

    ส่ง nights ไปยังฟังก์ชัน map() จากคลาส Transformations หากต้องการเข้าถึงทรัพยากรสตริง ให้กำหนดฟังก์ชันการแมปเป็นการเรียก formatNights() ระบุ nights และออบเจ็กต์ Resources
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. เปิดไฟล์เลย์เอาต์ fragment_sleep_tracker.xml ใน TextView ในพร็อพเพอร์ตี้ android:text ตอนนี้คุณสามารถแทนที่สตริงทรัพยากรด้วยการอ้างอิงถึง nightsString ได้แล้ว
"@{sleepTrackerViewModel.nightsString}"
  1. สร้างโค้ดใหม่และเรียกใช้แอป ตอนนี้ข้อมูลการนอนหลับทั้งหมดพร้อมเวลาเริ่มต้นควรแสดงแล้ว
  2. แตะปุ่มเริ่มอีก 2-3 ครั้ง แล้วคุณจะเห็นข้อมูลเพิ่มเติม

ในขั้นตอนถัดไป ให้เปิดใช้ฟังก์ชันสำหรับปุ่มหยุด

ขั้นตอนที่ 4: เพิ่มตัวแฮนเดิลการคลิกสำหรับปุ่มหยุด

ใช้รูปแบบเดียวกับในขั้นตอนก่อนหน้า ให้ใช้ตัวแฮนเดิลการคลิกสำหรับปุ่มหยุดใน SleepTrackerViewModel.

  1. เพิ่ม onStopTracking() ไปยัง ViewModel เปิดใช้โครูทีนใน uiScope หากยังไม่ได้ตั้งเวลาสิ้นสุด ให้ตั้งค่า endTimeMilli เป็นเวลาของระบบปัจจุบัน แล้วเรียกใช้ update() ด้วยข้อมูลกลางคืน

    ใน Kotlin ไวยากรณ์ return@label จะระบุฟังก์ชันที่คำสั่งนี้ส่งคืนจากฟังก์ชันที่ซ้อนกันหลายฟังก์ชัน
fun onStopTracking() {
   uiScope.launch {
       val oldNight = tonight.value ?: return@launch
       oldNight.endTimeMilli = System.currentTimeMillis()
       update(oldNight)
   }
}
  1. ใช้ update() โดยใช้รูปแบบเดียวกับที่คุณใช้เพื่อติดตั้งใช้งาน insert()
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. หากต้องการเชื่อมต่อตัวแฮนเดิลการคลิกกับ UI ให้เปิดไฟล์เลย์เอาต์ fragment_sleep_tracker.xml แล้วเพิ่มตัวแฮนเดิลการคลิกลงใน stop_button
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. สร้างและเรียกใช้แอป
  2. แตะเริ่ม แล้วแตะหยุด คุณจะเห็นเวลาเริ่มต้น เวลาสิ้นสุด คุณภาพการนอนหลับที่ไม่มีค่า และเวลานอนหลับ

ขั้นตอนที่ 5: เพิ่มตัวแฮนเดิลการคลิกสำหรับปุ่มล้าง

  1. เช่นเดียวกัน ให้ใช้ onClear() และ clear()
fun onClear() {
   uiScope.launch {
       clear()
       tonight.value = null
   }
}

suspend fun clear() {
   withContext(Dispatchers.IO) {
       database.clear()
   }
}
  1. หากต้องการเชื่อมต่อตัวแฮนเดิลการคลิกกับ UI ให้เปิด fragment_sleep_tracker.xml แล้วเพิ่มตัวแฮนเดิลการคลิกไปยัง clear_button
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. สร้างและเรียกใช้แอป
  2. แตะล้างเพื่อลบข้อมูลทั้งหมด จากนั้นแตะเริ่มและหยุดเพื่อสร้างข้อมูลใหม่

โปรเจ็กต์ Android Studio: TrackMySleepQualityCoroutines

  • ใช้ ViewModel, ViewModelFactory และการเชื่อมโยงข้อมูลเพื่อตั้งค่าสถาปัตยกรรม UI สำหรับแอป
  • ใช้โครูทีนสำหรับงานที่ใช้เวลานาน เช่น การดำเนินการกับฐานข้อมูลทั้งหมด เพื่อให้ UI ทำงานได้อย่างราบรื่น
  • โครูทีนเป็นแบบอะซิงโครนัสและไม่บล็อก โดยใช้suspendฟังก์ชันเพื่อทำให้โค้ดแบบอะซิงโครนัสเป็นแบบลำดับ
  • เมื่อโครูทีนเรียกฟังก์ชันที่มีเครื่องหมาย suspend โครูทีนจะระงับการดำเนินการจนกว่าผลลัพธ์จะพร้อมแทนที่จะบล็อกจนกว่าฟังก์ชันนั้นจะส่งคืนค่าเหมือนกับการเรียกฟังก์ชันปกติ จากนั้นจะกลับมาทำงานต่อจากจุดที่หยุดค้างไว้พร้อมผลลัพธ์
  • ความแตกต่างระหว่างการบล็อกและการระงับคือหากมีการบล็อกเทรด จะไม่มีการทำงานอื่นเกิดขึ้น หากมีการระงับเธรด ระบบจะทำงานอื่นๆ จนกว่าผลลัพธ์จะพร้อมใช้งาน

หากต้องการเปิดใช้โครูทีน คุณต้องมี Job, Dispatcher และ Scope ดังนี้

  • โดยพื้นฐานแล้ว งานคือทุกอย่างที่ยกเลิกได้ ทุกโครูทีนจะมีงาน และคุณสามารถใช้ งาน เพื่อยกเลิกโครูทีนได้
  • Dispatcher จะส่งโครูทีนให้ทำงานในเธรดต่างๆ Dispatcher.Main จะเรียกใช้งานในเทรดหลัก และ Dispartcher.IO ใช้สำหรับส่งต่อภาระงาน I/O ที่บล็อกไปยังพูลของเทรดที่แชร์
  • ขอบเขตจะรวมข้อมูลต่างๆ รวมถึงงานและ Dispatcher เพื่อกำหนดบริบทที่โครูทีนทำงาน ขอบเขตจะติดตามโครูทีน

หากต้องการใช้ตัวแฮนเดิลการคลิกที่ทริกเกอร์การดำเนินการฐานข้อมูล ให้ทำตามรูปแบบนี้

  1. เปิดใช้โครูทีนที่ทำงานในเทรดหลักหรือเทรด UI เนื่องจากผลลัพธ์มีผลต่อ UI
  2. เรียกใช้ฟังก์ชันระงับเพื่อทำงานที่ใช้เวลานาน เพื่อไม่ให้บล็อกเทรด UI ขณะรอผลลัพธ์
  3. งานที่ใช้เวลานานไม่เกี่ยวข้องกับ UI ดังนั้นให้เปลี่ยนไปใช้บริบท I/O วิธีนี้จะช่วยให้งานทำงานใน Thread Pool ที่ได้รับการเพิ่มประสิทธิภาพและจัดไว้สำหรับการดำเนินการประเภทนี้ได้
  4. จากนั้นเรียกใช้ฟังก์ชันฐานข้อมูลเพื่อทำงาน

ใช้Transformationsแผนที่เพื่อสร้างสตริงจากLiveDataออบเจ็กต์ทุกครั้งที่ออบเจ็กต์เปลี่ยนแปลง

หลักสูตร Udacity:

เอกสารประกอบสำหรับนักพัฒนาแอป Android:

เอกสารและบทความอื่นๆ

ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้

  • มอบหมายการบ้านหากจำเป็น
  • สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
  • ให้คะแนนงานการบ้าน

ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม

หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ

ตอบคำถามต่อไปนี้

คำถามที่ 1

ข้อใดต่อไปนี้คือข้อดีของโครูทีน

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

คำถามที่ 2

ฟังก์ชันระงับคืออะไร

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

คำถามที่ 3

การบล็อกและการระงับเธรดแตกต่างกันอย่างไร เลือกทุกข้อที่เป็นจริง

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

เริ่มบทเรียนถัดไป: 6.3 ใช้ LiveData เพื่อควบคุมสถานะปุ่ม

ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab หลักพื้นฐานของ Android Kotlin