Android Jetpack之ViewModel的使用及源码分析

ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。

现在,常用的项目架构,在MVP向MVVM转变。相对于MVP中的P(presenter),MVVM中的ViewModel有哪些优势呢。

ViewModel的优势

  • 可以持久的保留界面状态(比如,屏幕旋转)
  • 提供了对业务逻辑的访问权限()

我们先通过代码来看下ViewModel的使用。然后,看下ViewModel的原理

本文内容:

一、viewModel的使用

之前,学习了LiveData。这里,我们用LiveData+ViewModel组合的方式使用。

在这里插入图片描述

首先创建一个ViewModel,用来更新UI的数据

public class MyViewModel extends ViewModel {
    //liveData
    public MutableLiveData<String> name = new MutableLiveData<>();

    /**
     * 获取用户名字
     */
    public void obtainUserName() {
        name.setValue("张三");
    }
}


下面创建一个Activity,用2个TextView,1个用ViewModel更新数据;一个用普通方式更新数据

public class ViewModelActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_livedata_test);
        //textView用viewModel更新数据
        final TextView tvContent = findViewById(R.id.tv_content);
        
        //textView用普通方式更新数据
        final TextView tvUnUnUsedViewModelContent = findViewById(R.id.tv_content_unused_viewmodel);
        //获取viewModel实例
        final MyViewModel myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
        //添加LiveData观察者
        myViewModel.name.observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                tvContent.setText(s);
            }
        });
        //点击按钮,更新2个TextView的数据
        findViewById(R.id.btn_livedata_change).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myViewModel.obtainUserName();
                tvUnUnUsedViewModelContent.setText("xxx");
            }
        });
    }
}


最后,就是清单文件,什么也不配置

到这里,我们运行项目后,点击按钮,数据都是正常显示的。

在这里插入图片描述

但是,如果,我们旋转屏幕的话,就会发现第一个TextView的数据还存在,但是,第二个TextView的数据,就回到了默认状态
在这里插入图片描述

到这里,ViewModel+LiveData的基本使用就介绍完啦

  • 创建ViewModel
  • Activity获取ViewModel对象
  • 添加ViewModel里面的LiveData观察者

当数据改变的时候,UI的状态就可以自动改变了。

但是,ViewModel相对于普通的数据更新来说,它的优势,就是在旋转屏幕的时候,数据也不会丢失。

并且,ViewModel 的作用域将限定为 ViewModelStoreOwner 的 Lifecycle。它会一直保留在内存中,直到其 ViewModelStoreOwner 永久消失。ViewModelStoreOwner的实现类有(Activity、Fragment等)

ViewModel使用起来很简单,那一块看看是如何实现的吧

二、ViewModel源码分析

ViewModel的使用,只是通过实例化出来一个ViewModel,然后,调用ViewModel内部的数据来进行更新数据。

我们就来看看ViewModel是怎么实例化的。实例化代码

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

这里,我们分为2步

  • 1,new ViewModelProvider获取provider
  • 2,通过get(XXViewModel.class)获取ViewModel的实例

先看下new ViewModelProvider()做了什么操作

2.1, new ViewModelProvider(this)

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

...

//实现了ViewModelStoreOwner,HasDefaultViewModelProviderFactory接口
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner 
	

我们在new ViewModelProvider()传入了this(Activity)。
这里看下Activity,它实现了ViewModelStoreOwner和HasDefaultViewModelProviderFactory接口

实例化ViewModelProvider,调用this的构造器,相当于this(activity.getViewModelStore(),activity.getDefaultViewModelProviderFactory())

相当于2个参数都从activity内获取。

先往下看this()2个参数的构造器,最后,看下获取的内容是什么

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

我们发现,ViewModelProvider就是保存数据

  • 保存ViewModelStore
  • 保存factory

下面,看看从activity获取的factory跟ViewModelStore

先看下获取ViewModelStore的代码

  • ComponentActivity#getViewModelStore()
    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.");
        }
		//如果是null的话
        if (mViewModelStore == null) {
			//查看,最后一次配置(横竖屏)时候,里面是否已经保存了ViewModelStore
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
				//如果已经保存了,这里直接恢复
                mViewModelStore = nc.viewModelStore;
            }
			//没有保存,就new一个新的出来
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

通过上面的代码,我们可以发现,如果是横竖屏切换的话。

getLastNonConfigurationInstance()里面会保存之前的viewmodelstore的话,就直接恢复了之前的viewModelStore,这也就是为什么,我们横竖屏切换的话,数据不会丢失的原因了。

  • 获取factory的代码
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        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 (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }

工厂如果是null的话,直接实例化一个SavedStateViewModelFactory工厂

到这里new ViewModelProvider() 就看完啦

整体的流程:

在这里插入图片描述

2.2,ViewModelProvider#get(MyViewModel.class)分析

