一、引言
在现代Android应用开发中,处理UI数据的有效管理和状态保持是开发者面临的重要挑战之一。Google推出的Jetpack组件库中的ViewModel已成为解决这些问题的关键工具。ViewModel旨在以生命周期意识的方式存储和管理界面相关的数据,从而使数据在配置更改如屏幕旋转时依然保持不变,极大地提高了应用的稳定性和响应速度。通过本文的走读和代码示例,不仅可以帮助新手开发者快速理解ViewModel的基本用法,还可以使有经验的开发者深入掌握其高级功能和最佳实践。
二、ViewModel基本使用
在Android应用中使用ViewModel可以有效地解决界面数据管理问题。ViewModel是一个负责准备和管理Activity或Fragment中UI所需数据的类,它存在的目的是使数据能够在配置变化时生存下来,例如屏幕旋转。这种机制简化了数据处理流程,并提高了应用的健壮性。
创建ViewModel
创建一个ViewModel通常涉及扩展ViewModel类,并在其中添加存储UI所需数据的变量和方法。下面是一个简单的ViewModel实例,展示了如何存储用户的输入字符串:
import androidx.lifecycle.ViewModel
class UserInputViewModel : ViewModel() {
var userInput: String = ""
}
这个示例中,UserInputViewModel
维护一个名为userInput
的字符串。这个字符串在设备配置改变时将不会丢失,保证了用户数据的一致性。
使用ViewModel
在Activity或Fragment中使用ViewModel也非常直观。您需要通过ViewModelProvider来获取ViewModel的实例。这样做确保了配置更改时,您获得的ViewModel实例与之前的是相同的,从而数据得以保留。例如,在一个Activity中使用ViewModel来存储用户输入可以如下操作:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: UserInputViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(UserInputViewModel::class.java)
// Use viewModel to manage UI data
}
}
通过这种方式,ViewModel作为一个独立于Activity视图控制器的数据管理者,负责管理界面状态,降低了Activity或Fragment的负担,使其能够更专注于处理用户交互。
三、ViewModel的工作原理
ViewModel的核心功能是确保数据在设备配置更改时能够持久保存。这是通过将ViewModel的生命周期绑定到具体的Activity或Fragment的生命周期来实现的,确保ViewModel在屏幕旋转或其他配置变化时不会被销毁。
生命周期管理
当Activity重新创建时,例如屏幕旋转,Activity会被销毁并重新创建。如果没有ViewModel, 所有的界面数据将会丢失。但如果使用ViewModel,这些数据会被保留。这是因为ViewModel对象被设计为比它们服务的Activity或Fragment拥有更长的生命周期。
下面的代码片段展示了ViewModel如何在配置更改期间保持活动状态:
class ConfigurationViewModel : ViewModel() {
var configChangeData: String = "Initial Data"
// Data remains intact across configuration changes
}
```java
在Activity中使用此ViewModel:
class ConfigActivity : AppCompatActivity() {
private lateinit var viewModel: ConfigurationViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_config)
viewModel = ViewModelProvider(this).get(ConfigurationViewModel::class.java)
// Displaying or updating data in the UI
displayData(viewModel.configChangeData)
}
private fun displayData(data: String) {
findViewById<TextView>(R.id.data_text_view).text = data
}
}
通过这种设计,ViewModel使得数据管理独立于Activity或Fragment的生命周期,从而提高应用的稳定性和用户体验。
#### 数据的存储与管理
ViewModel不仅可以存储简单的数据,还可以与LiveData一起使用,实现数据的观察。这使得您可以构建响应式的UI组件,这些组件能够观察数据变化并做出响应。这种模式非常适合开发现代的、交互密集的应用。
```java
class LiveDataViewModel : ViewModel() {
val liveData = MutableLiveData<String>()
fun updateData(newData: String) {
liveData.value = newData
}
}
在Activity中订阅LiveData:
class LiveDataActivity : AppCompatActivity() {
private lateinit var liveDataViewModel: LiveDataViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_live_data)
liveDataViewModel = ViewModelProvider(this).get(LiveDataViewModel::class.java)
// Observing data changes
liveDataViewModel.liveData.observe(this, Observer { data ->
findViewById<TextView>(R.id.data_text_view).text = data
})
}
}
通过LiveData,ViewModel提供了一种强大的数据管理和通信机制,极大地增强了应用的交互性和灵活性。
四、源码分析
4.分析源码前的准备工作
4.1ViewModel 的生命周期
4.2.几个类的感性认识
- ViewModelStoreOwner:是一个接口,用来获取一个ViewModelStore对象
- ViewModelStore:存储多个ViewModel,一个ViewModelStore的拥有者( Activity )在配置改变, 重建的时候,依然会有这个实例
- ViewModel:一个对 Activity、Fragment 的数据管理类,通常配合 LiveData 使用
- ViewModelProvider:创建一个 ViewModel 的实例,并且在给定的ViewModelStoreOwner中存储 ViewModel
再例子中的代码
class ViewModelActivity : AppCompatActivity() {
//初始化 UserViewModel 通过 ViewModelProvider
private val userViewModel by lazy { ViewModelProvider(this)[UserViewModel::class.java] }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val button = Button(this)
setContentView(button)
//观察 User 数据,并打印
userViewModel.userLiveData.observe(this, Observer { user ->
"User = $user".log()
})
//点击按钮更新 User 信息
button.setOnClickListener {
userViewModel.updateUser()
}
}
}
首先看下UserViewModel的初始化过程。
private val userViewModel by lazy { ViewModelProvider(this)[UserViewModel::class.java] }
注:上面代码类似数组的写法是 Kotlin 的写法,其实是 ViewModelProvider 的get方法
5.ViewModelProvider的构造方法,以及 get 方法
5.1ViewModelProvider构造方法
先看ViewModelProvider构造方法,传入的参数为当前的 AppCompatActivity
//ViewModelProvider.java
private final Factory mFactor
private final ViewModelStore mViewModelStore;
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;
}
- 通过 ViewModelStoreOwner获取ViewModelStore对象并给 mViewModelStore赋值
- 给mFactory赋值,这里赋值的是NewInstanceFactory这个对象
5.2.ViewModelProvider的 get 方法
//ViewModelProvider.java
private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey";
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");
}
//1
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
注释1:
- 调用了两个参数的 get 方法
- 第一个参数是字符串的拼接,用来以后获取对应 ViewModel 实例的,保证了同一个 Key 取出是同一个 ViewModel
- 第二参数是 UserViewModel 的字节码文件对象
看下两个参数的get
方法
//ViewModelProvider.java
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);//1
//2
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.
}
}
//3
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
//4
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
注释 1:从ViewModelStore中,根据 key,取一个 ViewModel,ViewModelStore源码下文分析
注释 2:判断取出来的 ViewModel 实例和传进来的是否是一个,是同一个,直接返回此缓存中实例
注释 3:通过Factory创建一个ViewModel
注释 4:把新创建的ViewModel用ViewModelStore存储起来,以备下次使用,最后返回新创建的ViewModelStore
这里看一下ViewModel是怎么通过Factory创建出来的
通过 5.1 小节可以知道,这个Factory的实例是NewInstanceFactory
5.3.NewInstanceFactory的create方法
//ViewModelProvider.java 中的 AndroidViewModelFactory.java
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
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);
}
}
简单粗暴,通过反射,直接创建了ViewModel对象。
这里扩展一个,在实例UserViewModel的时候
private val userViewModel by lazy { ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application))[UserViewModel::class.java] }
也可以通过两个参数的构造方法,来实例化,其中第二个参数就是Factory类型。然后就会用 AndroidViewModelFactory来实例化UserViewModel,我们来具体看下代码
AndroidViewModelFactory是NewInstanceFactory的子类
//ViewModelProvider.java 中的 AndroidViewModelFactory
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//noinspection TryWithIdenticalCatches
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);
}
}
return super.create(modelClass);
}
如果我们创建的UserViewModel当初继承的是AndroidViewModel类就走modelClass.getConstructor(Application.class).newInstance(mApplication);
实例化方法,否则就走父类的实例化方法,也就是NewInstanceFactory的create方法
在开发中建议使用AndroidViewModel类,它会提供给一个Application级别的 Context。
接下来看一下ViewModelStoreOwner是什么,以及它的具体实现
6.ViewModelStoreOwner
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore getViewModelStore();
}
- 一个接口,里面一个方法返回了ViewModelStore对象
- 它的实现类在 AndroidX 中ComponentActivity和 Fragment
ComponentActivity的关键代码
//ComponentActivity.java
public class ComponentActivity extends androidx.core.app.ComponentActivity implements ViewModelStoreOwner,XXX{
private ViewModelStore mViewModelStore;
@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并返回了
来看下这个ViewModelStore类
7.ViewModelStore
7.1.ViewModelStore的源码
我下面贴的是完整代码,对你没看错。
public class ViewModelStore {
//1
private final HashMap<String, ViewModel> mMap = new HashMap<>();
//2
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
//3
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
//4
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
注释 1 :声明一个 Map 来存储ViewModel
注释 2:存储ViewModel,这个方法我们在7.2 小节ViewModelProvider的 get
方法中用到过
注释 3:取出 ViewModel,这个方法我们在7.2 小节ViewModelProvider的 get
方法中用到过。注意在从 Map中去 ViewModel 的时候是根据 Key,也就是7.2小节注释 1 拼接的那个字符串DEFAULT_KEY + “:” + canonicalName。这也就解释了第 4 节的疑问 为什么在对应的作用域内,保正只生产出对应的唯一实例
注释 4:这个是一个重点方法了,表明要清空存储的数据,还会调用到ViewModel的 clear
方法,也就是最终会调用带 ViewModel 的onCleared()
方法
那么这个ViewModelStore的 clear
方法,什么时候会调用呢?
7.2.ComponentActivity的构造方法
//ComponentActivity.java
public ComponentActivity() {
Lifecycle lifecycle = getLifecycle();
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
//1
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
}
在ComponentActivity的构造方法中,我们看到,在 Activity 的生命周期为 onDestory的时候,并且当前不是,配置更改(比如横竖屏幕切换)就会调用ViewModelStore 的 clear 方法,进一步回调用 ViewModel 的onCleared
方法。
这就回答了第四节提出的问题onCleared方法在什么调用
最后看一下 ViewModel 的源码,以及其子类AndroidViewModel
8.ViewModel 的源码
ViewModel类其实更像是更规范化的抽象接口
public abstract class ViewModel {
private volatile boolean mCleared = false;
@SuppressWarnings("WeakerAccess")
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 的子类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;
}
}
提供了一个规范,提供了一个 Application 的 Context
到现在整个源码过程就看了,包括前面,我们提到的那几个关键类的源码。
到目前为止,我们第 4 节抛出的问题,已经解决了,两个了,还有一个ViewModel为什么不会随着Activity的屏幕旋转而销毁;
9.分析为啥ViewModel不会随着Activity的屏幕旋转而销毁
首先知道的是 ViewModel 不被销毁,是在一个 ViewModelStore 的 Map 中存着呢,所以要保证ViewModelStore不被销毁。
首先得具备一个前置的知识
在 Activity 中提供了 onRetainNonConfigurationInstance
方法,用于处理配置发生改变时数据的保存。随后在重新创建的 Activity 中调用 getLastNonConfigurationInstance
获取上次保存的数据。
10.1.onRetainNonConfigurationInstance方法
//ComponentActivity.java
/**
* Retain all appropriate non-config state. You can NOT
* override this yourself! Use a {@link androidx.lifecycle.ViewModel} if you want to
* retain your own non config state.
*/
@Override
@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
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
//1
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
注意看下方法上的注释
- 不需要也不能重写此方法,因为用 final 修饰
- 配置发生改变时数据的保存,用ViewModel就行
- 注释 1:把ViewModel存储在 NonConfigurationInstances 对象中
现在再看下ComponentActivity 的 getViewModelStore方法
//ComponentActivity.java
@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) {
//1
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
注释 1:获取了NonConfigurationInstances一个对象,不为空从其身上拿一个ViewModelStore,这个就是之前保存的ViewModelStore
当 Activity 重建时还会走到getViewModelStore
方法,这时候就是在NonConfigurationInstances拿一个缓存的ViewModelStore。
ViewModel的高级应用
随着Android应用变得越来越复杂,ViewModel的使用也不再局限于简单的UI数据存储。它的高级用法包括与LiveData结合使用以及实现Repository模式,这些都是提升应用架构清晰度和维护性的关键技术。
结合LiveData使用
LiveData是一个可观察的数据持有者类,它遵循观察者模式,允许数据变化可以通知到观察者组件。与ViewModel结合使用时,LiveData提供了一种响应式方式来更新UI,这样可以确保UI组件总是展示最新的数据。
class UserViewModel : ViewModel() {
private val userRepository = UserRepository()
val userData: LiveData<User> = userRepository.getUserData()
fun updateUser(userData: User) {
userRepository.updateUser(userData)
}
}
在Activity或Fragment中,您可以观察这些LiveData对象:
class UserActivity : AppCompatActivity() {
private lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
viewModel.userData.observe(this, Observer { user ->
// 更新UI
})
}
}
实现Repository模式
Repository模式是一种将数据源管理逻辑从UI逻辑中分离的设计模式。ViewModel通过使用Repository类,可以从不同的数据源(如网络API、本地数据库)抽象地获取数据,同时保持UI组件的简洁和聚焦。
class UserRepository {
fun getUserData(): LiveData<User> {
// 实现从数据库或网络获取用户数据
}
fun updateUser(user: User) {
// 实现更新用户数据到数据库或网络
}
}
通过这种方式,ViewModel不仅作为数据的直接管理者,同时也协调不同后端服务或数据库的数据流,显著增强了应用的灵活性和可扩展性。
总结
在本文中,我们详细探讨了ViewModel的基本使用、工作原理及其在复杂应用中的高级应用。通过这些分析和代码示例,我们看到ViewModel不仅能够有效管理UI相关的数据,保持数据在配置变化时的持久性,而且通过与LiveData和Repository模式的结合使用,显著提高了应用的响应性和数据处理能力。
关键优点回顾
- 生命周期意识:ViewModel的设计允许它自然地处理用户界面所需的数据与Activity或Fragment生命周期的关联问题,从而简化了开发者的工作。
- 数据管理与隔离:通过将数据处理逻辑从UI逻辑中解耦,ViewModel帮助开发者实现更清晰和可维护的代码结构。
- 增强的应用稳定性和用户体验:保持数据在配置更改时的一致性,避免了应用在如屏幕旋转等情况下的数据丢失,提升了用户体验。
鼓励所有Android开发者实践ViewModel的使用,深入探索其与其他Jetpack组件的集成,以充分利用这些工具提供的强大功能,为用户创造出更流畅、更可靠的应用体验。
如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )