这次回来重新写Android,发现现在都用MVVM模式了,想想也是,以前用MVC,随着业务扩展,Activity代码量越来越大,改着改着就牵一发而动全身了。当然,最麻烦的还是随着屏幕的旋转的界面重新初始化处理。所以这也是ViewModel深得我心的一个原因。

ViewModel独立于配置变化,所以屏幕旋转所导致的Activity重建并不会影响ViewModel的生命周期,大概就是下面这个图的样子。

Android viewmodel中弹出toast的方法 安卓 viewmodel_android


通过查看ViewModel源码可以看到,ViewModel是一个抽象类,里面有一个onCleared()方法。注释上写着“当不再使用此ViewModel时将调用此方法,并将销毁该方法。当ViewModel观察到某些数据并且需要清除此订阅以防止此ViewModel泄漏时,它非常有用。”所以我们可以在这个方法中执行一些资源释放相关的操作。由上图也可以看到,由于屏幕旋转而导致的Activity重建,并不会调用这个方法。

也正因为如此,ViewModel的最重要的作用就是将视图与数据进行分离,并独立于Acitiviy得到重建

在页面中,我们使用ViewModelProvider类来实例化ViewModel

//获取ViewModel的实例,将countReserved传递给MainViewModel的构造函数
viewModel = ViewModelProvider(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)

来看一下源码:

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }

接收的第一个参数是ViewModelStoreOwner对象,对应我们调用时候的this,指代当前的Activity。之所以能这么用,是因为我们的Activity继承自AppCompatActivity,而查看源码发现,AppCompatActivity又继承自FragmentActivity,在androidx依赖包中,FragmentActivity默认实现了ViewModelStoreOwner接口。

public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
        TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
	........
}
public class FragmentActivity extends ComponentActivity implements
        ActivityCompat.OnRequestPermissionsResultCallback,
        ActivityCompat.RequestPermissionsRequestCodeValidator {
	......
	class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements
            ViewModelStoreOwner,
            OnBackPressedDispatcherOwner {
        public HostCallbacks() {
            super(FragmentActivity.this /*fragmentActivity*/);
        }
        @NonNull
        @Override
        public ViewModelStore getViewModelStore() {
            return FragmentActivity.this.getViewModelStore();
        }
        ......
    }
    ......
}

接口方法getViewModelStore()所定义的返回值类型为ViewModelStore。

查看ViewModelStore源码。

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

可以看出ViewModel实际上是以HashMap<String, ViewModel>的形式被缓存起来了。ViewModel与页面直接没有直接的关联,通过ViewModelProvider进行关联。当页面需要ViewModel时,会向ViewModelProvider索要,ViewModelProvider则会检查该ViewModel是否已经存在于缓存中,如果存在就直接返回,否则会实例化一个。因此,Activity由于配置变化导致的销毁重建并不会影响ViewModel,这也是ViewModel是独立于页面存在原因。

所以,需要注意的是:

  • 不要向ViewModel中传入任何类型的Context或带有Context引用的对象,这可能会导致页面无法被销毁,从而引发内存泄漏

如果一定要在ViewModel中使用Context,可以使用ViewModel的子类AndroidViewModel,因为它接收Application作为Context,这意味着,它的生命周期和Application是一样的,这就不算是一个内存泄漏了。

除了Activity,androidx依赖包中的Fragment也默认实现了ViewModelStoreOwner接口,因此,也可以在Fragment中正常使用ViewModel。