从传入的class,我们基本上也能猜测,应该是通过反射重建的ViewModel

看下具体代码


    @MainThread
    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");
        }
		//这里传入的是类的全路径 (xx.xx.Test),class
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

看下get重载方法


 @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
		//查看store里面是否保存了该key的ViewModel
		//如果是横竖屏切换的话,store中是保存有ViewModel的
        ViewModel viewModel = mViewModelStore.get(key);
		//如果viewModel是该class的。直接返回
        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
		//通过上面,我们知道activity里面默认的Factory是SavedStateViewModelFactory
		//SavedStateViewModelFactory实现了KeyedFactory类
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
		//创建的viewmodel保存到store中
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

这里面,先查看了viewModeStore是否保存了该ViewModel,如果保存了直接返回,没有的话,调用create()方法创建,并且保存到viewModelStore中。

主要作用:

  • 从ViewModelStore中获取ViewModel
  • 如果上面没有,通过Factory#create()方法获取ViewModel
  • 把ViewModel放到ViewModelStore中

我们看看Factory#create()方法
上面说到了,默认的Factory是SavedStateViewModelFactory,它继承了KeyedFactory.直接看它的create()方法

SavedStateViewModelFactory.java

	@Override
    public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
        boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
        Constructor<T> constructor;
		//获取ViewModel构造器类型(AndroidViewModel还是ViewModel)
        if (isAndroidViewModel) {
            constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
        } else {
            constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
        }
        // doesn't need SavedStateHandle
        if (constructor == null) {
            return mFactory.create(modelClass);
        }

        SavedStateHandleController controller = SavedStateHandleController.create(
                mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
        try {
			//通过反射获取ViewModel
            T viewmodel;
            if (isAndroidViewModel) {
                viewmodel = constructor.newInstance(mApplication, controller.getHandle());
            } else {
                viewmodel = constructor.newInstance(controller.getHandle());
            }
            viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
            return viewmodel;
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Failed to access " + modelClass, e);
        } catch (InstantiationException e) {
            throw new RuntimeException("A " + modelClass + " cannot be instantiated.", e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("An exception happened in constructor of "
                    + modelClass, e.getCause());
        }
    }

Facory获取ViewModel,主要是获取合适的构造器通过反射创建ViewModel对象。

到这里,我们查看完了ViewModel获取的全过程。

获取ViewModel的整体流程图

在这里插入图片描述

三、其它的相关知识点

3.1 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();
    }
}

通过上面,我们知道,这个类就是封装了一下Map对象。缓存的ViewModel也是缓存到了Map里面。

3.2 viewModel怎么做到在Activity重建的时候,依然能保存数据

在Activity发生屏幕旋转的时候,会执行Activity的onRetainCustomNonConfigurationInstance()方法

我们看下ComponentActivity#onRetainCustomNonConfigurationInstance()方法

  @Nullable
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
			//如果viewModelStore不是空的话,会获取他最后的配置
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }

        if (viewModelStore == null && custom == null) {
            return null;
        }
		//把viewModelStore的内容重新恢复
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

这里,可以明显看到,viewModelStore里面的内容在这里被恢复了。

那么,Activity重建的时候,数据又是怎么保存的呢?

在Activity屏幕旋转的时候,整体的流程应该是

  • 在Activity销毁之前,执行了ActivityThread#performDestroyActivity()方法
    如果是Activity重建的话,会把activity的配置NonConfigurationInstances信息保存到ActivityClientRecord 里面
  • 在Activity重建的时候,activity#performLaunchActivity(ActivityClientRecord,Intent)方法
  • 执行activity.attach()方法把lastNonConfigurationInstances对象传过去。

这样,就保证了ViewModelStore内容的恢复。(中间省略了很多过程,有兴趣的,自己看下)

ViewModel声明周期。Activity 经历屏幕旋转而后结束时所处的各种生命周期状态。该图还在关联的 activity 生命周期的旁边显示了 ViewModel 的生命周期。此图表说明了 activity 的各种状态。这些基本状态同样适用于 fragment 的生命周期。

官方配图
在这里插入图片描述

3.3,ViewModel的数据,会在什么时候被清除呢

在Activity初始化的时候,就通过Lifecycle进行了判断,代码

 public ComponentActivity() {
	...
 	getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
				//当页面销毁时
                if (event == Lifecycle.Event.ON_DESTROY) {
					//并且不是横竖屏切换等reLaunchActivity的情况
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });

	}

在Activity初始化的时候,已经为ViewModelStore设置了数据会在页面onDestroy时清理。

官方文档:https://developer.android.google.cn/topic/libraries/architecture/viewmodel


ViewModel类型:SharedViewModel,AndroidViewModel,ViewModel,SavedStateViewModel

猜你喜欢

转载自blog.csdn.net/ecliujianbo/article/details/128023830