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 สำหรับ
SleepTrackerFragment
Fragment แต่ไม่มีข้อมูล ปุ่มไม่ตอบสนองต่อการแตะ
ขั้นตอนที่ 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
ฟังก์ชันrequireNotNull
Kotlin จะส่ง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.Factory
SimpleDateFormat
HtmlCompat
เอกสารและบทความอื่นๆ
- รูปแบบจากโรงงาน
- Codelab ของ Coroutines
- โครูทีน เอกสารประกอบอย่างเป็นทางการ
- บริบทของโครูทีนและตัวจัดส่ง
Dispatchers
- ขับรถเกินขีดจำกัดความเร็วของ Android
Job
launch
- การคืนค่าและการข้ามใน Kotlin
- CDATA ย่อมาจาก character data CDATA หมายความว่าข้อมูลระหว่างสตริงเหล่านี้มีข้อมูลที่อาจตีความเป็นมาร์กอัป XML แต่ไม่ควรเป็นเช่นนั้น
ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้
- มอบหมายการบ้านหากจำเป็น
- สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
- ให้คะแนนงานการบ้าน
ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม
หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ
ตอบคำถามต่อไปนี้
คำถามที่ 1
ข้อใดต่อไปนี้คือข้อดีของโครูทีน
- ไม่บล็อก
- โดยจะทำงานแบบอะซิงโครนัส
- โดยสามารถเรียกใช้ในชุดข้อความอื่นที่ไม่ใช่ชุดข้อความหลัก
- ซึ่งจะช่วยให้แอปทำงานได้เร็วขึ้นเสมอ
- ใช้ข้อยกเว้น
- โดยสามารถเขียนและอ่านเป็นโค้ดเชิงเส้นได้
คำถามที่ 2
ฟังก์ชันระงับคืออะไร
- ฟังก์ชันปกติที่มีคำอธิบายประกอบด้วยคีย์เวิร์ด
suspend
- ฟังก์ชันที่เรียกใช้ภายในโครูทีนได้
- ขณะที่ฟังก์ชันระงับทำงาน เธรดการโทรจะถูกระงับ
- ฟังก์ชันระงับต้องทำงานอยู่เบื้องหลังเสมอ
คำถามที่ 3
การบล็อกและการระงับเธรดแตกต่างกันอย่างไร เลือกทุกข้อที่เป็นจริง
- เมื่อการดำเนินการถูกบล็อก จะไม่สามารถดำเนินการอื่นๆ ในเทรดที่ถูกบล็อกได้
- เมื่อการดำเนินการถูกระงับ เทรดจะทำงานอื่นๆ ได้ในขณะที่รอให้งานที่ส่งไปยังที่อื่นเสร็จสมบูรณ์
- การระงับมีประสิทธิภาพมากกว่า เนื่องจากเธรดอาจไม่ได้รอและไม่ได้ทำอะไรเลย
- ไม่ว่าจะถูกบล็อกหรือระงับ การดำเนินการจะยังคงรอผลลัพธ์ของโครูทีนก่อนที่จะดำเนินการต่อ
เริ่มบทเรียนถัดไป:
ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab หลักพื้นฐานของ Android Kotlin