在Android MVVM模式,我使用了Jetpack包中的ViewModel来实现业务层,固然你也可使用DataBinding,关于Android业务层架构的选择我在这篇文章中有更详细的说明:Android开发中API层的最佳实践。java
业务层无非就是网络请求,存储操做和数据处理操做,而后将处理好的数据更新给LiveData,UI层则自动更新。其中网络请求我是使用的协程来进行,而不是线程。android
为了防止UI销毁时异步任务仍然在进行所致使的内存泄露,咱们都会在onCleared()
方法中去取消异步任务。如何取消异步任务呢?懒惰的咱们固然不会在每一个ViewModel中去取消,而是去定义一个BaseVM类来存储每一个Job对象,而后统一取消。代码以下:api
open class BaseVM : ViewModel(){ val jobs = mutableListOf<Job>() override fun onCleared() { super.onCleared() jobs.forEach { it.cancel() } } } //UserVM class UserVM : BaseVM() { val userData = StateLiveData<UserBean>() fun login() { jobs.add(GlobalScope.launch { userData.postLoading() val result = "https://lixiaojun.xin/api/login".http(this).get<HttpResult<UserBean>>().await() if (result != null && result.succeed) { userData.postValueAndSuccess(result.data!!) } else { userData.postError() } }) } fun register(){ //... } } 复制代码
这样写看起来简洁统一,但并非最优雅的,它有两个问题:缓存
我所期待最好的样子是: 咱们只需专一地执行异步逻辑,它可以自动的监视UI销毁去自动干掉本身,让我能多一点时间打Dota。安全
有了美好的愿景后来分析一下目前代码存在的问题,咱们使用GlobalScope
开启的协程并不能监视UI生命周期,若是让父ViewModel负责管理和产生协程对象,子ViewModel直接用父类产生的协程对象开启协程,而父ViewModel在onCleared
中统一取消全部的协程,这样不就能实现自动销毁协程么。markdown
当我开始动手的时候,发现Jetpack的ViewModel模块最新版本正好增长了这个功能,它给每一个ViewModel增长了一个扩展属性viewModelScope
,咱们使用这个扩展属性来开启的协程就能自动在UI销毁时干掉本身。网络
首先,添加依赖,注意必定要是androidx版本的哦:架构
def lifecycle_version = "2.2.0-alpha01" // ViewModel and LiveData implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" 复制代码
重写上面的代码:异步
open class BaseVM : ViewModel(){ override fun onCleared() { super.onCleared() //父类啥也不用作 } } //UserVM class UserVM : BaseVM() { val userData = StateLiveData<UserBean>() fun login() { viewModelScope.launch { userData.postLoading() val result = "https://lixiaojun.xin/api/login".http(this).get<HttpResult<UserBean>>().await() if (result != null && result.succeed) { userData.postValueAndSuccess(result.data!!) } else { userData.postError() } } } } 复制代码
这个代码就足够优雅了,不再用关心何时UI销毁,协程会关心,不再会有内存泄露产生。若是咱们但愿某个异步任务在UI销毁时也执行的话,仍是用GlobalScope
来开启便可。ide
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
,这样的好处是使用起来更方便。viewModelScope
属性的getter方法,根据JOB_KEY
取出CoroutineScope对象,目前来看JOB_KEY
是固定的,后期可能增长多个Key。CloseableCoroutineScope
对象并经过setTagIfAbsent
方法进行缓存,根据方法名能看出是线程安全的操做。CloseableCoroutineScope
类是一个自定义的协程Scope对象,接收一个协程对象,它只有一个close()
方法,在该方法中取消协程而后看下ViewModel的核心代码:
public abstract class ViewModel { // Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460) @Nullable private final Map<String, Object> mBagOfTags = new HashMap<>(); private volatile boolean mCleared = false; @SuppressWarnings("WeakerAccess") protected void onCleared() { } @MainThread final void clear() { mCleared = true; if (mBagOfTags != null) { synchronized (mBagOfTags) { for (Object value : mBagOfTags.values()) { // see comment for the similar call in setTagIfAbsent closeWithRuntimeException(value); } } } onCleared(); } //线程安全的进储协程对象 <T> T setTagIfAbsent(String key, T newValue) { T previous; synchronized (mBagOfTags) { //noinspection unchecked previous = (T) mBagOfTags.get(key); if (previous == null) { mBagOfTags.put(key, newValue); } } T result = previous == null ? newValue : previous; if (mCleared) { closeWithRuntimeException(result); } return result; } /** * Returns the tag associated with this viewmodel and the specified key. */ @SuppressWarnings("TypeParameterUnusedInFormals") <T> T getTag(String key) { //noinspection unchecked synchronized (mBagOfTags) { return (T) mBagOfTags.get(key); } } private static void closeWithRuntimeException(Object obj) { if (obj instanceof Closeable) { try { ((Closeable) obj).close(); } catch (IOException e) { throw new RuntimeException(e); } } } } 复制代码
正如咱们所想,ViewModel作了这样几个事情:
onCleared
遍历全部的Scope对象,调用他们的close,取消协程的执行整个执行过程跟咱们以前的分析差很少,经过让父类来管理协程对象,并在onCleared
中去干掉这些协程。
VM层能够自然自动监视UI销毁,我一直在找寻如何优雅的自动取消异步任务,viewModelScope
在目前来看是最佳的方案。
有些人说老子用MVP,不用MVVM。MVP架构下逻辑层和UI层交互有这样几个方式:
若是3年前我会推荐你使用MVP,如今的话,相信我,用MVVM吧。ViewModel + Kotlin + 协程绝对是最早进的,效率最高,最优雅的技术栈组合。