Coroutines鍦ˋndroid涓殑瀹炶返
鍓嶉潰涓ょ瘒鏂囩珷璁蹭簡鍗忕▼鐨勫熀纭�鐭ヨ瘑鍜屽崗绋嬬殑閫氫俊.
瑙�:
- Kotlin Coroutines涓嶅鏉�, 鎴戞潵甯綘鐞嗕竴鐞�
- Kotlin鍗忕▼閫氫俊鏈哄埗: Channel
涓剧殑渚嬪瓙鍙兘绂诲疄闄呯殑搴旂敤浠g爜姣旇緝閬ヨ繙.
杩欑瘒鎴戜滑灏变粠Android搴旂敤鐨勮搴�, 鐪嬬湅瀹炶返涓兘鏈夊摢浜涘湴鏂瑰彲浠ョ敤鍒板崗绋�.
Coroutines鐨勭敤閫�
Coroutines鍦ˋndroid涓彲浠ュ府鎴戜滑鍋氫粈涔�:
- 鍙栦唬callbacks, 绠�鍖栦唬鐮�, 鏀瑰杽鍙鎬�.
- 淇濊瘉Main safety.
- 缁撴瀯鍖栫鐞嗗拰鍙栨秷浠诲姟, 閬垮厤娉勬紡.
杩欐湁涓�涓緥瀛�:
suspend fun fetchDocs() { // Dispatchers.Main
val result = get("developer.android.com") // Dispatchers.Main
show(result) // Dispatchers.Main
}
suspend fun get(url: String) = // Dispatchers.Main
withContext(Dispatchers.IO) { // Dispatchers.IO (main-safety block)
/* perform network IO here */ // Dispatchers.IO (main-safety block)
} // Dispatchers.Main
}
杩欓噷get
鏄竴涓�suspend
鏂规硶, 鍙兘鍦ㄥ彟涓�涓�suspend
鏂规硶鎴栬�呭湪涓�涓崗绋嬩腑璋冪敤.
get
鏂规硶鍦ㄤ富绾跨▼琚皟鐢�, 瀹冨湪寮�濮嬭姹備箣鍓峴uspend浜嗗崗绋�, 褰撹姹傝繑鍥�, 杩欎釜鏂规硶浼歳esume鍗忕▼, 鍥炲埌涓荤嚎绋�. 缃戠粶璇锋眰涓嶄細block涓荤嚎绋�.
main-safety鏄浣曚繚璇佺殑鍛�?
dispatcher鍐冲畾浜嗗崗绋嬪湪浠�涔堢嚎绋嬩笂鎵ц. 姣忎釜鍗忕▼閮芥湁dispatcher. 鍗忕▼suspend鑷繁, dispatcher璐熻矗resume瀹冧滑.
Dispatchers.Main
: 涓荤嚎绋�: UI浜や簰, 鏇存柊LiveData
, 璋冪敤suspend
鏂规硶绛�.Dispatchers.IO
: IO鎿嶄綔, 鏁版嵁搴撴搷浣�, 璇诲啓鏂囦欢, 缃戣矾璇锋眰.Dispatchers.Default
: 涓荤嚎绋嬩箣澶栫殑璁$畻浠诲姟(CPU-intensive work), 鎺掑簭, 瑙f瀽JSON绛�.
涓�涓ソ鐨勫疄璺垫槸浣跨敤withContext()
鏉ョ‘淇濇瘡涓柟娉曢兘鏄痬ain-safe鐨�, 璋冪敤鑰呭彲浠ュ湪涓荤嚎绋嬮殢鎰忚皟鐢�, 涓嶇敤鍏冲績閲岄潰鐨勪唬鐮佸埌搴曟槸鍝釜绾跨▼鐨�.
绠$悊鍗忕▼
涔嬪墠璁睸cope鍜孲tructured Concurrency鐨勬椂鍊欐彁杩�, scope鏈�鍏稿瀷鐨勫簲鐢ㄥ氨鏄寜鐓у璞$殑鐢熷懡鍛ㄦ湡, 鑷姩绠$悊鍏朵腑鐨勫崗绋�, 鍙婃椂鍙栨秷, 閬垮厤娉勬紡鍜屽啑浣欐搷浣�.
鍦ㄥ崗绋嬩箣涓啀鍚姩鏂扮殑鍗忕▼, 鐖跺瓙鍗忕▼鏄叡浜玸cope鐨�, 涔熷嵆scope浼歵rack鍏朵腑鎵�鏈夌殑鍗忕▼.
鍗忕▼琚彇娑堜細鎶涘嚭CancellationException
.
coroutineScope
鍜�supervisorScope
鍙互鐢ㄦ潵鍦╯uspend鏂规硶涓惎鍔ㄥ崗绋�. Structured concurrency淇濊瘉: 褰撲竴涓猻uspend鍑芥暟杩斿洖鏃�, 瀹冪殑鎵�鏈夊伐浣滈兘鎵ц瀹屾瘯.
瀹冧滑涓よ�呯殑鍖哄埆鏄�: 褰撳瓙鍗忕▼鍙戠敓閿欒鐨勬椂鍊�, coroutineScope
浼氬彇娑坰cope涓殑鎵�鏈夌殑瀛愬崗绋�, 鑰�supervisorScope
涓嶄細鍙栨秷娌℃湁鍙戠敓閿欒鐨勫叾浠栧瓙鍗忕▼.
Activity/Fragment & Coroutines
鍦ˋndroid涓�, 鍙互鎶婁竴涓睆骞�(Activity/Fragment)鍜屼竴涓�CoroutineScope
鍏宠仈, 杩欐牱鍦ˋctivity鎴朏ragment鐢熷懡鍛ㄦ湡缁撴潫鐨勬椂鍊�, 鍙互鍙栨秷杩欎釜scope涓嬬殑鎵�鏈夊崗绋�, 濂介伩鍏嶅崗绋嬫硠婕�.
鍒╃敤CoroutineScope
鏉ュ仛杩欎欢浜嬫湁涓ょ鏂规硶: 鍒涘缓涓�涓�CoroutineScope
瀵硅薄鍜宎ctivity鐨勭敓鍛藉懆鏈熺粦瀹�, 鎴栬�呰activity瀹炵幇CoroutineScope
鎺ュ彛.
鏂规硶1: 鎸佹湁scope寮曠敤:
class Activity {
private val mainScope = MainScope()
fun destroy() {
mainScope.cancel()
}
}
鏂规硶2: 瀹炵幇鎺ュ彛:
class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) {
fun destroy() {
cancel() // Extension on CoroutineScope
}
}
榛樿绾跨▼鍙互鏍规嵁瀹為檯鐨勯渶瑕佹寚瀹�.
Fragment鐨勫疄鐜扮被浼�, 杩欓噷涓嶅啀涓句緥.
ViewModel & Coroutines
Google鐩墠鎺ㄥ箍鐨凪VVM妯″紡, 鐢盫iewModel鏉ュ鐞嗛�昏緫, 鍦╒iewModel涓娇鐢ㄥ崗绋�, 鍚屾牱涔熸槸鍒╃敤scope鏉ュ仛绠$悊.
ViewModel鍦ㄥ睆骞曟棆杞殑鏃跺�欏苟涓嶄細閲嶅缓, 鎵�浠ヤ笉鐢ㄦ媴蹇冨崗绋嬪湪杩欎釜杩囩▼涓鍙栨秷鍜岄噸鏂板紑濮�.
鏂规硶1: 鑷繁鍒涘缓scope
private val viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
榛樿鏄湪UI绾跨▼.
CoroutineScope
鐨勫弬鏁版槸CoroutineContext
, 鏄竴涓厤缃睘鎬х殑闆嗗悎. 杩欓噷鎸囧畾浜哾ispatcher鍜宩ob.
鍦╒iewModel琚攢姣佺殑鏃跺��:
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
杩欓噷viewModelJob鏄痷iScope鐨刯ob, 鍙栨秷浜唙iewModelJob, 鎵�鏈夎繖涓猻cope涓嬬殑鍗忕▼閮戒細琚彇娑�.
涓�鑸�CoroutineScope
鍒涘缓鐨勬椂鍊欎細鏈変竴涓粯璁ょ殑job, 鍙互杩欐牱鍙栨秷:
uiScope.coroutineContext.cancel()
鏂规硶2: 鍒╃敤viewModelScope
濡傛灉鎴戜滑鐢ㄤ笂闈㈢殑鏂规硶, 鎴戜滑闇�瑕佺粰姣忎釜ViewModel閮借繖鏍峰啓. 涓轰簡閬垮厤杩欎簺boilerplate code, 鎴戜滑鍙互鐢�viewModelScope
.
娉�: 瑕佷娇鐢╲iewModelScope闇�瑕佹坊鍔犵浉搴旂殑KTX渚濊禆.
- For ViewModelScope, use
androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-beta01
or higher.
viewModelScope
缁戝畾鐨勬槸Dispatchers.Main
, 浼氳嚜鍔ㄥ湪ViewModel clear鐨勬椂鍊欒嚜鍔ㄥ彇娑�.
鐢ㄧ殑鏃跺�欑洿鎺ョ敤灏卞彲浠ヤ簡:
class MainViewModel : ViewModel() {
// Make a network request without blocking the UI thread
private fun makeNetworkRequest() {
// launch a coroutine in viewModelScope
viewModelScope.launch(Dispatchers.IO) {
// slowFetch()
}
}
// No need to override onCleared()
}
鎵�鏈夌殑setting up鍜宑learing宸ヤ綔閮芥槸搴撳畬鎴愮殑.
LifecycleScope & Coroutines
姣忎竴涓�Lifecycle瀵硅薄閮芥湁涓�涓�LifecycleScope
.
鍚屾牱涔熼渶瑕佹坊鍔犱緷璧�:
- For LifecycleScope, use
androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01
or higher.
瑕佽闂�CoroutineScope
鍙互鐢�lifecycle.coroutineScope
鎴栬��lifecycleOwner.lifecycleScope
灞炴��.
姣斿:
activity.lifecycleScope.launch {}
fragment.lifecycleScope.launch {}
fragment.viewLifecycleOwner.launch {}
lifecycleScope
鍙互鍚姩鍗忕▼, 褰揕ifecycle缁撴潫鐨勬椂鍊�, 浠讳綍杩欎釜scope涓惎鍔ㄧ殑鍗忕▼閮戒細琚彇娑�.
杩欐瘮杈冮�傚悎浜庡鐞嗕竴浜涘甫delay鐨刄I鎿嶄綔, 姣斿闇�瑕佺敤handler.postDelayed鐨勬洿鏂癠I鐨勬搷浣�, 鏈夊涓搷浣滅殑鏃跺�欏祵濂楅毦鐪�, 杩樺鏄撴湁娉勬紡闂.
鐢ㄤ簡lifecycleScope涔嬪悗, 鏃㈤伩鍏嶄簡宓屽浠g爜, 鍙堣嚜鍔ㄥ鐞嗕簡鍙栨秷.
lifecycleScope.launch {
delay(DELAY)
showFullHint()
delay(DELAY)
showSmallHint()
}
LifecycleScope鍜孷iewModelScope
浣嗘槸LifecycleScope鍚姩鐨勫崗绋嬪嵈涓嶉�傚悎璋冪敤repository鐨勬柟娉�. 鍥犱负瀹冪殑鐢熷懡鍛ㄦ湡鍜孉ctivity/Fragment鏄竴鑷寸殑, 澶鐗囧寲浜�, 瀹规槗琚彇娑�, 閫犳垚娴垂.
璁惧鏃嬭浆鏃�, Activity浼氳閲嶅缓, 濡傛灉鍙栨秷璇锋眰鍐嶉噸鏂板紑濮�, 浼氶�犳垚涓�绉嶆氮璐�.
鍙互鎶婅姹傛斁鍦╒iewModel涓�, UI灞傞噸鏂版敞鍐岃幏鍙栫粨鏋�. viewModelScope
鍜�lifecycleScope
鍙互缁撳悎璧锋潵浣跨敤.
涓句緥: ViewModel杩欐牱鍐�:
class NoteViewModel: ViewModel {
val noteDeferred = CompletableDeferred()
viewModelScope.launch {
val note = repository.loadNote()
noteDeferred.complete(note)
}
suspend fun loadNote(): Note = noteDeferred.await()
}
鑰屾垜浠殑UI涓�:
fun onCreate() {
lifecycleScope.launch {
val note = userViewModel.loadNote()
updateUI(note)
}
}
杩欐牱鍋氫箣鍚庣殑濂藉:
- ViewModel淇濊瘉浜嗘暟鎹姹傛病鏈夋氮璐�, 灞忓箷鏃嬭浆涓嶄細閲嶆柊鍙戣捣璇锋眰.
- lifecycleScope淇濊瘉浜唙iew娌℃湁leak.
鐗瑰畾鐢熷懡鍛ㄦ湡闃舵
灏界scope鎻愪緵浜嗚嚜鍔ㄥ彇娑堢殑鏂瑰紡, 浣犲彲鑳借繕鏈変竴浜涢渶姹傞渶瑕侀檺鍒跺湪鏇村姞鍏蜂綋鐨勭敓鍛藉懆鏈熷唴.
姣斿, 涓轰簡鍋�FragmentTransaction
, 浣犲繀椤荤瓑鍒�Lifecycle
鑷冲皯鏄�STARTED
.
涓婇潰鐨勪緥瀛愪腑, 濡傛灉闇�瑕佹墦寮�涓�涓柊鐨刦ragment:
fun onCreate() {
lifecycleScope.launch {
val note = userViewModel.loadNote()
fragmentManager.beginTransaction()....commit() //IllegalStateException
}
}
寰堝鏄撳彂鐢�IllegalStateException
.
Lifecycle鎻愪緵浜�:
lifecycle.whenCreated
, lifecycle.whenStarted
, lifecycle.whenResumed
.
濡傛灉娌℃湁鑷冲皯杈惧埌鎵�瑕佹眰鐨勬渶灏忕敓鍛藉懆鏈�, 鍦ㄨ繖浜涘潡涓惎鍔ㄧ殑鍗忕▼浠诲姟, 灏嗕細suspend.
鎵�浠ヤ笂闈㈢殑渚嬪瓙鏀规垚杩欐牱:
fun onCreate() {
lifecycleScope.launchWhenStarted {
val note = userViewModel.loadNote()
fragmentManager.beginTransaction()....commit()
}
}
濡傛灉Lifecycle
瀵硅薄琚攢姣�(state==DESTROYED
), 杩欎簺when鏂规硶涓殑鍗忕▼涔熶細琚嚜鍔ㄥ彇娑�.
LiveData & Coroutines
LiveData
鏄竴涓緵UI瑙傚療鐨剉alue holder.
LiveData
鐨勬暟鎹彲鑳芥槸寮傛鑾峰緱鐨�, 鍜屽崗绋嬬粨鍚�:
val user: LiveData = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
杩欎釜渚嬪瓙涓殑liveData
鏄竴涓猙uilder function, 瀹冭皟鐢ㄤ簡璇诲彇鏁版嵁鐨勬柟娉�(涓�涓�suspend
鏂规硶), 鐒跺悗鐢�emit()
鏉ュ彂灏勭粨鏋�.
鍚屾牱涔熸槸闇�瑕佹坊鍔犱緷璧栫殑:
- For liveData, use
androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01
or higher.
瀹為檯涓婁娇鐢ㄦ椂, 鍙互emit()
澶氭:
val user: LiveData = liveData {
emit(Result.loading())
try {
emit(Result.success(fetchUser()))
} catch(ioException: Exception) {
emit(Result.error(ioException))
}
}
姣忔emit()
璋冪敤閮戒細suspend杩欎釜鍧�, 鐩村埌LiveData
鐨勫�煎湪涓荤嚎绋嬭璁剧疆.
LiveData
杩樺彲浠ュ仛鍙樻崲:
class MyViewModel: ViewModel() {
private val userId: LiveData = MutableLiveData()
val user = userId.switchMap { id ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
emit(database.loadUserById(id))
}
}
}
濡傛灉鏁版嵁搴撶殑鏂规硶杩斿洖鐨勭被鍨嬫槸LiveData绫诲瀷, emit()
鏂规硶鍙互鏀规垚emitSource()
. 渚嬪瓙瑙�: Use coroutines with LiveData.
缃戠粶/鏁版嵁搴� & Coroutines
鏍规嵁Architecture Components鐨勬瀯寤烘ā寮�:
- ViewModel璐熻矗鍦ㄤ富绾跨▼鍚姩鍗忕▼, 娓呯悊鏃跺彇娑堝崗绋�, 鏀跺埌鏁版嵁鏃剁敤
LiveData
浼犵粰UI. - Repository鏆撮湶
suspend
鏂规硶, 纭繚鏂规硶main-safe. - 鏁版嵁搴撳拰缃戠粶鏆撮湶
suspend
鏂规硶, 纭繚鏂规硶main-safe. Room鍜孯etrofit閮芥槸绗﹀悎杩欎釜pattern鐨�.
Repository鏆撮湶suspend
鏂规硶, 鏄富绾跨▼safe鐨�, 濡傛灉瑕佸缁撴灉鍋氫竴浜沨eavy鐨勫鐞�, 姣斿杞崲璁$畻, 闇�瑕佺敤withContext
鑷纭畾涓荤嚎绋嬩笉琚樆濉�.
Retrofit & Coroutines
Retrofit浠�2.6.0寮�濮嬫彁渚涗簡瀵瑰崗绋嬬殑鏀寔.
瀹氫箟鏂规硶鐨勬椂鍊欏姞涓�suspend
鍏抽敭瀛�:
interface GitHubService {
@GET("orgs/{org}/repos?per_page=100")
suspend fun getOrgRepos(
@Path("org") org: String
): List
}
suspend鏂规硶杩涜璇锋眰鐨勬椂鍊�, 涓嶄細闃诲绾跨▼.
杩斿洖鍊煎彲浠ョ洿鎺ユ槸缁撴灉绫诲瀷, 鎴栬�呭寘涓�灞�Response
:
@GET("orgs/{org}/repos?per_page=100")
suspend fun getOrgRepos(
@Path("org") org: String
): Response>
Room & Coroutines
Room浠�2.1.0鐗堟湰寮�濮嬫彁渚涘鍗忕▼鐨勬敮鎸�. 鍏蜂綋灏辨槸DAO鏂规硶鍙互鏄�suspend
鐨�.
@Dao
interface UsersDao {
@Query("SELECT * FROM users")
suspend fun getUsers(): List
@Insert
suspend fun insertUser(user: User)
@Update
suspend fun updateUser(user: User)
@Delete
suspend fun deleteUser(user: User)
}
Room浣跨敤鑷繁鐨刣ispatcher鏉ョ‘瀹氭煡璇㈣繍琛屽湪鍚庡彴绾跨▼.
鎵�浠ヤ綘鐨勪唬鐮佷笉搴旇浣跨敤withContext(Dispatchers.IO)
, 浼氳浠g爜鍙樺緱澶嶆潅骞朵笖鏌ヨ鍙樻參.
鏇村鍐呭鍙: Room 馃敆 Coroutines.
WorkManager & Coroutines
WorkManager涔熸湁鍗忕▼鐗堟湰, 娣诲姞work-runtime-ktx
渚濊禆, 鐒跺悗鏀瑰彉鍩虹被, 浠ュ墠缁ф壙Worker
, 鐜板湪缁ф壙CoroutineWorker
.
姣斿:
class UploadNotesWorker(...) : CoroutineWorker(...) {
suspend fun doWork(): Result {
val newNotes = db.queryNewNotes()
noteService.uploadNotes(newNotes)
db.markAsSynced(newNotes)
return Result.success()
}
}
杩欐浠g爜鍏朵腑鏁版嵁搴撶敤Room
, 缃戠粶鐢�Retrofit
, 杩欐牱3涓柟娉曢兘鏄�suspend
鐨�.
鐢ㄤ簡鍗忕▼鐨勭増鏈箣鍚�, 鍙栨秷鎿嶄綔鏇村鏄�.
鏇磋缁嗙殑璇风湅: Threading in CoroutineWorker
寮傚父澶勭悊
suspend
鏂规硶涓殑寮傚父灏嗕細resume鍒拌皟鐢ㄨ��.
鏇翠竴鑸殑, 鍗忕▼涓殑閿欒浼氶�氱煡鍒板畠鐨勮皟鐢ㄨ�呮垨鑰卻cope.
launch
鍜�async
鐨勫紓甯稿鐞嗕笉鍚�.
杩欐槸鍥犱负async
杩斿洖鍊�, 鏄湡寰�await
璋冪敤鐨�, 鎵�浠ヤ細鎸佹湁寮傚父, 鍦ㄨ皟鐢�await()
鐨勬椂鍊欐墠杩斿洖(缁撴灉鎴栧紓甯�).
鎵�浠ュ鏋�await()
娌℃湁琚皟鐢ㄧ殑璇�, 寮傚父灏变細琚悆浜�.
娴嬭瘯
鎺ㄨ崘浣跨敤runBlockingTest
鏉ユ浛鎹�runBlocking
, 灏嗕細鍒╃敤virtual time, 鑺傜渷娴嬭瘯鏃堕棿.
鏇村鍏充簬娴嬭瘯鐨勮缁嗗唴瀹硅: kotlinx-coroutines-test
鍙傝��
- Codelab: Using Kotlin Coroutines in your Android App
- Improve app performance with Kotlin coroutines
- Use Kotlin coroutines with Architecture components
- Coroutine Context and Dispatchers
- Threading in CoroutineWorker
鍗氬:
- Kotlin Coroutines patterns & anti-patterns
- Coroutines on Android (part II): Getting started
- Coroutines On Android (part III): Real work
- Part 2 鈥� Coroutine Cancellation and Structured Concurrency
- Room 馃敆 Coroutines
Google鐨勮棰�:
- LiveData with Coroutines and Flow (Android Dev Summit '19)
- Understand Kotlin Coroutines on Android (Google I/O'19)