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: ดาวน์โหลดและเรียกใช้แอปเริ่มต้น
- ดาวน์โหลดแอป TrackMySleepQuality-Coroutines-Starter จาก GitHub
- สร้างและเรียกใช้แอป แอปจะแสดง UI สำหรับ
SleepTrackerFragmentFragment แต่ไม่มีข้อมูล ปุ่มไม่ตอบสนองต่อการแตะ
ขั้นตอนที่ 2: ตรวจสอบโค้ด
โค้ดเริ่มต้นสำหรับ Codelab นี้เหมือนกับโค้ดโซลูชันสำหรับ Codelab 6.1 สร้างฐานข้อมูล Room
- เปิด res/layout/activity_main.xml เลย์เอาต์นี้มี Fragment
nav_host_fragmentนอกจากนี้ ให้สังเกตแท็ก<merge>ด้วย
คุณใช้แท็กmergeเพื่อกำจัดเลย์เอาต์ที่ซ้ำซ้อนเมื่อรวมเลย์เอาต์ได้ และควรใช้แท็กนี้ ตัวอย่างเลย์เอาต์ที่ซ้ำซ้อนคือ ConstraintLayout > LinearLayout > TextView ซึ่งระบบอาจกำจัด LinearLayout ได้ การเพิ่มประสิทธิภาพประเภทนี้จะช่วยลดความซับซ้อนของลำดับชั้นของมุมมองและปรับปรุงประสิทธิภาพของแอป - ในโฟลเดอร์ navigation ให้เปิด navigation.xml คุณจะเห็น 2 Fragment และการดำเนินการนำทางที่เชื่อมต่อ Fragment ทั้ง 2
- ในโฟลเดอร์เลย์เอาต์ ให้ดับเบิลคลิกที่ Fragment ของเครื่องมือติดตามการนอนหลับเพื่อดูเลย์เอาต์ XML โปรดสังเกตสิ่งต่อไปนี้
- ระบบจะรวมข้อมูลเลย์เอาต์ไว้ในองค์ประกอบ
<layout>เพื่อเปิดใช้การเชื่อมโยงข้อมูล ConstraintLayoutและมุมมองอื่นๆ จะจัดเรียงอยู่ภายในองค์ประกอบ<layout>- ไฟล์มีแท็กตัวยึดตำแหน่ง
<data>
นอกจากนี้ แอปเริ่มต้นยังมีขนาด สี และการจัดรูปแบบสำหรับ UI ด้วย แอปมีRoomฐานข้อมูล, DAO และเอนทิตี SleepNight หากคุณไม่ได้ทำ Codelab ก่อนหน้า โปรดสำรวจลักษณะเหล่านี้ของโค้ดด้วยตนเอง
ตอนนี้คุณมีฐานข้อมูลและ UI แล้ว สิ่งที่คุณต้องทำคือรวบรวมข้อมูล เพิ่มข้อมูลลงในฐานข้อมูล และแสดงข้อมูล การดำเนินการทั้งหมดนี้จะทำใน ViewModel โมเดลมุมมองเครื่องมือติดตามการนอนหลับจะจัดการการคลิกปุ่ม โต้ตอบกับฐานข้อมูลผ่าน DAO และให้ข้อมูลแก่ UI ผ่าน LiveData การดำเนินการฐานข้อมูลทั้งหมดจะต้องทำงานแยกจากเทรด UI หลัก และคุณจะทำเช่นนั้นได้โดยใช้โครูทีน
ขั้นตอนที่ 1: เพิ่ม SleepTrackerViewModel
- ในแพ็กเกจ sleeptracker ให้เปิด SleepTrackerViewModel.kt
- ตรวจสอบคลาส
SleepTrackerViewModelซึ่งมีให้คุณในแอปเริ่มต้นและแสดงไว้ด้านล่างด้วย โปรดทราบว่าชั้นเรียนจะขยายเวลาเป็นAndroidViewModel()คลาสนี้เหมือนกับViewModelแต่จะใช้บริบทของแอปพลิเคชันเป็นพารามิเตอร์และทำให้พร้อมใช้งานเป็นพร็อพเพอร์ตี้ เพราะคุณจะต้องใช้ในภายหลัง
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
}ขั้นตอนที่ 2: เพิ่ม SleepTrackerViewModelFactory
- ในแพ็กเกจ sleeptracker ให้เปิด SleepTrackerViewModelFactory.kt
- ตรวจสอบโค้ดที่ระบุไว้สำหรับโรงงาน ซึ่งแสดงอยู่ด้านล่าง
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
- ใน
SleepTrackerFragmentให้รับข้อมูลอ้างอิงไปยังบริบทของแอปพลิเคชัน ใส่การอ้างอิงในonCreateView()ด้านล่างbindingคุณต้องมีการอ้างอิงถึงแอปที่แนบ Fragment นี้ไว้เพื่อส่งไปยังผู้ให้บริการโรงงาน ViewModel
ฟังก์ชันrequireNotNullKotlin จะส่งIllegalArgumentExceptionหาก value เป็นnull
val application = requireNotNull(this.activity).application- คุณต้องมีการอ้างอิงถึงแหล่งข้อมูลผ่านการอ้างอิงถึง DAO ใน
onCreateView()ก่อนreturnให้กำหนดdataSourceหากต้องการรับการอ้างอิงไปยัง DAO ของฐานข้อมูล ให้ใช้SleepDatabase.getInstance(application).sleepDatabaseDao
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao- ใน
onCreateView()ให้สร้างอินสแตนซ์ของviewModelFactoryก่อนreturnคุณต้องส่งdataSourceและapplication
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)- ตอนนี้คุณมีโรงงานแล้ว ให้รับการอ้างอิงถึง
SleepTrackerViewModelพารามิเตอร์SleepTrackerViewModel::class.javaหมายถึงคลาส Java รันไทม์ของออบเจ็กต์นี้
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)- โค้ดที่เสร็จสมบูรณ์แล้วควรมีลักษณะดังนี้
// 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ไฟล์เลย์เอาต์
- ภายในบล็อก
<data>ให้สร้าง<variable>ที่อ้างอิงคลาสSleepTrackerViewModel
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>ใน SleepTrackerFragment
- ตั้งค่ากิจกรรมปัจจุบันเป็นเจ้าของวงจรของ Binding เพิ่มโค้ดนี้ภายในเมธอด
onCreateView()ก่อนคำสั่งreturn
binding.setLifecycleOwner(this)- กำหนดตัวแปรการเชื่อมโยง
sleepTrackerViewModelให้กับsleepTrackerViewModelวางโค้ดนี้ไว้ในonCreateView()ใต้โค้ดที่สร้างSleepTrackerViewModel
binding.sleepTrackerViewModel = sleepTrackerViewModel- คุณอาจเห็นข้อผิดพลาดเนื่องจากต้องสร้างออบเจ็กต์การเชื่อมโยงใหม่ ล้างและสร้างโปรเจ็กต์ใหม่เพื่อกำจัดข้อผิดพลาด
- สุดท้ายนี้ โปรดตรวจสอบว่าโค้ดของคุณสร้างและทำงานได้โดยไม่มีข้อผิดพลาด
ใน Kotlin คอรุทินเป็นวิธีจัดการงานที่ใช้เวลานานได้อย่างราบรื่นและมีประสิทธิภาพ โคโรทีน Kotlin ช่วยให้คุณแปลงโค้ดที่อิงตามการเรียกกลับเป็นโค้ดแบบลำดับได้ โดยปกติแล้ว โค้ดที่เขียนตามลำดับจะอ่านง่ายกว่า และยังใช้ฟีเจอร์ภาษา เช่น ข้อยกเว้น ได้ด้วย สุดท้ายแล้ว โครูทีนและโค้ดเรียกกลับก็ทำหน้าที่เดียวกัน นั่นคือรอจนกว่าจะมีผลลัพธ์จากงานที่ใช้เวลานานและดำเนินการต่อ

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

หากต้องการใช้โครูทีนใน Kotlin คุณต้องมี 3 สิ่งต่อไปนี้
- งาน
- ผู้มอบหมายงาน
- ขอบเขต
งาน: โดยพื้นฐานแล้ว งานคือสิ่งที่ยกเลิกได้ ทุกโครูทีนจะมีงาน และคุณสามารถใช้ งาน เพื่อยกเลิกโครูทีนได้ คุณจัดเรียงงานเป็นลำดับชั้นแบบระดับบน-ย่อยได้ การยกเลิกงานหลักจะยกเลิกงานย่อยทั้งหมดของงานนั้นทันที ซึ่งสะดวกกว่าการยกเลิกแต่ละโครูทีนด้วยตนเองมาก
Dispatcher: Dispatcher จะส่งโครูทีนออกไปเพื่อเรียกใช้ในเธรดต่างๆ เช่น Dispatcher.Main จะเรียกใช้งานในเทรดหลัก และ Dispatcher.IO จะส่งต่อภาระงาน I/O ที่บล็อกไปยังพูลของเทรดที่แชร์
ขอบเขต: ขอบเขตของโครูทีนจะกำหนดบริบทที่โครูทีนทำงาน ขอบเขตจะรวมข้อมูลเกี่ยวกับงานและตัวจัดส่งของโครูทีน ขอบเขตจะติดตามโครูทีน เมื่อเปิดใช้โครูทีน โครูทีนจะ "อยู่ในขอบเขต" ซึ่งหมายความว่าคุณได้ระบุขอบเขตที่จะติดตามโครูทีนแล้ว
คุณต้องการให้ผู้ใช้โต้ตอบกับข้อมูลการนอนหลับได้ด้วยวิธีต่อไปนี้
- เมื่อผู้ใช้แตะปุ่มเริ่ม แอปจะสร้างการนอนหลับใหม่และจัดเก็บการนอนหลับไว้ในฐานข้อมูล
- เมื่อผู้ใช้แตะปุ่มหยุด แอปจะอัปเดตเวลากลางคืนด้วยเวลาสิ้นสุด
- เมื่อผู้ใช้แตะปุ่มล้าง แอปจะลบข้อมูลในฐานข้อมูล
การดำเนินการฐานข้อมูลเหล่านี้อาจใช้เวลานาน ดังนั้นจึงควรเรียกใช้ในเธรดแยกต่างหาก
ขั้นตอนที่ 1: ตั้งค่าโครูทีนสำหรับการดำเนินการกับฐานข้อมูล
เมื่อแตะปุ่มเริ่มในแอปเครื่องมือติดตามการนอนหลับ คุณต้องการเรียกใช้ฟังก์ชันใน SleepTrackerViewModel เพื่อสร้างอินสแตนซ์ใหม่ของ SleepNight และจัดเก็บอินสแตนซ์ในฐานข้อมูล
การแตะปุ่มใดก็ตามจะทริกเกอร์การดำเนินการในฐานข้อมูล เช่น การสร้างหรืออัปเดต SleepNight ด้วยเหตุผลนี้และเหตุผลอื่นๆ คุณจึงใช้โครูทีนเพื่อใช้ตัวแฮนเดิลการคลิกสำหรับปุ่มของแอป
- เปิดไฟล์
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"
- เปิดไฟล์
SleepTrackerViewModel - ในเนื้อหาของคลาส ให้กำหนด
viewModelJobและกำหนดอินสแตนซ์ของJobให้viewModelJobนี้ช่วยให้คุณยกเลิกโครูทีนทั้งหมดที่ ViewModel นี้เริ่มต้นได้เมื่อไม่ได้ใช้ ViewModel แล้วและถูกทำลาย วิธีนี้จะช่วยให้คุณไม่ต้องมีโครูทีนที่ไม่มีที่ให้กลับ
private var viewModelJob = Job()- ที่ส่วนท้ายของเนื้อหาของคลาส ให้ลบล้าง
onCleared()และยกเลิกโครูทีนทั้งหมด เมื่อทำลายViewModelแล้ว ระบบจะเรียกใช้onCleared()
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}- กำหนด
uiScopeสำหรับโครูทีนที่ด้านล่างคำจำกัดความของviewModelJobขอบเขตจะกำหนดว่าโครูทีนจะทำงานในเธรดใด และขอบเขตยังต้องทราบเกี่ยวกับงานด้วย หากต้องการรับขอบเขต ให้ขออินสแตนซ์ของCoroutineScopeแล้วส่งตัวจัดสรรและงาน
การใช้ Dispatchers.Main หมายความว่าโครูทีนที่เปิดใช้ใน uiScope จะทำงานในเทรดหลัก ซึ่งเป็นเรื่องที่สมเหตุสมผลสำหรับโครูทีนหลายรายการที่เริ่มต้นโดย ViewModel เนื่องจากหลังจากที่โครูทีนเหล่านี้ทำการประมวลผลบางอย่างแล้ว ก็จะส่งผลให้ UI ได้รับการอัปเดต
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)- ใต้คําจํากัดความของ
uiScopeให้กําหนดตัวแปรชื่อtonightเพื่อเก็บคืนปัจจุบัน ทําให้ตัวแปรเป็นMutableLiveDataเนื่องจากคุณต้องดูข้อมูลและเปลี่ยนแปลงได้
private var tonight = MutableLiveData<SleepNight?>()- หากต้องการเริ่มต้นตัวแปร
tonightโดยเร็วที่สุด ให้สร้างบล็อกinitใต้นิยามของtonightแล้วเรียกใช้initializeTonight()คุณกำหนดinitializeTonight()ในขั้นตอนถัดไป
init {
initializeTonight()
}- ใช้
initializeTonight()ใต้initเปิดใช้โครูทีนในuiScopeภายใน ให้รับค่าสำหรับtonightจากฐานข้อมูลโดยการเรียกใช้getTonightFromDatabase()และกำหนดค่าให้กับtonight.valueคุณกำหนดgetTonightFromDatabase()ในขั้นตอนถัดไป
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}- นำ
getTonightFromDatabase()มาใช้ กำหนดให้เป็นprivate suspendฟังก์ชันที่แสดงผลSleepNightที่เป็น Null ได้ หากไม่มีSleepNightที่เริ่มในปัจจุบัน ซึ่งจะทำให้คุณได้รับข้อผิดพลาดเนื่องจากฟังก์ชันต้องแสดงผลบางอย่าง
private suspend fun getTonightFromDatabase(): SleepNight? { }- ภายในเนื้อหาฟังก์ชันของ
getTonightFromDatabase()ให้ส่งคืนผลลัพธ์จากโครูทีนที่ทำงานในบริบทDispatchers.IOใช้ตัวจัดสรร I/O เนื่องจากการรับข้อมูลจากฐานข้อมูลเป็นการดำเนินการ I/O และไม่เกี่ยวข้องกับ UI
return withContext(Dispatchers.IO) {}- ภายในบล็อกการคืนค่า ให้โคโรทีนรับคืนค่าคืนนี้ (คืนที่ใหม่ที่สุด) จากฐานข้อมูล หากเวลาเริ่มต้นและเวลาสิ้นสุดไม่ตรงกัน แสดงว่าคืนนั้นสิ้นสุดแล้ว ให้ส่งคืน
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() มาก
- เริ่มต้นด้วยคำจำกัดความฟังก์ชันสำหรับ
onStartTracking()คุณวางตัวแฮนเดิลการคลิกไว้เหนือonCleared()ในไฟล์SleepTrackerViewModelได้
fun onStartTracking() {}- ภายใน
onStartTracking()ให้เรียกใช้โครูทีนในuiScopeเนื่องจากคุณต้องใช้ผลลัพธ์นี้เพื่อดำเนินการต่อและอัปเดต UI
uiScope.launch {}- ภายในการเปิดใช้โครูทีน ให้สร้าง
SleepNightใหม่ ซึ่งจะบันทึกเวลาปัจจุบันเป็นเวลาเริ่มต้น
val newNight = SleepNight()- ใน Coroutine ที่เปิดใช้ ให้เรียกใช้
insert()เพื่อแทรกnewNightลงในฐานข้อมูล คุณจะเห็นข้อผิดพลาดเนื่องจากยังไม่ได้กำหนดฟังก์ชันระงับinsert()นี้ (นี่ไม่ใช่ฟังก์ชัน DAO ที่มีชื่อเดียวกัน)
insert(newNight)
- นอกจากนี้ ให้อัปเดต
tonightภายในการเปิดใช้โครูทีนด้วย
tonight.value = getTonightFromDatabase()- ด้านล่าง
onStartTracking()ให้กำหนดinsert()เป็นฟังก์ชันprivate suspendที่รับSleepNightเป็นอาร์กิวเมนต์
private suspend fun insert(night: SleepNight) {}- สำหรับเนื้อหาของ
insert()ให้เปิดใช้โครูทีนในบริบท I/O และแทรกกลางคืนลงในฐานข้อมูลโดยเรียกใช้insert()จาก DAO
withContext(Dispatchers.IO) {
database.insert(night)
}- ในไฟล์เลย์เอาต์
fragment_sleep_tracker.xmlให้เพิ่มตัวแฮนเดิลการคลิกสำหรับonStartTracking()ไปยังstart_buttonโดยใช้ความมหัศจรรย์ของการเชื่อมโยงข้อมูลที่คุณตั้งค่าไว้ก่อนหน้านี้ สัญกรณ์ฟังก์ชัน@{() ->จะสร้างฟังก์ชัน Lambda ที่ไม่มีอาร์กิวเมนต์และเรียกตัวแฮนเดิลการคลิกในsleepTrackerViewModel
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"- สร้างและเรียกใช้แอป แตะปุ่มเริ่ม การดำเนินการนี้จะสร้างข้อมูล แต่คุณจะยังไม่เห็นอะไร คุณจะแก้ไขในภายหลัง
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 ได้รับข้อมูลใหม่จากฐานข้อมูล
- เปิด
Util.ktไฟล์และยกเลิกการแสดงความคิดเห็นในโค้ดสำหรับคำจำกัดความของformatNights()และคำสั่งimportที่เกี่ยวข้อง หากต้องการยกเลิกการแสดงความคิดเห็นในโค้ดใน Android Studio ให้เลือกโค้ดทั้งหมดที่ทำเครื่องหมายด้วย//แล้วกดCmd+/หรือControl+/ - โปรดทราบว่า
formatNights()จะแสดงผลประเภทSpannedซึ่งเป็นสตริงที่จัดรูปแบบ HTML - เปิด strings.xml โปรดสังเกตการใช้
CDATAเพื่อจัดรูปแบบทรัพยากรสตริงสำหรับการแสดงข้อมูลการนอนหลับ - เปิด SleepTrackerViewModel ในคลาส
SleepTrackerViewModelให้กำหนดตัวแปรชื่อnightsใต้คำจำกัดความของuiScopeรับคืนทุกคืนจากฐานข้อมูลและกำหนดให้กับตัวแปรnights
private val nights = database.getAllNights()- เพิ่มโค้ดเพื่อเปลี่ยน
nightsเป็นnightsStringใต้นิยามของnightsใช้ฟังก์ชันformatNights()จากUtil.kt
ส่งnightsไปยังฟังก์ชันmap()จากคลาสTransformationsหากต้องการเข้าถึงทรัพยากรสตริง ให้กำหนดฟังก์ชันการแมปเป็นการเรียกformatNights()ระบุnightsและออบเจ็กต์Resources
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}- เปิดไฟล์เลย์เอาต์
fragment_sleep_tracker.xmlในTextViewในพร็อพเพอร์ตี้android:textตอนนี้คุณสามารถแทนที่สตริงทรัพยากรด้วยการอ้างอิงถึงnightsStringได้แล้ว
"@{sleepTrackerViewModel.nightsString}"- สร้างโค้ดใหม่และเรียกใช้แอป ตอนนี้ข้อมูลการนอนหลับทั้งหมดพร้อมเวลาเริ่มต้นควรแสดงแล้ว
- แตะปุ่มเริ่มอีก 2-3 ครั้ง แล้วคุณจะเห็นข้อมูลเพิ่มเติม

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

