Kotlin Coroutines在Android中的实践 (2)

用了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 & Coroutines

LiveData是一个供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 & Coroutines

Retrofit从2.6.0开始提供了对协程的支持.

定义方法的时候加上suspend关键字:

interface GitHubService { @GET("orgs/{org}/repos?per_page=100") suspend fun getOrgRepos( @Path("org") org: String ): List<Repo> }

suspend方法进行请求的时候, 不会阻塞线程.
返回值可以直接是结果类型, 或者包一层Response:

@GET("orgs/{org}/repos?per_page=100") suspend fun getOrgRepos( @Path("org") org: String ): Response<List<Repo>> Room & Coroutines

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwwfzg.html