欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Jetpack(5):ViewModel

程序员文章站 2022-06-10 10:43:34
...

ViewModel介绍

ViewModel类是被设计用来以可感知生命周期的方式存储和管理 UI 相关数据为了更好的以生命周期的方式管理界面相关的数据。ViewModel中数据会一直存活,即使configuration发生改变(比如旋转屏幕),数据仍然可以存在不会销毁。

Android中的ViewModel是一个可以用来存储UI相关的数据的类

那ViewModel为什么可以管理这些数据呢?

主要还是因为ViewModel的生命周期比Activtiy、Fragment生命周期来的更长。

ViewModel类允许数据在配置变化(例如屏幕旋转)后存活。

还记得MVP中的Model吗。这里的ViewModel有点类似MVP中的Model的作用。但是google出了一套AAC组件。这些组件让开发者能开发高效的项目。其中ViewModel也是其中组件之一。

AAC(Android Architecture Components) :实际上是android官方提供的一系列组件,用来实现MVVM架构的。

引入ViewModel的原因

  1. Activity或Fragment这类应用组件都有自己的生命周期,他们的生命周期都是被Framework所管理。Framework可能会根据用户的一些操作以及设备的状态对Activity或Fragment进行销毁和重建。作为开发者,这些行为我们是无法干预的。伴随着Activity或Fragment的销毁和重建,它们当中的数据也会随着一起销毁和重建。对于一些简单的数据,Activity可以使用onSaveInstanceState()方法,并从onCreate的bundle中重新获取,但这一方法仅仅适合一些简单的UI状态,对于列表型这种庞大的数据类型并不适合。

  2. Activity或Fragment经常会做一些异步的耗时操作,随之就需要管理这些异步操作得到的数据,并在destroyed的时候清理它们,从而避免内存溢出这类问题的发生。但是这样的处理会随着项目扩大而变得十分复杂。

  3. Activity或Fragment本身需要处理很多用户的输入事件并和操作系统打交道,当它们还要花时间管理那些数据资源时,它们所在的类就会变得异常庞大,造就出所谓的god activities和god fragments,这样很尴尬。

所以引入ViewModel之后,数据就可以从UI中分离出来,让每个模块的职责更加清晰合理。并且当Activity或Fragment重建的时候,ViewModel会自动保留之前的数据并给新的Activity或Fragment使用。

ViewModel可以解决以下痛点

1. 数据持久化

在屏幕旋转的时候会经历 Activity 的销毁与重新创建,这里就涉及到数据保存的问题,显然重新请求或加载数据是不友好的。在 ViewModel 出现之前我们可以用 Activity 的 onSaveInstanceState() 机制保存和恢复数据,但缺点很明显,onSaveInstanceState只适合保存少量的可以被序列化、反序列化的数据,这种机制明显不合适。

ViewModel 生命周期图如下:

Android Jetpack(5):ViewModel
由图可知,ViewModel 生命周期是贯穿整个 activity 生命周期,包括 Activity 因旋转造成的重创建,直到 Activity 真正意义上销毁后才会结束。既然如此,用来存放数据再好不过了。

2. 异步回调问题

通常我们 App 需要频繁异步请求数据,比如调接口请求服务器数据。当然这些请求的回调都是相当耗时的,之前我们在 Activity 或 Fragment里接收这些回调。所以不得不考虑潜在的内存泄漏情况,比如 Activity 被销毁后接口请求才返回。处理这些问题,会给我们增添好多复杂的工作。但现在我们利用 ViewModel 处理数据回调,可以完美的解决此痛点。意思只要继承我们的ViewModel后,可能会出现的bug,google都帮我们处理了。

3. 分担 UI controller 负担

从最早的 MVC 到目前流行的 MVP、MVVM,目的无非是 明确职责,分离 UI Controller 负担。
UI Controller 比如 Activity 、Fragment 是设计用来渲染展示数据、响应用户行为、处理系统的某些交互。如果再要求他去负责加载网络或数据库数据,会让其显得臃肿和难以管理。

所以为了简洁、清爽、丝滑,我们可以分离出数据操作的职责给 ViewModel。

4. Fragments 间共享数据

比如在一个 Activity 里有多个 Fragment,这 Fragment 之间需要做某些交互。我之前的做法是接口回调,需要统一在 Activity 里管理,并且不可避免的 Fragment 之间还得互相持有对方的引用。

ViewModel的生命周期

