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

相关推荐
东莞梦幻科技37 分钟前
体育直播系统趣猜功能开发技术实现方案
android
姜行运1 小时前
数据结构入门【算法复杂度】
android·c语言·数据结构·算法
二流小码农1 小时前
鸿蒙开发:父组件如何调用子组件中的方法?
android·ios·harmonyos
张风捷特烈2 小时前
Flutter 知识集锦 | 获取函数调用栈
android·flutter·dart
故事与他6458 小时前
Thinkphp(TP)框架漏洞攻略
android·服务器·网络·中间件·tomcat
每次的天空10 小时前
项目总结:GetX + Kotlin 协程实现跨端音乐播放实时同步
android·开发语言·kotlin
m0_7482331712 小时前
SQL之delete、truncate和drop区别
android·数据库·sql
CYRUS_STUDIO13 小时前
OLLVM 增加 C&C++ 字符串加密功能
android·c++·安全
帅次15 小时前
Flutter 输入组件 Radio 详解
android·flutter·ios·kotlin·android studio
&有梦想的咸鱼&16 小时前
Android Compose 框架的状态与 ViewModel 的协同(collectAsState)深入剖析(二十一)
android