废话不说直奔主题
如果不使用viewModelScope,我们的代码是这样的
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
}
}
我们需要在onCleared方法中主动调用viewModelJob.cancel()方法取消该协程和它的所有子协程,如果我们忘记调用viewModelJob.cancel(),就会发生内存泄漏,Android官方给我们提供了一个lib库,帮助我们解决该问题,这样自己省去了写viewModelJob.cancel()
使用viewModelScope的方式如下
...
import androidx.lifecycle.viewModelScope
...
fun demo() {
viewModelScope.launch(Dispatchers.Main) {
launch(Dispatchers.IO) {
val result = App.retrofit.create(WanApi::class.java).getHome()
}
}
}
viewModelScope的使用方法很简单,只需要在gradle中添加kotlin的协程lib后额外引入如下lib(写文章时的版本是2.1.0)
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
然后在页面销毁的时候,viewModel就会在自动调用clear后调用CoroutineContext的cancel方法,这里其实和自己写没有区别,只不过不需要自己管理了
那么原理是什么呢?
首先,看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()
}
}
看到没,和我们自己写是一样的,也是CoroutineScope(Dispatchers.Main + viewModelJob)
,然后我们看setTagIfAbsent方法,这个方法从代码上看就知道在ViewModel中,代码如下
<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;
}
简单的说,就是把CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main))
存到了mBagOfTags这个HashMap里,然后在viewModel调用clear()方法的时候,这个CoroutineScope就被取消了,代码如下
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException(value);
}
}
}
onCleared();//我们之前把CoroutineScope的取消放到onCleared方法里
}
至于clear()是什么时候调用的,流程也很简单,用Android Studio查看一下调用关系就会发现在ViewModelStore
类里调用了
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
...
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
而这个clear()
在哪里调用的呢,继续查看调用关系,会在ComponentActivity
的构造函数里找到如下代码(Fragment的我没有找,不过取消方式应该类似,都是页面销毁的时候调用)
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {
public ComponentActivity() {
...
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();//1
}
}
}
});
...
}
public ViewModelStore getViewModelStore() {//2
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
}
可以看到是在上面的注释1调用的,而注释2的getViewModelStore()
方法里的mViewModelStore
属性是ComponentActivity
的实例属性,这个属性只在getViewModelStore()
方法暴露了,所以肯定有类调用了此方法在mViewModelStore
实例里存入了viewModel
实例,然后查看调用栈,调用的地方也是有好几处,但是有一个调用方是ViewModelProviders
的of
方法,这个方法就是我们平时获取viewModel
实例的地方,代码如下
public static ViewModelProvider of(@NonNull FragmentActivity activity, @Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
...
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
Application application = checkApplication(checkActivity(fragment));
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(fragment.getViewModelStore(), factory);
}
这也就是ViewModel
不应该自己new
的原因,而是应该用该ViewModelProviders.of
获取,这样就能让Activity
和Fragment
自动管理ViewModel
,在页面销毁的时候自动调用ViewModel
的clear()
方法
至此,本文结束,现在我们知道了为什么ViewModelProviders.of
获取的viewModel
在页面销毁的时候会自动调用clear()
方法