ViewModel对象的范围由获取ViewModel时传递至ViewModelProvider的Lifecycle所决定。ViewModel始终处在内存中,直到Lifecycle永久地离开—对于Activity来说,是当它终止(finish)的时候,对于Fragment来说,是当它分离(detached)的时候。

val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)

Activity在生命周期中可能会触发多次onCreate(),而ViewModel则只会在第一次onCreate()时创建,然后直到最后Activity销毁。

当ViewModel的实例生成之后,它会一直待在内存中,直到对应的Lifecycle彻底结束。下图展示了一个Activity经过旋转然后到结束运行这段期间各生命周期的状态,在Activity的生命周期旁边显示了其中ViewModel的生命周期。虽然这个图描述的是Activity的状态,但是Fragment和它是类似的。

Android Jetpack(5):ViewModel
从图中可以看出,在第一次调用Activity对象的onCreate()方法时创建了一个ViewModel。在Activity运行过程中可能会多次调用onCreate()方法(比如当设备屏幕旋转时),但是ViewModel一直存在,直到Activity结束并销毁。这意味着ViewModel不会因为它的创建者的一个配置变化而被销毁,Activity 的新实例将与现有的ViewModel重新连接。

这里最大的亮点是以生命周期的方式。举例:假如在Activity里使用。他会贯穿整个Activity里的生命周期。

ViewModel只会在Activity存活,且只会创建一次。当销毁时,会主动调用onClered。

因为在Activity存活时,只创建一次,那么在此Activity下的所有Fragment都可以共享一个ViewModel。

由于 ViewModel 生命周期可能长与 activity 生命周期,所以为了避免内存泄漏Google禁止在ViewModel中持有Context或activity或view的引用。如果非得使用Context,可以继承AndroidViewModel 类中获取ApplicationContext。

这张图也解释了为什么ViewModel中不能持有Activity、Fragment、view的引用。因为Activity在重建后是一个新的对象,如果ViewModel中持有旧对象的引用,这个旧对象可能就等不到释放,造成泄漏。

之前我们在activity销毁重建时,可以用activity的onSaveInstanceState()机制保存和恢复数据,但缺点明显,只适合保存少量的可以被序列化、反序列化的数据。假如我们需要保存一个比较大的数据,这个时候ViewModel就可以实现。
Android Jetpack(5):ViewModel

ViewModel使用

ViewModel使用流程

UI,也就是Activity或Fragment,它的职责仅仅是视图的管理(大部分是刷新工作),相当于是一个ViewController的角色。ViewModel类相当于数据集散地,UI要这个数据了,ViewModel就去帮它在仓库找好,无论是数据库还是网络都行。ViewModel拿到数据之后就通知UI,通常情况下这个通知由LiveData来完成。最后通过LiveData去找DataBinding,完成数据的刷新。

Android Jetpack(5):ViewModel
依赖

ViewModel 的依赖,可以查看官方文档,导入最新的版本。

官网地址

java

	   def lifecycle_version = "2.2.0"
        def arch_version = "2.1.0"

        // ViewModel
        implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
        // LiveData
        implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"

kotlin

    def lifecycle_version = "2.2.0"
    def arch_version = "2.1.0"

    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

创建一个ViewModel类

继承ViewModel,需要注意的是ViewModel类中不应该持有Activity、Fragment、view的引用。

架构组件为UI控制器提供ViewModel助手类,ViewModel对象在配置更改期间会自动保留,以便它们保存的数据立即可用于下一个Activity或fragment实例。例如,如果您需要在应用中显示用户列表,请明确分配职责来获取数据并将用户列表保存到ViewModel,而不是Activity或fragment。

ViewModel一般配合 LiveData 使用。

class MainViewModel:ViewModel() {

    private val mainLiveData=MutableLiveData<String>()

    fun loadData() {
        mainLiveData.postValue("xyh")
    }

    fun getMainLiveData(): MutableLiveData<String> {
        return mainLiveData
    }
}

然后你可以从一个Activity中加载数据,如下所示:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //获取ViewModel
        val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)

        //2.2.0-alpha02 以前的版本是通过下面的方式,新版本已弃用
        //ViewModelProviders.of(this).get(MainViewModel::class.java)

        //加载数据
        mainViewModel.loadData()

        mainViewModel.getMainLiveData().observe(this, object : Observer<String> {
            override fun onChanged(t: String?) {
                Log.e("xyh", "onChanged: $t")
            }
        })
    }
}