ขั้นตอนที่ 5: เพิ่มตัวแฮนเดิลการคลิกสำหรับปุ่มล้าง
- เช่นเดียวกัน ให้ใช้
onClear()และclear()
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}- หากต้องการเชื่อมต่อตัวแฮนเดิลการคลิกกับ UI ให้เปิด
fragment_sleep_tracker.xmlแล้วเพิ่มตัวแฮนเดิลการคลิกไปยังclear_button
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"- สร้างและเรียกใช้แอป
- แตะล้างเพื่อลบข้อมูลทั้งหมด จากนั้นแตะเริ่มและหยุดเพื่อสร้างข้อมูลใหม่
โปรเจ็กต์ Android Studio: TrackMySleepQualityCoroutines
- ใช้
ViewModel,ViewModelFactoryและการเชื่อมโยงข้อมูลเพื่อตั้งค่าสถาปัตยกรรม UI สำหรับแอป - ใช้โครูทีนสำหรับงานที่ใช้เวลานาน เช่น การดำเนินการกับฐานข้อมูลทั้งหมด เพื่อให้ UI ทำงานได้อย่างราบรื่น
- โครูทีนเป็นแบบอะซิงโครนัสและไม่บล็อก โดยใช้
suspendฟังก์ชันเพื่อทำให้โค้ดแบบอะซิงโครนัสเป็นแบบลำดับ - เมื่อโครูทีนเรียกฟังก์ชันที่มีเครื่องหมาย
suspendโครูทีนจะระงับการดำเนินการจนกว่าผลลัพธ์จะพร้อมแทนที่จะบล็อกจนกว่าฟังก์ชันนั้นจะส่งคืนค่าเหมือนกับการเรียกฟังก์ชันปกติ จากนั้นจะกลับมาทำงานต่อจากจุดที่หยุดค้างไว้พร้อมผลลัพธ์ - ความแตกต่างระหว่างการบล็อกและการระงับคือหากมีการบล็อกเทรด จะไม่มีการทำงานอื่นเกิดขึ้น หากมีการระงับเธรด ระบบจะทำงานอื่นๆ จนกว่าผลลัพธ์จะพร้อมใช้งาน
หากต้องการเปิดใช้โครูทีน คุณต้องมี Job, Dispatcher และ Scope ดังนี้
- โดยพื้นฐานแล้ว งานคือทุกอย่างที่ยกเลิกได้ ทุกโครูทีนจะมีงาน และคุณสามารถใช้ งาน เพื่อยกเลิกโครูทีนได้
- Dispatcher จะส่งโครูทีนให้ทำงานในเธรดต่างๆ
Dispatcher.Mainจะเรียกใช้งานในเทรดหลัก และDispartcher.IOใช้สำหรับส่งต่อภาระงาน I/O ที่บล็อกไปยังพูลของเทรดที่แชร์ - ขอบเขตจะรวมข้อมูลต่างๆ รวมถึงงานและ Dispatcher เพื่อกำหนดบริบทที่โครูทีนทำงาน ขอบเขตจะติดตามโครูทีน
หากต้องการใช้ตัวแฮนเดิลการคลิกที่ทริกเกอร์การดำเนินการฐานข้อมูล ให้ทำตามรูปแบบนี้
- เปิดใช้โครูทีนที่ทำงานในเทรดหลักหรือเทรด UI เนื่องจากผลลัพธ์มีผลต่อ UI
- เรียกใช้ฟังก์ชันระงับเพื่อทำงานที่ใช้เวลานาน เพื่อไม่ให้บล็อกเทรด UI ขณะรอผลลัพธ์
- งานที่ใช้เวลานานไม่เกี่ยวข้องกับ UI ดังนั้นให้เปลี่ยนไปใช้บริบท I/O วิธีนี้จะช่วยให้งานทำงานใน Thread Pool ที่ได้รับการเพิ่มประสิทธิภาพและจัดไว้สำหรับการดำเนินการประเภทนี้ได้
- จากนั้นเรียกใช้ฟังก์ชันฐานข้อมูลเพื่อทำงาน
ใช้Transformationsแผนที่เพื่อสร้างสตริงจากLiveDataออบเจ็กต์ทุกครั้งที่ออบเจ็กต์เปลี่ยนแปลง
หลักสูตร Udacity:
เอกสารประกอบสำหรับนักพัฒนาแอป Android:
RoomDatabase- การใช้เลย์เอาต์ซ้ำด้วย <include/>
ViewModelProvider.FactorySimpleDateFormatHtmlCompat
เอกสารและบทความอื่นๆ
- รูปแบบจากโรงงาน
- Codelab ของ Coroutines
- โครูทีน เอกสารประกอบอย่างเป็นทางการ
- บริบทของโครูทีนและตัวจัดส่ง
Dispatchers- ขับรถเกินขีดจำกัดความเร็วของ Android
Joblaunch- การคืนค่าและการข้ามใน Kotlin
- CDATA ย่อมาจาก character data CDATA หมายความว่าข้อมูลระหว่างสตริงเหล่านี้มีข้อมูลที่อาจตีความเป็นมาร์กอัป XML แต่ไม่ควรเป็นเช่นนั้น
ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้
- มอบหมายการบ้านหากจำเป็น
- สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
- ให้คะแนนงานการบ้าน
ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม
หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ
ตอบคำถามต่อไปนี้
คำถามที่ 1
ข้อใดต่อไปนี้คือข้อดีของโครูทีน
- ไม่บล็อก
- โดยจะทำงานแบบอะซิงโครนัส
- โดยสามารถเรียกใช้ในชุดข้อความอื่นที่ไม่ใช่ชุดข้อความหลัก
- ซึ่งจะช่วยให้แอปทำงานได้เร็วขึ้นเสมอ
- ใช้ข้อยกเว้น
- โดยสามารถเขียนและอ่านเป็นโค้ดเชิงเส้นได้
คำถามที่ 2
ฟังก์ชันระงับคืออะไร
- ฟังก์ชันปกติที่มีคำอธิบายประกอบด้วยคีย์เวิร์ด
suspend - ฟังก์ชันที่เรียกใช้ภายในโครูทีนได้
- ขณะที่ฟังก์ชันระงับทำงาน เธรดการโทรจะถูกระงับ
- ฟังก์ชันระงับต้องทำงานอยู่เบื้องหลังเสมอ
คำถามที่ 3
การบล็อกและการระงับเธรดแตกต่างกันอย่างไร เลือกทุกข้อที่เป็นจริง
- เมื่อการดำเนินการถูกบล็อก จะไม่สามารถดำเนินการอื่นๆ ในเทรดที่ถูกบล็อกได้
- เมื่อการดำเนินการถูกระงับ เทรดจะทำงานอื่นๆ ได้ในขณะที่รอให้งานที่ส่งไปยังที่อื่นเสร็จสมบูรณ์
- การระงับมีประสิทธิภาพมากกว่า เนื่องจากเธรดอาจไม่ได้รอและไม่ได้ทำอะไรเลย
- ไม่ว่าจะถูกบล็อกหรือระงับ การดำเนินการจะยังคงรอผลลัพธ์ของโครูทีนก่อนที่จะดำเนินการต่อ
เริ่มบทเรียนถัดไป:
ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab หลักพื้นฐานของ Android Kotlin