在Android中,官方推薦和Jetpack架構組件一起使用
(1)引入協程
- implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3’
(2)引入Jetpack架構組件的KTX擴展
- 對於 ViewModelScope,請使用 androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-beta01 或更高版本。
- 對於 LifecycleScope,請使用 androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01 或更高版本。
- 對於 liveData,請使用 androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01 或更高版本。
ViewModelScope
應用中的每個 ViewModel 定義了 ViewModelScope。如果 ViewModel 已清除,則在此範圍內啓動的協程都會自動取消。如果您具有僅在 ViewModel 處於活動狀態時才需要完成的工作,此時協程非常有用。例如,如果要爲佈局計算某些數據,則應將工作範圍限定至 ViewModel,以便在 ViewModel 清除後,系統會自動取消工作以避免消耗資源。
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
LifecycleScope
爲每個 Lifecycle 對象定義了 LifecycleScope。在此範圍內啓動的協程會在 Lifecycle 被銷燬時取消。您可以通過 lifecycle.coroutineScope 或 lifecycleOwner.lifecycleScope 屬性訪問 Lifecycle 的 CoroutineScope。
以下示例演示瞭如何使用 lifecycleOwner.lifecycleScope 異步創建預計算文本:
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
val params = TextViewCompat.getTextMetricsParams(textView)
val precomputedText = withContext(Dispatchers.Default) {
PrecomputedTextCompat.create(longTextContent, params)
}
TextViewCompat.setPrecomputedText(textView, precomputedText)
}
}
}
LifecycleScope + 生命週期狀態
Lifecycle 提供了其他方法:lifecycle.whenCreated、lifecycle.whenStarted 和 lifecycle.whenResumed。如果 Lifecycle 未至少處於所需的最低狀態,則會暫停在這些塊內運行的任何協程。
以下示例包含僅當關聯的 Lifecycle 至少處於 STARTED 狀態時纔會運行的代碼塊:
class MyFragment: Fragment {
init { // Notice that we can safely launch in the constructor of the Fragment.
lifecycleScope.launch {
whenStarted {
// The block inside will run only when Lifecycle is at least STARTED.
// It will start executing when fragment is started and
// can call other suspend methods.
loadingView.visibility = View.VISIBLE
val canAccess = withContext(Dispatchers.IO) {
checkUserAccess()
}
// When checkUserAccess returns, the next line is automatically
// suspended if the Lifecycle is not *at least* STARTED.
// We could safely run fragment transactions because we know the
// code won't run unless the lifecycle is at least STARTED.
loadingView.visibility = View.GONE
if (canAccess == false) {
findNavController().popBackStack()
} else {
showContent()
}
}
// This line runs only after the whenStarted block above has completed.
}
}
}
如果在協程處於活動狀態時通過某種 when 方法銷燬了 Lifecycle,協程會自動取消。在以下示例中,一旦 Lifecycle 狀態變爲 DESTROYED,finally 塊即會運行:
class MyFragment: Fragment {
init {
lifecycleScope.launchWhenStarted {
try {
// Call some suspend functions.
} finally {
// This line might execute after Lifecycle is DESTROYED.
if (lifecycle.state >= STARTED) {
// Here, since we've checked, it is safe to run any
// Fragment transactions.
}
}
}
}
}
LifecycleScope + 生命週期狀態 的使用總結
通過一種聲明式的寫法(即在Activity或者Fragment的初始化時,聲明好各個生命週期需要做的事情),而不是程序性的寫法(即在各個生命週期回調方法中處理相應邏輯),更有利於整體代碼結構的把握,更易於理解和維護。所以建議大家多使用這種具有聲明式的寫法處理業務邏輯。
將協程與 LiveData 一起使用
您可以使用 liveData 構建器函數調用 suspend 函數,並將結果作爲 LiveData 對象傳送。
在以下示例中,loadUser() 是在其他位置聲明的暫停函數。使用 liveData 構建器函數異步調用 loadUser(),然後使用 emit() 發出結果:
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
當 LiveData 變爲活動狀態時,代碼塊開始執行;當 LiveData 變爲非活動狀態時,代碼塊會在可配置的超時過後自動取消。如果代碼塊在完成前取消,則會在 LiveData 再次變爲活動狀態後重啓;如果在上次運行中成功完成,則不會重啓。請注意,代碼塊只有在自動取消的情況下才會重啓。如果代碼塊由於任何其他原因(例如,拋出 CancelationException)而取消,則不會重啓。
您還可以從代碼塊中發出多個值。每次 emit() 調用都會暫停執行代碼塊:
val user: LiveData<Result> = liveData {
emit(Result.loading())
try {
emit(Result.success(fetchUser()))
} catch(ioException: Exception) {
emit(Result.error(ioException))
}
}
您也可以將 liveData 與 Transformations 結合使用,如以下示例所示:
class MyViewModel: ViewModel() {
private val userId: LiveData<String> = MutableLiveData()
val user = userId.switchMap { id ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
emit(database.loadUserById(id))
}
}
}
您可以從 LiveData 中發出多個值,方法是在每次想要發出新值時調用 emitSource() 函數。請注意,每次調用 emit() 或 emitSource() 都會移除之前添加的來源。
class UserDao: Dao {
@Query("SELECT * FROM User WHERE id = :id")
fun getUser(id: String): LiveData<User>
}
class MyRepository {
fun getUser(id: String) = liveData<User> {
val disposable = emitSource(
userDao.getUser(id).map {
Result.loading(it)
}
)
try {
val user = webservice.fetchUser(id)
// Stop the previous emission to avoid dispatching the updated user
// as `loading`.
disposable.dispose()
// Update the database.
userDao.insert(user)
// Re-establish the emission with success type.
emitSource(
userDao.getUser(id).map {
Result.success(it)
}
)
} catch(exception: IOException) {
// Any call to `emit` disposes the previous one automatically so we don't
// need to dispose it here as we didn't get an updated value.
emitSource(
userDao.getUser(id).map {
Result.error(exception, it)
}
)
}
}
}
參考:
Android官網介紹:
利用 Kotlin 協程提升應用性能
將 Kotlin 協程與架構組件一起使用