用了lifecycleScope之后, 既避免了嵌套代码, 又自动处理了取消.
lifecycleScope.launch { delay(DELAY) showFullHint() delay(DELAY) showSmallHint() } LifecycleScope和ViewModelScope但是LifecycleScope启动的协程却不适合调用repository的方法. 因为它的生命周期和Activity/Fragment是一致的, 太碎片化了, 容易被取消, 造成浪费.
设备旋转时, Activity会被重建, 如果取消请求再重新开始, 会造成一种浪费.
可以把请求放在ViewModel中, UI层重新注册获取结果. viewModelScope和lifecycleScope可以结合起来使用.
举例: ViewModel这样写:
class NoteViewModel: ViewModel { val noteDeferred = CompletableDeferred<Note>() 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保证了view没有leak.
特定生命周期阶段尽管scope提供了自动取消的方式, 你可能还有一些需求需要限制在更加具体的生命周期内.
比如, 为了做FragmentTransaction, 你必须等到Lifecycle至少是STARTED.
上面的例子中, 如果需要打开一个新的fragment:
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 & CoroutinesLiveData是一个供UI观察的value holder.
LiveData的数据可能是异步获得的, 和协程结合:
val user: LiveData<User> = liveData { val data = database.loadUser() // loadUser is a suspend function. emit(data) }这个例子中的liveData是一个builder function, 它调用了读取数据的方法(一个suspend方法), 然后用emit()来发射结果.
同样也是需要添加依赖的:
For liveData, use androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01 or higher.
实际上使用时, 可以emit()多次:
val user: LiveData<Result> = 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<String> = MutableLiveData() val user = userId.switchMap { id -> liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) { emit(database.loadUserById(id)) } } }如果数据库的方法返回的类型是LiveData类型, emit()方法可以改成emitSource(). 例子见: .
网络/数据库 & Coroutines根据Architecture Components的构建模式:
ViewModel负责在主线程启动协程, 清理时取消协程, 收到数据时用LiveData传给UI.
Repository暴露suspend方法, 确保方法main-safe.
数据库和网络暴露suspend方法, 确保方法main-safe. Room和Retrofit都是符合这个pattern的.
Repository暴露suspend方法, 是主线程safe的, 如果要对结果做一些heavy的处理, 比如转换计算, 需要用withContext自行确定主线程不被阻塞.
Retrofit & CoroutinesRetrofit从2.6.0开始提供了对协程的支持.
定义方法的时候加上suspend关键字:
interface GitHubService { @GET("orgs/{org}/repos?per_page=100") suspend fun getOrgRepos( @Path("org") org: String ): List<Repo> }suspend方法进行请求的时候, 不会阻塞线程.
返回值可以直接是结果类型, 或者包一层Response: