一、引言
在 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 的使用和源码原理有了更深入的理解。