如果Activity重新创建,它将接收由第一个Activity创建的相同的ViewModel实例。当持有ViewModel的Activity finish后,框架将调用ViewModel对象的onCleared()方法,以便它可以清理资源。

获取ViewModel得方式

val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)

//2.2.0-alpha02 以前的版本是通过下面的方式,新版本已弃用  
val mainViewModel= ViewModelProviders.of(this).get(MainViewModel::class.java);

this参数一般为Activity或Fragment,因此ViewModelProvider可以获取组件的生命周期。

构造器有参数的ViewModel

当我们的ViewModel需要进行构造器需要穿参数的时候,就不能像上面一样进行实例化了。而需要借助于ViewModelProvider的Fatory来进行构造。

class MainViewModel(val name: String) : ViewModel() {

    private val mainLiveData = MutableLiveData<String>()

    fun loadData() {
        mainLiveData.postValue("xyh$name")
    }

    fun getMainLiveData(): MutableLiveData<String> {
        return mainLiveData
    }

    class MainViewModelFactory(private val name: String) : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            return MainViewModel(name) as T
        }
    }
}

可以看到在MainViewModel有一个内部类基础自ViewModelProvider.Factory接口。并重写了create方法。这个方法返回一个ViewModel这里我们就是要实例化MainViewModel对象,因此这里返回一个SharedViewModel对象,可以看到参数这时候就通过MainViewModelFactory传给了MainViewModel了。

这时候不光是MainViewModel要进行修改,在进行MainViewModel的获取的时候也是需要进行相应的修改的:

        val mainViewModel = ViewModelProvider(
            this,
            MainViewModel.MainViewModelFactory("赵丽颖")
        ).get(MainViewModel::class.java)

其实就是在获取ViewModelProvider对象的of方法有修改。这里用的是带有Factory的of方法。传入刚才在MainViewModel中写的Factory的实例。

AndroidViewModel

使用ViewModel的时候,需要注意的是ViewModel不能够持有View、Lifecycle、Acitivity引用,而且不能够包含任何包含前面内容的类。因为这样很有可能会造成内存泄漏。

那如果需要使用Context对象改怎么办。这时候我们可以给ViewModel一个Application。Application是一个Context,而且一个应用也只会有Application。

我们自己添加Application?其实没必要Google还有一个AndroidViewModel。这是一个包含Application的ViewModel。

AndroidViewModel 是ViewModel的一个子类,可以直接调用getApplication(),由此来访问应用的全局资源。

下面是一个AndroidViewModel的源码:

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    @NonNull
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

因为ViewModel在指定的Activity或Fragment实例外存活,它应该永远不能引用一个View,或持有任何包含Activity context引用的类。如果ViewModel需要Application的context(如获取系统服务),可以扩展AndroidViewmodel,并拥有一个构造器接收Application。

使用ViewModel在Fragment间共享数据

一个Activity中的多个Fragment相互通讯是很常见的。之前每个Fragment需要定义接口描述,所属Activity将二者捆绑在一起。此外,每个Fragment必须处理其他Fragment未创建或不可见的情况。通过使用ViewModel可以解决这个痛点,这些Fragment可以使用它们的Activity共享ViewModel来处理通讯。

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, item -> {
           // Update the UI.
        });
    }
}

注意:上面两个Fragment都用到了如下代码来获取ViewModel,getActivity()返回的是同一个宿主Activity,因此两个Fragment之间返回的是同一个SharedViewModel对象。

SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);

这种方式的好处包括:

  • Activity不需要做任何事情,也不需要知道通讯的事情
  • Fragment不需要知道彼此,除了SharedViewModel进行联系。如果它们(Fragment)其中一个消失了,其余的仍然能够像往常一样工作。
  • 每个Fragment有自己的生命周期,而且不会受其它Fragment生命周期的影响。事实上,一个Fragment替换另一个Fragment,UI的工作也不会受到任何影响。

总结

  • ViewModel职责是为Activity或Fragment管理、请求数据,具体数据请求逻辑不应该写在ViewModel中,否则ViewModel的职责会变得太重,此处需要一个引入一个Repository,负责数据请求相关工作。具体请参考 Android架构组件。

  • ViewModel可以用于Activity内不同Fragment的交互,也可以用作Fragment之间一种解耦方式。

  • ViewModel也可以负责处理部分Activity/Fragment与应用其他模块的交互。

  • ViewModel生命周期(以Activity为例)起始于Activity第一次onCreate(),结束于Activity最终finish时。

