一点点入坑JetPack:ViewModel篇

4,305 阅读6分钟

前言

费了很多脑细胞,把Lifecycle单拆出来整了一篇文章。那么接下来自然而然的就到了ViewModel,为了让系列像系列的样子,所以这里仍然是单独把ViewModel拿出来。

你别说单独抽出来,还真有点干干巴巴,麻麻赖赖,一点都不圆润。那还说啥呢?盘它...

一点点入坑JetPack:ViewModel篇

一点点入坑JetPack:Lifecycle篇

一点点入坑JetPack:LiveData篇

一点点入坑JetPack:实战前戏NetworkBoundResource篇

一点点入坑JetPack(终章):实战MVVM

正文

一、ViewModel

新官上任三把火,强敌面前秀三波。对于ViewModel来说,它算是JetPack框架中堪当中枢的角色,说实话它实在不好单独去聊,更多的是和LiveData共进退。这里必须安利一下,ViewModel+LiveData的确很好用,甚至可能加上Room简直...飘了,拽了,感觉自己个头都不矮了;疯了,狂了,敢在宇宙之间称王了....

碍于篇幅的原因,这里单独聊ViewModel,后边会综合介绍展现其强大的战斗力...

关于ViewModel来说,其实还是蛮简单的。从ViewModel官方的描述来看ViewModel的存在,解决了俩大问题:

1.1、解决问题1

我们都知道,当我们的Activity/Fragment因为某些因素被销毁重建时,我们的成员变量便失去了意义。因此我们常常需要通过 onSaveInstanceState()和onCreate()/onSaveInstanceState(Bundle)完成对数据的恢复(通常还要保证其正确的序列化)。并且对于大型数据来书,便有些乏力,比如:List、Bitmap...

而ViewModel就是解决此问题。

1.2、解决问题2

另一个问题是Activity/Fragment经常需要进行一些异步操作。一旦涉及到异步,我们都明白这里存在内存泄漏的可能性。因此我们保证Activity/Fragment在销毁后及时清理异步操作,以避免潜在的内存泄漏。

ViewModel并没有自动帮我们解决这个问题,而是通过onCleared()交给我们业务自己重写去处理。

1.3、使用方法

关于ViewModel的使用,实在没啥好说的。实在是太简单了,一个简单的demo:

class MyViewModel : ViewModel() {
    var name: String = "MDove"
}

// Activity中调用
class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val model = ViewModelProviders.of(this).get(MyViewModel::class.java)
        // TODO model.name
    }
}

我们只需要将想要被保存、被管理的变量,声明在ViewModel的实现类中即可。然后通过ViewModelProviders.of()/get()拿到这个实例。就可能像往常一样自由的使用,而不需要担心Activity/Fragment重建所带来的一系列问题。

注意警告!

文档在此处,有一个大大的警告:Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.

为啥?从上述解决的问题来看,ViewModel很明显生命周期会比Activity要长,因此如果持有Activity相关实例,必然会带来内存泄漏。(那如果的确有业务需要咋整?使用AndroidViewModel(application)即可。)

1.4、Fragment共享

值得注意的一点:of方法需要传递一个Activity/Fragment。因为ViewModel需要与其生命周期绑定。既然可以传递一个Activity,那么我们就能够猜到:是不是对于此Activity下的Fragment这个ViewModel也是可见的?

没错,正是如此。官方也作出了解读:Activity中的两个或多个Framgent需要相互通信是很常见的,这个常见的痛点可以通过使用ViewModel对象来解决,这些Fragment可以共享ViewModel来处理通信问题。

所以我们在同Activity下,不同的Fragment实例,可以直接通过传入activity,拿到同样的ViewModel实例,进而实现数据通讯。

真的很方便...

二、源码

如果我们打开ViewModel的源码,我们会发现...

public abstract class ViewModel {
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}

就是一个抽象类,没错,整个ViewModel的设计就是很简洁,我们往ViewModelProviders中继续看:

@NonNull
@MainThread
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);
}

我们可以看到在实例化ViewModelProvider中,需要传一个ViewModelStore,而这个ViewModelStore直接通过传入的FragmentActivity中拿,让我们走进去看一看:

@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) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

我们可以看到,这个ViewModelStore是在FragmentActivity中是一个mViewModelStore的变量。这个ViewModelStore是什么?从名字可以看出它是一个ViewModel的Store。

ViewModelStore的很简单,就是一个Map在后文中会展开。

最开始我看到这时,很懵。ViewModel是保证我们重建后实例的唯一,可是这竟然是一个成员变量,很明显重建后变量就没了?!...(PS:当然有这种疑问,是因为我自己蠢...)

怎么肥死,小老弟??...其实这里是没问题的,我们仔细看一看,这个mViewModelStore赋值是通过这一行代码:

NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
    mViewModelStore = nc.viewModelStore;
}

没错,就是这行代码,保证了我们重建后恢复原来的mViewModelStore,进而保证了我们的ViewModel的唯一性。

@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
           // TODO: log a warning.
        }
    }

    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

mViewModelStore源码 -> ViewModelStore源码,很常见的Map存储操作

// 
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);
    }

    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

三、小总结

从ViewModel的使用上来说,似乎并没有什么“船新的版本”...更多的是帮我们搞定了一些现存的坑。的确是如此,但其实ViewModel更多是带来了一种思想:数据驱动,也就是MVVM。

ViewModel作为中枢,担任了从数据源拿数据,交由LiveData通知UI层更新UI。用一张图来解释这种变革:

Google Sample为Repository的编写,提供了一个很巧妙的设计:NetworkBoundResource。全类一共有120+的代码,却基于LiveData+ViewModel帮我们约束了:从服务器取从数据库取网络获取失败,从数据库取...等等一系列网络请求、本地请求约数。

关于这个类的设计与用法,会在后续的实战篇一点点展开。没错,当你用上它们,你会爱上这款“游戏”。

尾声

今天的文章想聊的内容就到此结束了,更多的是ViewModel的一个引子。毕竟对于我们来说,我tm不需要知道这些,只需要告诉我怎么写就行。老夫写代码就是ctrl+c/v!

不着急,一点点来。后边我会把业务中正在运行的代码拿出来,做实战操作分析。饭要一口口的吃,文章要一篇篇的写...

我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,以及我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

个人公众号:咸鱼正翻身