Jetpack框架探究03:ViewModel组件的使用与源码分析
2021/4/12 20:25:28
本文主要是介绍Jetpack框架探究03:ViewModel组件的使用与源码分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
ViewModel 具备宿主生命后期感知能力的数据存储组件,使用 ViewModel 保存的数据,在页面因配置变更
导致页面销毁重建之后依然也是存在的,其中配置变更主要是指横竖屏切换、分辨率调整、权限变更、系统字体样式变更。ViewModel 的优势:
- 页面更改数据不丢失
当设备因配置更改导致 Activity/Fragment 重建,ViewModel 中的数据并不会因此而丢失,配合 LiveData 可以在页面重建后立马能收到最新保存的数据用以重新渲染页面。
- 生命周期感应
在 ViewModel 中难免会做一些网络请求或数据的处理,可以复写 onCleared() 方法,终止清理一些操作,释放内存。该方法在宿主 onDestroy 时被调用。
- 数据共享
对于单 Activity 对 Fragment 的页面,可以使用 ViewModel 实现页面之间的数据共享,实际上不同的 Activity也可以实现数据共享。
1. ViewModel基本使用
(1)首先,在module的build.gradle中添加依赖;
//通常情况下,只需要添加appcompat就可以了 api 'androidx.appcompat:appcompat:1.1.0' //如果想单独使用,可引入下面的依赖 api 'androidx.lifecycler:lifecycle-viewmodel:2.0.0'
(2)其次,实现一个与当前Activity/Fragment关联的ViewModel;
通常一个Activity对应一个ViewModel,Activity可以由多个Fragment组成,因此多个Fragment共享这个ViewModel对象存储的数据(注:实际上是LiveData存储的数据
)。当然,多个Activity也可以共享同一个ViewModel,但是需要在Application中特殊配置;
class SharedViewModel : ViewModel() { val selected = MutableLiveData<Item>() fun select(item: Item) { selected.value = item } }
(3)最后,实例化一个ViewModel对象。
在ViewModel中,我们结合LiveData即可实现一个MVVM架构开发模式,其中,ViewModel将充当VM层,负责从M层获取数据并通知V层自动更新UI(LiveData实现
)。
// 获取Activity对应的ViewModel // 该ViewModel的生命周期与Activity一致 val viewModel = ViewModelProvider(activity).get(SharedViewModel::class.java) // 在Fragment中获取Activity的ViewModel // 那么Activity与Fragment共享该ViewModel对象 val viewModel = ViewModelProvider(requestActivity()).get(SharedViewModel::class.java) // 创建一个Fragment的ViewModel对象 // 该ViewModel的生命周期与Fragment一致 val viewModel = ViewModelProvider(fragment).get(SharedViewModel::class.java)
当然,我们也可以Kotlin的特性更容易的获取ViewModel对象,但需要注意的是,这需要在项目中分别依赖activity-ktx
和fragment-ktx
库。
// Activity及其两个Fragment获得得将是同一个SharedViewModel对象 // 即共享数据 class MyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { val model: SharedViewModel by viewModels() model.selected().observe(this, Observer<Item>{ item -> }) } } class MasterFragment : Fragment() { private lateinit var itemSelector: Selector private val model: SharedViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) itemSelector.setOnClickListener { item -> // Update the UI } } } class DetailFragment : Fragment() { private val model: SharedViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) model.selected.observe(viewLifecycleOwner, Observer<Item> { item -> // Update the UI }) } }
2. ViewModel源码解析
2.1 ViewModel的生命周期
2.2 ViewModel实现原理
ViewModel框架UML类图如下:
具体说明如下:
- ViewModelProvider
ViewModelProvider
是提供创建ViewModel对象的入口,它的构造方法需要传入一个ViewModelStoreOwner对象和一个Factory对象,其内部将通过工厂模式
完成对ViewModel实例的创建。源码如下:
public class ViewModelProvider { private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"; ... public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { mFactory = factory; mViewModelStore = store; } public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { // 具体工厂创建ViewModel实例 } }
- ViewModelStoreOwner
ViewModelStoreOwner
是一个接口,通过实现该接口,用于表明自己是一个ViewModelStore
的拥有者(owner
)。Activity/Fragment均继承了这个接口,在Activity/Fragment中,可以通过getViewModelStore()
方法获取与其关联的ViewModelStore对象。源码如下:
public interface ViewModelStoreOwner { @NonNull ViewModelStore getViewModelStore(); }
- ViewModelStore
ViewModelStore
是一个ViewModel实例存储仓库,它内部维护了一个HashMap集合,实现存储多个ViewModel实例。在Activity/Fragment中,可以通过调用其getViewModelStore()
方法来获取与之关联的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()); } public final void clear() { for (ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); } }
- Factory
在ViewModel框架中,是通过工厂模式实现对ViewModel对象的创建,其中,Factory就是所有具体工厂类的父接口,它提供了一个通用的方法create
来创建一个ViewModel对象,但具体的实现是在具体工厂类中,如KeyedFactory、SavedStateViewModelFactory 、NewInstanceFactory、AndroidViewModelFactory。源码如下:
public interface Factory { @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass); }
2.2.1 创建ViewModel对象过程
首先,创建一个ViewModelProvider对象,其构建方法中需要传入一个ViewModelStoreOwner
和指定创建ViewModel实例的工厂Factory
,这个工厂对象可选,对于Activity和Fragment来说,默认调用ViewModelStoreOwner
的getDefaultViewModelProviderFactory()方法获取,因为它们均实现了HasDefaultViewModelProviderFactory接口。代码如下:
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) { // owner即Activity或Fragment // 它们均继承了ViewModelStoreOwner和HasDefaultViewModelProviderFactory this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance()); } public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) { this(owner.getViewModelStore(), factory); } // ViewModelStore: ViewModel仓库,存储所有ViewModel对象 // factory:创建ViewModel的具体工厂 public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { mFactory = factory; mViewModelStore = store; }
其次,通过工厂模式
实例化一个ViewModel对象,并返回。具体过程如下:
- a. 检查ViewModelStore仓库中是否缓存了要创建的ViewModel对象;
- b. 如果没有指定工厂,就使用SavedStateViewModelFactory具体工厂来创建ViewModel对象;
- c. 将创建的ViewModel对象缓存到ViewModelStore仓库中。
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { // 1. 首先,检查是否存在modelClass的缓存 // (1)尝试从ViewModel仓库中获取key对应的ViewModel对象 // 其中,mViewModelStore = owner.getViewModelStore() ViewModel viewModel = mViewModelStore.get(key); // (2) 核实viewModel是否确实为modelClass的一个实例 // 如果是,直接return即可 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. } } // 2. 否则,判断mFactory是哪一个工厂实例 // 并调用对应的create方法创建ViewModel对象 if (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass); } else { viewModel = (mFactory).create(modelClass); } // 3. 将创建的ViewModel对象缓存到ViewModel仓库中 mViewModelStore.put(key, viewModel); return (T) viewModel; }
根据ViewModelProvider的构造方法可知,假如我们在Activity或Fragment中创建ViewModel对象的话,mFactory将是它们的getDefaultViewModelProviderFactory()
方法返回的具体工厂对象,即均为SavedStateViewModelFactory。代码如下:
//.../androidx/activity/ComponentActivity.java public class ComponentActivity extends androidx.core.app.ComponentActivity implements LifecycleOwner, ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner, OnBackPressedDispatcherOwner { ... 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."); } // 创建SavedStateViewModelFactory对象 if (mDefaultFactory == null) { mDefaultFactory = new SavedStateViewModelFactory( getApplication(), this, getIntent() != null ? getIntent().getExtras() : null); } return mDefaultFactory; } } // .../androidx/fragment/app/Fragment.java public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner, ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner { ... public ViewModelProvider.Factory getDefaultViewModelProviderFactory() { if (mFragmentManager == null) { throw new IllegalStateException("Can't access ViewModels from detached fragment"); } // 创建SavedStateViewModelFactory对象 if (mDefaultFactory == null) { mDefaultFactory = new SavedStateViewModelFactory( requireActivity().getApplication(), this, getArguments()); } return mDefaultFactory; } }
接着,我们来看一下SavedStateViewModelFactory#create
是如何构建ViewModel对象。具体过程如下:
- a. 检查要创建的ViewModel是否包含参数
savedStateHandle
的构造方法,分实现AndroidViewModel与否; - b. 如果不存在(a)描述的构造方法,则使用AndroidViewModelFactory具体工厂创建ViewModel对象;
- c. 否则,使用(a)返回的构造方法反射实例化一个ViewModel对象。
public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) { // 1. 首先,查找是否存在modelClass的构造方法 // 根据modelClass是否继承于AndroidViewModel分两种情况 // 如果是,查找含有(application, savedStateHandle)参数的构造方法; // 否则,查找含有(savedStateHandle)参数的构造方法; boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass); Constructor<T> constructor; if (isAndroidViewModel) { constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE); } else { constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE); } // doesn't need SavedStateHandle // 2. 其次,如果没有找到 // 说明要创建的ViewModel不包含对应的构造方法 // 此时也说明,这里不需要SavedStateHandle if (constructor == null) { return mFactory.create(modelClass); } // 3. 找到对应的构造方法 // 处理需要SavedStateHandle的情况(本文暂时不考虑) SavedStateHandleController controller = SavedStateHandleController.create( mSavedStateRegistry, mLifecycle, key, mDefaultArgs); try { 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()); } }
由SavedStateViewModelFactory
的构造方法可知,上述代码的mFactory
即位AndroidViewModelFactory
的一个实例。代码如下:
public SavedStateViewModelFactory(@NonNull Application application, @NonNull SavedStateRegistryOwner owner, @Nullable Bundle defaultArgs) { mSavedStateRegistry = owner.getSavedStateRegistry(); mLifecycle = owner.getLifecycle(); mDefaultArgs = defaultArgs; mApplication = application; // 单利模式创建AndroidViewModelFactory mFactory = ViewModelProvider.AndroidViewModelFactory.getInstance(application); }
AndroidViewModelFactory继承于NewInstanceFactory,它们均是Factory的具体工厂实现类,需要注意的是,如果我们自定义的ViewModel继承于AndroidViewModel,那么就使用AndroidViewModelFactory具体工程来实例化该对象,否则,使用NewInstanceFactory具体工程来实例化。代码如下:
public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory { private static AndroidViewModelFactory sInstance; @NonNull public static AndroidViewModelFactory getInstance(@NonNull Application application) { if (sInstance == null) { sInstance = new AndroidViewModelFactory(application); } return sInstance; } private Application mApplication; public AndroidViewModelFactory(@NonNull Application application) { mApplication = application; } @NonNull @Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { // 1. 如果modelClass是AndroidViewModel的子类 // 调用其指定的构造方法来实例化一个对象 if (AndroidViewModel.class.isAssignableFrom(modelClass)) { try { return modelClass.getConstructor(Application.class).newInstance(mApplication); } catch (NoSuchMethodException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } catch (IllegalAccessException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } catch (InstantiationException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } catch (InvocationTargetException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } } // 2. 否则,调用父类NewInstanceFactory的create方法 // 该方法会使用默认的构造方法实例化一个对象 // modelClass.newInstance() return super.create(modelClass); } } public static class NewInstanceFactory implements Factory { private static NewInstanceFactory sInstance; @NonNull static NewInstanceFactory getInstance() { if (sInstance == null) { sInstance = new NewInstanceFactory(); } return sInstance; } @SuppressWarnings("ClassNewInstance") @NonNull @Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { try { // 使用默认构造方法 // 实例化一个对象 return modelClass.newInstance(); } catch (InstantiationException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } catch (IllegalAccessException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } } }
2.2.2 ViewModel复用原因分析
由ViewModelProvider$get
方法可知,当ViewModel被实例化完毕后,将会被存储到ViewModel的仓库ViewModelStore中(注:Activity可以关联多个ViewModel对象
),所有Activity与其关联的ViewModel对象都会缓存到这个ViewModelStore的Map集合中。在Activity获取ViewModelStore对象如下:
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."); } // 1. 判断ViewModelStore是否存在,没有存在则创建一个 if (mViewModelStore == null) { //(1) 查看NonConfigurationInstances中是否有缓存 // 如果有就返回这个缓存 // static final class NonConfigurationInstances { // Object custom; // ViewModelStore viewModelStore; // } // 注:NonConfigurationInstances为ComponentActivity的静态内部类 // 被final修饰,即不可被继承 NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null) { // Restore the ViewModelStore from NonConfigurationInstances mViewModelStore = nc.viewModelStore; } //(2) 否则,直接new一个 if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); } } // 2. 否则,直接返回已经存在的ViewModelStore return mViewModelStore; }
由上述代码可知,getViewModelStore()方法中会首先从NonConfigurationInstances
来获取ViewModelStore
实例对象。那么,ViewModelStore何时被存储到NonConfigurationInstances
?答案是:onRetainNonConfigurationInstance
。因系统原因页面被回收时,会触发该方法,所以 viewModelStore 对象此时会被存储在NonConfigurationInstance 中。在页面恢复重建时,会再次把这个 NonConfigurationInstance 对象传递到新的Activity 中实现对象复用。onRetainNonConfigurationInstance()
源码如下:
public final Object onRetainNonConfigurationInstance() { Object custom = onRetainCustomNonConfigurationInstance(); ViewModelStore viewModelStore = mViewModelStore; if (viewModelStore == null) { // 如果NonConfigurationInstance保存了viewModelStore,把它取出来 NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null) { viewModelStore = nc.viewModelStore; } } if (viewModelStore == null && custom == null) { return null; } NonConfigurationInstances nci = new NonConfigurationInstances(); nci.custom = custom; //把viewModelStore放到NonConfigurationInstances中并返回 nci.viewModelStore = viewModelStore; //把viewModelStore放到NonConfigurationInstances中并返回 return nci; }
2.2.3 ViewModel#onClear()被调用过程
ViewModel的onClear()
方法是在Activity被销毁被回调,即通过监听Activity的Lifecycle实现。当生命周期为onDestory时,会去调用ViewModelStore的clear()方法,在该方法中会遍历集合中的ViewModel对象,并依次调用其onClear()
方法,最后,再将集合清空。相关源码如下:
// .../androidx/activity/ComponentActivity.class public ComponentActivity() { Lifecycle lifecycle = getLifecycle(); ... getLifecycle().addObserver(new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event == Lifecycle.Event.ON_DESTROY) { if (!isChangingConfigurations()) { getViewModelStore().clear(); } } } }); } // .../androidx/lifecycle/ViewModelStore.class public final void clear() { for (ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); } // .../androidx/lifecycle/ViewModel.class protected void onCleared() { } @MainThread final void clear() { mCleared = true; if (mBagOfTags != null) { synchronized (mBagOfTags) { for (Object value : mBagOfTags.values()) { // see comment for the similar call in setTagIfAbsent closeWithRuntimeException(value); } } } onCleared(); }
强调一点:ViewModel只是针对于页配置变更
时,ViewModel能够实现复用,对于因内存不足或者因为电量不足导致页面被回收情况,是无法实现ViewModel复用的,此时就需要ViewModel配合SavedState
实现,它将承担起ViewModel与onSaveIntanceState之间通信的桥梁,其中,onSaveIntanceState在非配置变更导致页面被回收时被触发。最后,总结一下ViewModel与onSaveIntanceState方法区别:
- onSaveIntanceState
onSaveIntanceState只能存储轻量级的 key-value 键值对数据,非配置变更导致的页面被回收时才会触发,此时数据存储在 ActivityRecord 中;
- ViewModel
ViewModel可以存放任意 Object 数据,因配置变更导致的页面被回收才有效。此时存在ActivityThread#ActivityClientRecord 中。
Github源码:ExampleJetpack
这篇关于Jetpack框架探究03:ViewModel组件的使用与源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-15在使用平台私钥进行解密时提示 "私钥解密失败" 错误信息是什么原因?-icode9专业技术文章分享
- 2024-11-15Layui框架有哪些方式引入?-icode9专业技术文章分享
- 2024-11-15Layui框架中有哪些减少对全局环境的污染方法?-icode9专业技术文章分享
- 2024-11-15laydate怎么关闭自动的日期格式校验功能?-icode9专业技术文章分享
- 2024-11-15laydate怎么取消初始日期校验?-icode9专业技术文章分享
- 2024-11-15SendGrid 的邮件发送时,怎么设置回复邮箱?-icode9专业技术文章分享
- 2024-11-15使用 SendGrid API 发送邮件后获取到唯一的请求 ID?-icode9专业技术文章分享
- 2024-11-15mailgun 发送邮件 tags标签最多有多少个?-icode9专业技术文章分享
- 2024-11-15mailgun 发送邮件 怎么批量发送给多个人?-icode9专业技术文章分享
- 2024-11-15如何搭建web开发环境并实现 web项目在浏览器中访问?-icode9专业技术文章分享