ViewModel组件

1、什么是 ViewModel

1.1、先考虑两个场景

  • 场景一:我们开发的 APP 可以转屏,转屏后将触发 Controller(Activity or Fragment) 的重建,为了维护转屏前后数据的一致性,我们要么将需要维护的数据以 Bundle 的形式在 onSaveInstance中保存,必要的时候需要对复合数据实现繁琐的 Parceable 接口,如果数据量太大则我们必须将数据持久化,在转屏后重新拉取数据(from database or networks);
  • 场景二:我们的 Activity 中同时维护了多个 Fragment,每个 Fragment 需要共享一些数据,传统的做法是由宿主Activity 持有共享数据,并暴露数据获取接口给各个寄生 Fragment。

随着业务规模的扩大,以上的两种场景在传统实现方法中显得越来越繁琐且不易维护,且数据模块不易独立进行测试。

特别说明:

  • 关于场景一,同样的场景还适用于各种配置相关的信息发生变化的情况,比如键盘、系统字体、语言区域等,它们的共同作用是都会导致当前Controller 的重建。

1.2、视图与数据模型之间的桥梁ViewModel

在页面(Activity/Fragment)功能较为简单的情况下,我们通常会将UI交互、与数据获取等相关的业务逻辑全部写在页面中。但是在页面功能复杂的情况下,代码量会变的非常多,也违反了"单一功能原则"。 页面只应该负责处理用户与UI控件的交互,并将数据展示到屏幕上,而数据获取相关的业务逻辑应该单独处理和存放。
在这里插入图片描述

ViewModel 解决的问题:

ViewModel 是 android 新的 mvvm 框架的一部分,它的出现就是为了解决以上两个场景中数据与 Controller 耦合过度的问题。

  • 其 基本原理 是维护一个与配置无关的对象,该对象可存储 Controller 中需要的任何数据,其生命周期与宿主Controller 的生命周期保持一致,不因 Controller 的重建而失效
    (注意:Controller 的重建仍然在 Controller 生命周期内,并不会产生一个新的生命周期,即 Controller 的 onDestroy 并不会调用)

这意味着无论是转屏还是系统字体变化等因配置变化产生的 Controller 重建都不会回收 ViewModel 中维护的数据,重建的 Controller 仍然可以从同一个 ViewModel 中通过获取数据恢复状态。

2、ViewModel的生命周期

在这里插入图片描述
ViewModel的生命周期会比创建它的Activity、Fragment的生命周期都要长。即ViewModel中的数据会一直存活在Activity/Fragment中。

3、ViewModel的使用

1、首先引入ViewModel的相关依赖

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

2、布局文件activity_view_model.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ViewModelActivity">

    <TextView
        android:id="@+id/tv_figure"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="0"
        android:textSize="20sp" />

    <Button
        android:id="@+id/btn_add1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="40dp"
        android:text="+1" />

    <Button
        android:id="@+id/btn_add2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp"
        android:text="+2" />
</RelativeLayout>

3、创建MyViewModel类继承于ViewModel

public class MyViewModel extends ViewModel {
    public int number=0;
}

4、ViewModelActivity

public class ViewModelActivity extends AppCompatActivity {
    private TextView tv_figure;
    private Button btn_add1, btn_add2;
    private MyViewModel myViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_model);
        tv_figure = findViewById(R.id.tv_figure);
        btn_add1 = findViewById(R.id.btn_add1);
        btn_add2 = findViewById(R.id.btn_add2);
        //这个获取MyViewModel对象的方法已经被废弃了
//        myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        //创建MyViewModel对象
        myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
        tv_figure.setText(String.valueOf(myViewModel.number));
        btn_add1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myViewModel.number++;
                tv_figure.setText(String.valueOf(myViewModel.number));
            }
        });
        btn_add2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myViewModel.number += 2;
                tv_figure.setText(String.valueOf(myViewModel.number));
            }
        });
    }
}

实验结果:
在这里插入图片描述

4、ViewModel的原理

我们用一个结构图来剖析 ViewModel 的构造过程:
在这里插入图片描述
如图所示:

  • 所有已经实例化的 ViewModel 都缓存在一个叫做 ViewModelStore 的封装对象中,其实质是一个 HashMap;
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();
    }
}
  • ViewModelStore 与具体的 Controller 绑定,并与宿主 Controller 俱生俱灭,所以这就解释了为何ViewModel 与宿主 Controller 的生命周期是一样长了,因为缓存它的 ViewModelStore 与宿主 Controller 寿命相等;
  • 获取 ViewModel 实例的过程委托给了一个叫做 ViewModelProvider 的工具类,它包含一个创建 ViewModel的工厂类 Factory 和一个对 ViewModelStore 的引用;

总的构造过程为:先从 ViewModelStore 中获取缓存的 ViewModel,若没有缓存过则用 Facotry 实例化一个新的 ViewModel 并缓存,具体的过程分为 4 步,具体可参考图示。

本小节剩下部分分析源码,对于只关心原理的同学此部分可以略过:

我们在获取 ViewModel 的时候,一般通过如下方式:

// 在 Controller(这里以 Fragment 为例)的 onCreate 方法中调用
final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);

我们看一下 ViewModelProviders.of() 的实现:

public static ViewModelProvider of(@NonNull Fragment fragment) {
    return of(fragment, null);
}
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
    Application application = checkApplication(checkActivity(fragment));
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }

    // 最终用宿主 Controller 的 ViewModelStore 和一个 Factory 实例化一个
    // ViewModelProvider
    return new ViewModelProvider(fragment.getViewModelStore(), factory);
}

我们再看一下 ViewModelProvider.get() 方法获取 ViewModel 实例的过程:

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }

    // 我们看到了 ViewModel 在 ViewModelStore 中的 key 表示
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

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.
        }
    }

    // 缓存中没有,通过 Factory 构造
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }

    // 新实例保存缓存
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

4.1、问题

看到这里,大家是否都有个疑问,明明ViewModelProvider需要传入ViewModelStoreOwner对象,那为什么我们第三节的demo中传入this(当前Activity)就可以实例化呢?

myViewModel = new ViewModelProvider(this).get(MyViewModel.class);

因为ViewModelActivity继承自AppCompatActivity, AppCompatActivity继承自FragmentActivityFragmentActivity继承自ComponentActivity,而ComponentActivity实现了ViewModelStoreOwner接口,这个接口中有一个getViewModelStore()方法返回的是ViewModelStore

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner 
.....

@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;
    }

参考

1、Android架构之ViewModel组件
2、Android之ViewModel的使用
3、Android ViewModel,再学不会你砍我
4、Android mvvm框架之ViewModel篇
5、Android JetPack组件之ViewModel的使用详解

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