coroutine คือรูปแบบการออกแบบการเกิดขึ้นพร้อมกันที่คุณสามารถใช้ได้ Android เพื่อลดความซับซ้อนของโค้ดที่ทำงานแบบไม่พร้อมกัน Coroutines ได้รับการเพิ่มลงใน Kotlin เวอร์ชัน 1.3 และอิงตาม มาจากภาษาอื่นๆ
สำหรับ Android โครูทีนจะช่วยจัดการงานที่ต้องทำเป็นเวลานาน ซึ่งอาจ ไม่เช่นนั้นให้บล็อกเทรดหลัก และทำให้แอปไม่ตอบสนอง นักพัฒนาซอฟต์แวร์มืออาชีพกว่า 50% ที่ใช้โครูทีนได้รายงานว่าพบว่า ประสิทธิภาพการทำงานที่เพิ่มขึ้น หัวข้อนี้จะอธิบายวิธีใช้โครูทีน Kotlin เพื่อจัดการกับสิ่งเหล่านี้ โดยช่วยให้คุณเขียนโค้ดของแอปที่ดูสะอาดตาและกระชับยิ่งขึ้น
ฟีเจอร์
Coroutines เป็นโซลูชันที่เราแนะนำให้ใช้สำหรับการเขียนโปรแกรมแบบไม่พร้อมกันบน Android ฟีเจอร์ที่น่าสนใจมีดังต่อไปนี้
- มีขนาดเล็ก: คุณสามารถเรียกใช้โครูทีนหลายรายการในชุดข้อความเดียวได้เนื่องจากเหตุผลต่อไปนี้ การสนับสนุนสำหรับ การระงับ ซึ่งไม่บล็อกเทรดที่โครูทีนทำงานอยู่ กำลังระงับ ประหยัดหน่วยความจำหลังการบล็อก ในขณะเดียวกันก็รองรับการทำงานพร้อมกันหลายรายการ
- หน่วยความจำรั่วไหลน้อยลง: ใช้ การเกิดขึ้นพร้อมกันที่มีโครงสร้าง ในการดำเนินการภายในขอบเขต
- การสนับสนุนการยกเลิกในตัว: การยกเลิก เผยแพร่โดยอัตโนมัติผ่านลำดับชั้น Coroutine ที่กำลังทำงานอยู่
- การผสานรวม Jetpack: ไลบรารี Jetpack จำนวนมากประกอบด้วย ส่วนขยายที่ช่วยรองรับโครูทีนเต็มรูปแบบ ใช้บ้าง ห้องสมุดยังมีหมวดหมู่ของตนเอง ขอบเขต Coroutine ที่คุณสามารถ เพื่อใช้การเกิดขึ้นพร้อมกันแบบมีโครงสร้าง
ภาพรวมตัวอย่าง
ตัวอย่างตามคู่มือสถาปัตยกรรมแอป ในหัวข้อนี้ ให้ส่งคำขอเครือข่ายและส่งคืนผลลัพธ์ไปยัง เทรด ซึ่งแอปสามารถแสดงผลลัพธ์ให้กับผู้ใช้
กล่าวอย่างเจาะจงคือ ViewModel
คอมโพเนนต์สถาปัตยกรรมจะเรียกใช้เลเยอร์ที่เก็บบนเทรดหลักเพื่อ
เรียกใช้คำขอเครือข่าย คู่มือนี้จะทบทวนวิธีแก้ปัญหาต่างๆ
ที่ใช้โครูทีนเพื่อเลิกบล็อกเทรดหลัก
ViewModel
มีชุดส่วนขยาย KTX ที่ทํางานร่วมกับ
โครูทีน ส่วนขยายเหล่านี้คือ
คลัง lifecycle-viewmodel-ktx
และมีการนำไปใช้แล้ว
ในคู่มือนี้
ข้อมูลการขึ้นต่อกัน
หากต้องการใช้โครูทีนในโปรเจ็กต์ Android ให้เพิ่มทรัพยากร Dependency ต่อไปนี้
ลงในไฟล์ build.gradle
ของแอป
ดึงดูด
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") }
กำลังดำเนินการในชุดข้อความเบื้องหลัง
การส่งคำขอเครือข่ายในเทรดหลักจะทำให้เทรดดังกล่าวรอหรือบล็อก
จนกว่าจะได้รับคำตอบ เนื่องจากเทรดถูกบล็อก ระบบปฏิบัติการจึงไม่
สามารถโทรหา onDraw()
ซึ่งทำให้แอปค้างและอาจ
จะไปยังกล่องโต้ตอบ "แอปพลิเคชันไม่ตอบสนอง" (ANR) เพื่อผู้ใช้ที่ดียิ่งขึ้น
ให้เรียกใช้การดำเนินการนี้กับเทรดในเบื้องหลัง
ก่อนอื่น เราจะมาดูชั้นเรียน Repository
ของเราและดูว่าเป็นอย่างไร
ส่งคำขอเครือข่าย:
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
class LoginRepository(private val responseParser: LoginResponseParser) {
private const val loginUrl = "https://example.com/login"
// Function that makes the network request, blocking the current thread
fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
val url = URL(loginUrl)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json; utf-8")
setRequestProperty("Accept", "application/json")
doOutput = true
outputStream.write(jsonBody.toByteArray())
return Result.Success(responseParser.parse(inputStream))
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}
makeLoginRequest
เป็นแบบพร้อมกันและบล็อกชุดข้อความการโทร เป็นโมเดล
การตอบสนองต่อคำขอเครือข่าย เรามีคลาส Result
เป็นของเราเอง
ViewModel
จะเรียกใช้คำขอเครือข่ายเมื่อผู้ใช้คลิก
ตัวอย่างเช่น บนปุ่ม
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
เมื่อใช้โค้ดก่อนหน้า LoginViewModel
จะบล็อกเทรด UI เมื่อ
ในการส่งคำขอเครือข่าย โซลูชันที่ง่ายที่สุดในการย้ายการดำเนินการ
ปิดเทรดหลักคือการสร้างโครูทีนใหม่และเรียกใช้เครือข่าย
คำขอในชุดข้อความ I/O
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine to move the execution off the UI thread
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
มาวิเคราะห์โค้ดโครูทีนในฟังก์ชัน login
กัน
viewModelScope
เป็นCoroutineScope
ที่กำหนดไว้ล่วงหน้าซึ่งรวมอยู่กับ ส่วนขยาย KTXViewModel
โปรดทราบว่าโครูทีนทั้งหมดต้องทํางานในCoroutineScope
จะจัดการโครูทีนที่เกี่ยวข้องอย่างน้อย 1 รายการlaunch
คือฟังก์ชันที่สร้างโคโรทีนและส่งฟังก์ชัน ของส่วนการทำงานของฟังก์ชันไปยังผู้มอบหมายงานที่เกี่ยวข้องDispatchers.IO
บ่งชี้ว่าโครูทีนนี้ควรเรียกใช้ใน เทรดที่สงวนไว้สำหรับการดำเนินการ I/O
ฟังก์ชัน login
จะดำเนินการดังต่อไปนี้
- แอปเรียกฟังก์ชัน
login
จากเลเยอร์View
ในเทรดหลัก launch
สร้างโครูทีนใหม่และสร้างคำขอเครือข่าย แยกต่างหากในเทรดที่สงวนไว้สำหรับการดำเนินการ I/O- ขณะที่โครูทีนทำงานอยู่ ฟังก์ชัน
login
จะดำเนินการต่อไป และส่งกลับ ซึ่งเป็นไปได้ว่าก่อนที่คำขอเครือข่ายจะเสร็จสิ้น โปรดทราบว่า เพื่อความง่าย ตอนนี้ระบบจะไม่สนใจการตอบสนองของเครือข่าย
เนื่องจาก coroutine นี้เริ่มต้นด้วย viewModelScope
จึงมีการดำเนินการใน
ขอบเขตของ ViewModel
หาก ViewModel
ถูกทำลายเนื่องจาก
ผู้ใช้ออกจากหน้าจอไป viewModelScope
ทำงานโดยอัตโนมัติ
ยกเลิกแล้ว และระบบก็จะยกเลิกโครูทีนทั้งหมดที่ทำงานอยู่ด้วย
ปัญหาหนึ่งในตัวอย่างก่อนหน้านี้คือ
makeLoginRequest
ต้องจดจำว่าต้องปิดการดำเนินการอย่างชัดเจน
เทรดหลัก มาดูกันว่าเราจะแก้ไข Repository
เพื่อแก้โจทย์
ปัญหานี้ให้กับเรา
ใช้โครูทีนเพื่อความปลอดภัยหลัก
เราจะถือว่าฟังก์ชันปลอดภัยหลักเมื่อไม่บล็อกการอัปเดต UI ใน
เทรดหลัก ฟังก์ชัน makeLoginRequest
ไม่ปลอดภัยหลักเนื่องจากการโทร
makeLoginRequest
จากเทรดหลักจะบล็อก UI ใช้เมนู
ฟังก์ชัน withContext()
จากไลบรารี Coroutines สำหรับย้ายการดำเนินการ
โครูทีนไปยังชุดข้อความอื่น
class LoginRepository(...) {
...
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// Move the execution of the coroutine to the I/O dispatcher
return withContext(Dispatchers.IO) {
// Blocking network request code
}
}
}
withContext(Dispatchers.IO)
ย้ายการดำเนินการกับโครูทีนไปยัง
เทรด I/O ทำให้ฟังก์ชันการเรียกใช้ของเราปลอดภัย และทำให้ UI ใช้งานได้
อัปเดตตามที่จำเป็น
และ makeLoginRequest
จะมีคีย์เวิร์ด suspend
กำกับอยู่ คีย์เวิร์ดนี้
คือวิธีบังคับใช้ฟังก์ชันของ Kotlin ที่จะเรียกใช้จากภายในโครูทีน
ในตัวอย่างต่อไปนี้ โคโรทีนจะสร้างขึ้นใน LoginViewModel
เมื่อ makeLoginRequest
ย้ายการดำเนินการออกจากเทรดหลัก โครูทีน
เรียกใช้ในฟังก์ชัน login
ได้แล้วในเทรดหลัก
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine on the UI thread
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
// Make the network call and suspend execution until it finishes
val result = loginRepository.makeLoginRequest(jsonBody)
// Display result of the network request to the user
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
โปรดทราบว่ายังต้องมีโครูทีนที่นี่ เนื่องจาก makeLoginRequest
มีค่า
ฟังก์ชัน suspend
และต้องเรียกใช้ฟังก์ชัน suspend
ทั้งหมด
โครูทีน
โค้ดนี้แตกต่างจากตัวอย่าง login
ก่อนหน้านี้ใน 2 รูปแบบ ดังนี้
launch
ไม่รับพารามิเตอร์Dispatchers.IO
เมื่อคุณไม่ได้ ส่งDispatcher
ไปยังlaunch
ซึ่งโครูทีนทั้งหมดที่เปิดตัวviewModelScope
จะทำงานในเทรดหลัก- ขณะนี้ผลของคำขอเครือข่ายได้รับการจัดการเพื่อแสดงความสำเร็จ หรือ UI ล้มเหลว
ฟังก์ชันการเข้าสู่ระบบจะทำงานดังนี้
- แอปเรียกฟังก์ชัน
login()
จากเลเยอร์View
ในเทรดหลัก launch
สร้างโครูทีนใหม่ในชุดข้อความหลัก และโครูทีนเริ่มต้นขึ้น- ในโครูทีน การโทรหา
loginRepository.makeLoginRequest()
ตอนนี้ระงับการดำเนินการโคโรทีนเพิ่มเติมจนกว่าจะถึงwithContext
บล็อกในmakeLoginRequest()
เสร็จสิ้นการทำงาน - เมื่อบล็อก
withContext
เสร็จแล้ว โครูทีนในlogin()
จะกลับมาทำงานอีกครั้ง การดำเนินการในเทรดหลักด้วยผลลัพธ์ของคำขอเครือข่าย
การจัดการข้อยกเว้น
หากต้องการจัดการข้อยกเว้นที่เลเยอร์ Repository
ส่งได้ ให้ใช้ Kotlin
การสนับสนุนสำหรับข้อยกเว้นในตัว
ในตัวอย่างต่อไปนี้ เราใช้การบล็อก try-catch
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
val result = try {
loginRepository.makeLoginRequest(jsonBody)
} catch(e: Exception) {
Result.Error(Exception("Network request failed"))
}
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
ในตัวอย่างนี้ ข้อยกเว้นที่ไม่คาดคิดจาก makeLoginRequest()
เป็นข้อผิดพลาดใน UI
แหล่งข้อมูลเพิ่มเติมเกี่ยวกับโครูทีน
ดูรายละเอียดเพิ่มเติมของโครูทีนใน Android ได้ที่ ปรับปรุงประสิทธิภาพของแอปด้วยโครูทีน Kotlin
ดูแหล่งข้อมูลเพิ่มเติมเกี่ยวกับโครูทีนได้จากลิงก์ต่อไปนี้
- ภาพรวม Coroutines (JetBrains)
- คู่มือ Coroutines (JetBrains)
- แหล่งข้อมูลเพิ่มเติมเกี่ยวกับโครูทีนและโฟลว์ของ Kotlin