一、引言
在 Android 开发中,界面与数据的管理一直是重要且复杂的问题。特别是在处理配置变更(如屏幕旋转)时,Activity 或 Fragment 的重建会导致数据丢失。Jetpack 组件中的 ViewModel 为开发者提供了一种优雅的解决方案,它能够在配置变更时保留数据,并且帮助开发者分离视图逻辑和数据逻辑,提高代码的可维护性和可测试性。本文将详细介绍 ViewModel 的使用方法,并深入剖析其源码原理。
二、ViewModel 的基本使用
2.1 添加依赖
要使用 ViewModel,首先需要在项目的 build.gradle
文件中添加相应的依赖:
groovy
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
2.2 创建 ViewModel 类
创建一个继承自 ViewModel
的类,用于存储和管理界面相关的数据。以下是一个简单的计数器 ViewModel 示例:
kotlin
import androidx.lifecycle.ViewModel
class CounterViewModel : ViewModel() {
private var count = 0
fun getCount(): Int {
return count
}
fun incrementCount() {
count++
}
}
在这个 CounterViewModel
类中,我们定义了一个私有变量 count
,并提供了获取和增加计数的方法。
2.3 在 Activity 或 Fragment 中使用 ViewModel
在 Activity 或 Fragment 中获取 ViewModel 实例,并使用其提供的方法来管理数据。以下是在 Activity 中使用的示例:
kotlin
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import com.example.viewmodeldemo.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: CounterViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 获取 ViewModel 实例
viewModel = ViewModelProvider(this).get(CounterViewModel::class.java)
// 更新界面显示
updateUI()
// 设置按钮点击事件
binding.incrementButton.setOnClickListener {
viewModel.incrementCount()
updateUI()
}
}
private fun updateUI() {
binding.countTextView.text = viewModel.getCount().toString()
}
}
在 MainActivity
中,我们通过 ViewModelProvider
获取 CounterViewModel
的实例,并在按钮点击事件中调用 ViewModel
的方法来更新数据和界面。
2.4 结合 LiveData 使用 ViewModel
为了实现数据的响应式更新,通常会结合 LiveData 来使用 ViewModel。LiveData 是一种可观察的数据持有者类,它可以感知 Activity、Fragment 等组件的生命周期,从而避免内存泄漏。
修改 ViewModel 类
kotlin
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class CounterViewModel : ViewModel() {
private val _count = MutableLiveData<Int>()
val count: LiveData<Int>
get() = _count
init {
_count.value = 0
}
fun incrementCount() {
_count.value = (_count.value ?: 0) + 1
}
}
在这个修改后的 CounterViewModel
类中,我们使用 MutableLiveData
来存储 count
的值,并提供一个不可变的 LiveData
供外部观察。
在 Activity 中观察 LiveData
kotlin
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import com.example.viewmodeldemo.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: CounterViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel = ViewModelProvider(this).get(CounterViewModel::class.java)
// 观察 LiveData
viewModel.count.observe(this) { newCount ->
binding.countTextView.text = newCount.toString()
}
binding.incrementButton.setOnClickListener {
viewModel.incrementCount()
}
}
}
在 MainActivity
中,通过 observe()
方法观察 LiveData
的变化,当 count
的值发生改变时,会自动更新界面。
三、ViewModel 源码原理解析
3.1 ViewModelProvider
ViewModelProvider
是获取 ViewModel
实例的核心类。它负责管理 ViewModel
的创建和缓存。以下是 ViewModelProvider
的主要构造方法和 get()
方法的源码分析:
kotlin
public class ViewModelProvider {
private final Factory mFactory;
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;
}
@NonNull
@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");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
// 如果缓存中已经存在该 ViewModel 实例,则直接返回
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
// 如果缓存中不存在该 ViewModel 实例,则创建一个新的实例
if (viewModel != null) {
// TODO: log a warning.
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
}
}
在 ViewModelProvider
的构造方法中,我们需要传入一个 ViewModelStore
和一个 Factory
。ViewModelStore
用于存储 ViewModel
实例,Factory
用于创建 ViewModel
实例。
在 get()
方法中,首先会根据 ViewModel
的类名生成一个唯一的键,然后从 ViewModelStore
中查找该键对应的 ViewModel
实例。如果存在,则直接返回;如果不存在,则使用 Factory
创建一个新的实例,并将其存储到 ViewModelStore
中。
3.2 ViewModelStore
ViewModelStore
是一个存储 ViewModel
实例的容器,它以键值对的形式存储 ViewModel
实例。以下是 ViewModelStore
的源码分析:
kotlin
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());
}
/**
* 当 Activity 或 Fragment 销毁时,调用该方法清除所有的 ViewModel 实例
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
}
ViewModelStore
内部使用一个 HashMap
来存储 ViewModel
实例。put()
方法用于存储 ViewModel
实例,如果该键已经存在,则会调用旧的 ViewModel
的 onCleared()
方法。get()
方法用于获取指定键对应的 ViewModel
实例。clear()
方法用于清除所有的 ViewModel
实例,并调用它们的 onCleared()
方法。
3.3 ViewModelProvider.Factory
ViewModelProvider.Factory
是一个工厂接口,用于创建 ViewModel
实例。默认情况下,ViewModelProvider
使用 NewInstanceFactory
来创建 ViewModel
实例。以下是 NewInstanceFactory
的源码分析:
kotlin
public class NewInstanceFactory implements Factory {
private static NewInstanceFactory sInstance;
/**
* 返回单例的 NewInstanceFactory 实例
*/
@NonNull
static NewInstanceFactory getInstance() {
if (sInstance == null) {
sInstance = new NewInstanceFactory();
}
return sInstance;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
// 通过反射创建 ViewModel 实例
try {
return modelClass.getConstructor().newInstance();
} 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);
}
}
}
NewInstanceFactory
实现了 Factory
接口,通过反射调用 ViewModel
的无参构造方法来创建实例。
3.4 配置变更时 ViewModel 的保留机制
当 Activity 或 Fragment 发生配置变更(如屏幕旋转)时,ViewModel
不会被销毁,而是会继续保留其数据。这是因为 ViewModelStore
会在 Activity 或 Fragment 销毁时被保留下来,当 Activity 或 Fragment 重新创建时,会使用之前保留的 ViewModelStore
来恢复 ViewModel
实例。
具体来说,在 ComponentActivity
中,有一个 NonConfigurationInstances
类用于保存 ViewModelStore
:
kotlin
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
@Override
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// 如果没有保存的 ViewModelStore,则从之前的 NonConfigurationInstances 中获取
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// 恢复之前保存的 ViewModelStore
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
在 onRetainNonConfigurationInstance()
方法中,会将 ViewModelStore
保存到 NonConfigurationInstances
中,当 Activity 重新创建时,在 onCreate()
方法中会从 NonConfigurationInstances
中恢复 ViewModelStore
。
四、总结
ViewModel 是 Android Jetpack 中一个非常实用的组件,它通过分离数据管理和界面逻辑,有效地解决了配置变更时数据丢失的问题。通过 ViewModelProvider
、ViewModelStore
和 ViewModelProvider.Factory
等核心类的协同工作,实现了 ViewModel
的创建、缓存和生命周期管理。在实际开发中,结合 LiveData 等其他 Jetpack 组件,可以构建出更加健壮和高效的 Android 应用。希望通过本文的介绍,你对 Jetpack ViewModel 的使用和源码原理有了更深入的理解。