ViewModel中引入协程
AndroidX Lifecycle v2.1.0 在 ViewModel 中引入 viewModelScope
,当 ViewModel 被销毁时它会自动取消协程任务,这个特性真的好用。本文介绍 viewModelScope
使用和内部实现方式,分析 ViewModel 是如何自动取消协程的。
ViewModel 引入协程
当我们在 ViewModel 里面需要引入协程,首先要在 ViewModel 中新建一个 CoroutineScope, 用来管理所有协程任务,同时需要 onCleared()
方法里面取消协程任务,模板代码实现如下:
class MyViewModel : ViewModel() {
private val viewModelJob = SupervisorJob()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
override fun onCleared() {
super.onCleared()
viewModelJob.cancel() // Cancel all coroutines
}
fun launchDataLoad() {
uiScope.launch {
sortList()
// Modify UI
}
}
suspend fun sortList() = withContext(Dispatchers.Default) {
// Heavy work
}
}
然而,很多情况我们会经常忘记取消协程,导致出现内存泄漏等各种问题。 遇到这种场景,可以使用 ViewModel 扩展属性 viewModelScope
来优化代码。
viewModelScope 方式
注意 lifecycle-viewmodel-ktx 版本号: 2.1.0-beta01
viewModelScope
管理协程的方式与我们在 ViewModel 引入协程的方式一样,使用非常简单,模板代码实现如下:
class MyViewModel : ViewModel() {
fun launchDataLoad() {
viewModelScope.launch {
sortList()
// Modify UI
}
}
suspend fun sortList() = withContext(Dispatchers.Default) {
// Heavy work
}
}
viewModelScope 内部实现
// androidx.lifecycle.ViewModel.viewModelScope
private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main))
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
分析 viewModelScope
源码有 3 点需要关注:
- 注意使用
SupervisorJob
而不是用Job
- 为了 ViewModel 能够取消协程,需要实现
Closeable
接口 -
viewModelScope
默认使用Dispatchers.Main
, 方便 Activity 和 Fragment 更新 UI
ViewModel 内部取消协程
ViewModel 类通过 HashMap 存储 CoroutineScope 对象,当使用 getTag(JOB_KEY)
方法获取对象不存在时,创建一个新的 CoroutineScope 并调用 setTagIfAbsent(JOB_KEY, scope)
方法存储新建的 CoroutineScope 对象。ViewModel 被销毁时内部会执行 clear()
方法,在 clear()
方法中遍历调用 closeWithRuntimeException
取消了 viewModelScope
的协程,实现流程非常清晰。相关代码如下:
// androidx.lifecycle.ViewModel
// clear() -> closeWithRuntimeException() -> coroutineContext.cancel()
private final Map<String, Object> mBagOfTags = new HashMap<>();
<T> T getTag(String key) {
synchronized (mBagOfTags) {
return (T) mBagOfTags.get(key);
}
}
<T> T setTagIfAbsent(String key, T newValue) {
T previous;
synchronized (mBagOfTags) {
previous = (T) mBagOfTags.get(key);
if (previous == null) {
mBagOfTags.put(key, newValue);
}
}
T result = previous == null ? newValue : previous;
if (mCleared) {
closeWithRuntimeException(result);
}
return result;
}
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException(value);
}
}
onCleared();
}
private static void closeWithRuntimeException(Object obj) {
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
结论
如果你也正在使用 MVVM 和协程,非常推荐在 ViewModel 中使用 viewModelScope
方式。不仅简化 ViewModel 代码,而且还能管理协程生命周期。
- 写一个BASIC认证的https协议
- java发送邮件功能,以发送qq邮件为例
- spring boot加载复杂的yml文件获取不到值的问题
- JUC包下的CountDownLatch,CyclicBarrier,Semaphore
- java队列,ArrayBlockingQueue
- 多线程使用wait和notify做生产者消费者模型导致线程全部假死
- 偏执的iOS逆向研究员:收集全版本的macOS iOS+越狱+内核调试
- java 多线程暂停与恢复:suspend,resume
- ArrayList底层实现
- Master-work模式
- Condition控制线程通信:java三个线程循环打印ABC
- java根据wait,notify,synchronize关键字自定义队列
- ActiveMQ几个重要的配置文件
- TiDB 在 Ping++ 金融聚合支付业务中的实践
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- tp5 sum某个字段相加得到总数的例子
- laravel框架创建授权策略实例分析
- tp5递归 无限级分类详解
- PHP 进程池与轮询调度算法实现多任务的示例代码
- 如何在Laravel5.8中正确地应用Repository设计模式
- laravel框架中路由设置,路由参数和路由命名实例分析
- PHP框架实现WebSocket在线聊天通讯系统
- 设定php简写功能的方法
- 使用PHP+Redis实现延迟任务,实现自动取消订单功能
- 详解no input file specified 三种解决方法
- PHP实现微信提现功能(微信商城)
- 关于Yii2框架跑脚本时内存泄漏问题的分析与解决
- laravel 框架结合关联查询 when()用法分析
- php 实现简单的登录功能示例【基于thinkPHP框架】
- laravel框架邮箱认证实现方法详解