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

相关推荐
_祝你今天愉快2 小时前
UI核心 - UI绘制流程及原理
android
mmsx2 小时前
Android 一个设备多个 App 抢:跨进程共享服务的架构设计
android·kotlin·app
mmsx2 小时前
Android GIS系列 栅格怎么存、格网怎么归算:两个容易混淆的 GIS 链路
android·性能优化·app
陈柒吖2 小时前
安卓代码加固(1):加密DEX
android·前端
mmsx2 小时前
Android GIS系列 串口、蓝牙、USB、网络:一套统一抽象怎么扛住四种通道
android·app·产品
二流小码农2 小时前
鸿蒙开发:ArrayList,可不会让UI更新哦
android·ios·harmonyos
hai_android2 小时前
深入理解 Java 的四种引用:强引用、软引用、弱引用、虚引用
android·kotlin·android jetpack
恋猫de小郭2 小时前
Flutter Golden Tests:给 AI Agent 的 UI 测试系统
android·前端·flutter
知昂七昂2 小时前
AOSP编译:敲下 `lunch xxx-userdebug`,你知道系统偷偷干了什么吗?
android
千里马学框架5 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation