Jetpack ViewModel 使用和原理解析

一、引言

在 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 和一个 FactoryViewModelStore 用于存储 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 实例,如果该键已经存在,则会调用旧的 ViewModelonCleared() 方法。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 中一个非常实用的组件,它通过分离数据管理和界面逻辑,有效地解决了配置变更时数据丢失的问题。通过 ViewModelProviderViewModelStoreViewModelProvider.Factory 等核心类的协同工作,实现了 ViewModel 的创建、缓存和生命周期管理。在实际开发中,结合 LiveData 等其他 Jetpack 组件,可以构建出更加健壮和高效的 Android 应用。希望通过本文的介绍,你对 Jetpack ViewModel 的使用和源码原理有了更深入的理解。

相关推荐
姜行运5 分钟前
数据结构【二叉搜索树(BST)】
android·数据结构·c++·c#
JhonKI8 小时前
【MySQL】存储引擎 - CSV详解
android·数据库·mysql
开开心心_Every8 小时前
手机隐私数据彻底删除工具:回收或弃用手机前防数据恢复
android·windows·python·搜索引擎·智能手机·pdf·音视频
大G哥9 小时前
Kotlin Lambda语法错误修复
android·java·开发语言·kotlin
鸿蒙布道师12 小时前
鸿蒙NEXT开发动画案例2
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
androidwork12 小时前
Kotlin Android工程Mock数据方法总结
android·开发语言·kotlin
xiangxiongfly91514 小时前
Android setContentView()源码分析
android·setcontentview
人间有清欢15 小时前
Android开发补充内容
android·okhttp·rxjava·retrofit·hilt·jetpack compose
人间有清欢16 小时前
Android开发报错解决
android
每次的天空18 小时前
Android学习总结之kotlin协程面试篇
android·学习·kotlin