ViewModel的内部实现

首先是通过ViewModelProvider(this)构造方法,创建一个ViewModelProvider,并在其构造方法中,获取到存储ViewModel的ViewModelStore对象。

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }
   public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

owner就是我们传入的this(FragmentActivity、AppCompatActivity、Fragment)。

我们看看getViewModelStore方法:

当配置发生更改时一般会造成数据丢失 而NonConfigurationInstances实例则可以在配置发生变化时保存一些数据和状态,在oncreate方法恢复使数据不会丢失。

当配置发生变化的时候它保存了ViewModelStore。所以这里先从NonConfigurationInstance实例中获取ViewModelStore

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        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) {
            //如果当前的mViewModelStore为空,会先向nc中取mViewModelStore,这里边存储的就是上一次Activity对应的实例的mViewModelStore
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

可见,这里的关键是NonConfigurationInstances。在设备旋转的时候,当前Activity被销毁了,mViewModelStore等数据会被封装到NonConfigurationInstances中存储出来,创建了新的对象会重新传入该NonConfigurationInstances。

ViewModelStore :

用于缓存ViewModel的一个操作类。

public class ViewModelStore {
	//用于存储ViewModel的集合
    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();
    }
}

可见ViewModelStore还是比较简单的,就是用HashMap来存储的数据。

ViewModel的生成和获取相关, 是在ViewModelProvider的get方法中:

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        //获取我们传入的NumberViewModel简称(就这样叫吧),作为key的一部分
        String canonicalName = modelClass.getCanonicalName();
        //判断是不是局部类与匿名类
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    
    //获取ViewModel实例
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        //从集合中获取ViewModel并检测是不是已经实例化,如果存在就不要新建
        ViewModel viewModel = mViewModelStore.get(key);

        //判断viewModel是否是modelClass类的实例
        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            //如果是则直接返回已存在的viewModel
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        //否则通过工厂来新生成一个viewModel实例,用反射获取到ViewModel的实例
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        //把新生成的viewModel实例存入mViewModelStore中
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

其实主要还是利用了反射得到了ViewModel的实例。我们才可以使用ViewModel里面的方法。

那么什么是反射呢? 这里简单介绍一下:

反射机制:反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

优点:可以实现动态创建对象和编译,体现出很大的灵活性

缺点:对性能有影响,此类操作总是慢于直接执行相同的操作

最后说一下VierModel是如何销毁的

    /**
    * 在FragmentActivity的onDestroy方法中调用了mViewModelStore.clear()
    */
     @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mViewModelStore != null && !isChangingConfigurations()) {
            mViewModelStore.clear();
        }
        mFragments.dispatchDestroy();
    }
 
    /**
     * 先遍历ViewModel实例 调用各自的clear() 
     * 再清除集合中的ViewModel实例
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
    /**
    * 清除一些标记 并调用onCleared()
    */
    @MainThread
    final void clear() {
        mCleared = true;
        // Since clear() is final, this method is still called on mock objects
        // and in those cases, mBagOfTags is null. It'll always be empty though
        // because setTagIfAbsent and getTag are not final so we can skip
        // clearing it
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // see comment for the similar call in setTagIfAbsent
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }
    //通过继承ViewModel 可以重写该方法。 该方法会在ViewMode被销毁前调用
    protected void onCleared() {
    }

通过源码分析ViewModel有三个重要的类:ViewModel 、ViewModelProvider 、 ViewModelStore

ViewModel :负责准备和管理数据的类,该抽象类其实是声明一些通用方法

ViewModelProvider :ViewModel 的核心类,主要是利用反射实例化出ViewModel 对象。利用工厂模式生产出具体的ViewModel 实例。

ViewModelStore:缓存ViewModel实例的一些操作(存储、获取、清除)

核心原理简单通俗描述如下:

  1. ViewModel类存储了Actvity的UI数据
  2. ViewModelStore又存储了ViewModel实例
  3. 在配置发生变化的时候在FragmentActivity.onRetainNonConfigurationInstance()方法中利用NonConfigurationInstances保存了ViewModelStore实例
  4. 并在FragmentActivtiy.oncreate()方法中恢复了ViewModelStore。也就是保存了ViewModel。所以数据才不会在配置更改时丢失
  5. 最后在FragmentActivtiy.onDestroy()方法中清除存储在ViewModelStore中的ViewModel对象。