ViewModel详细使用与解析

276 阅读12分钟

目录

前言

使用viewModel有以下优点:

使用ViewModel需要了解MVVM模式

一、ViewModel怎么使用

二、ViewModel解析

ViewModel怎么感知生命周期

ViewModel不会因Configuration Change(如屏幕旋转)而销毁

多个 Fragment 可以共享同一 ViewModel

总结



前言

ViewModel作为Jetpack架构组件的核心成员,在Android应用开发中承担着UI数据管理与生命周期感知的重要职责。理解ViewModel的设计原理和正确使用方式,对于构建健壮、可维护的Android应用至关重要。

使用viewModel有以下优点:

  1. 将View中的UI界面数据和业务管理分离开,降低耦合性
  2. 可感知生命周期的组件。
  3. 不会因配置(如屏幕旋转)Configuration Change改变而销毁。
  4. 可以配合 LiveData 使用。
  5. 多个 Fragment 可以共享同一 ViewModel。
  6. 等等······

使用ViewModel需要了解MVVM模式

MVVM很好理解的,感兴趣的可以阅读这篇文章,我这里就借用大佬的内容:

MVVM 将代码划分为三个部分:

  • View: Activity 和 Layout XML 文件,与 MVP 中 View 的概念相同;
  • Model: 负责管理业务数据逻辑,如网络请求、数据库(Repository)处理,与 MVP 中 Model 的概念相同;
  • ViewModel: 存储视图状态,负责处理表现逻辑,并将数据设置给可观察数据容器。

在实现细节上,View 和 Presenter 从双向依赖变成 View 可以向 ViewModel 发指令,但 ViewModel 不会直接向 View 回调,而是让 View 通过观察者的模式去监听数据的变化,有效规避了 MVP 双向依赖的缺点。但 MVVM 本身也存在一些缺点:

  • 多数据流: View 与 ViewModel 的交互分散,缺少唯一修改源,不易于追踪;
  • LiveData 膨胀: 复杂的页面需要定义多个 MutableLiveData,并且都需要暴露为不可变的 LiveData。

​编辑

一、ViewModel怎么使用

1)、创建一个基础ViewModel类

// 基础ViewModel类
class BaseViewModel : ViewModel() {
    // 使用LiveData管理UI状态
    private val _uiState = MutableLiveData<UiState>()
    val uiState: LiveData<UiState> = _uiState

    // 使用协程管理异步操作
    private val viewModelScope = CoroutineScope(Dispatchers.Main + Job())

    // 错误处理
    private val _error = MutableLiveData<String>()
    val error: LiveData<String> = _error

    // 加载状态
    private val _isLoading = MutableLiveData<Boolean>()
    val isLoading: LiveData<Boolean> = _isLoading

    // 清理资源
    override fun onCleared() {
        super.onCleared()
        viewModelScope.cancel()
    }
}
// 用户相关ViewModel
class UserViewModel : BaseViewModel() {
    private val userRepository: UserRepository = UserRepository()

    // 可以放入用户数据
    private val _userData = MutableLiveData<User>()
    val userData: LiveData<User> = _userData

    // 通过异步处理加载用户数据
    fun loadUserData(userId: String) {
        viewModelScope.launch {
            try {
                _isLoading.value = true
                val user = userRepository.getUser(userId)
                _userData.value = user
            } catch (e: Exception) {
                _error.value = e.message
            } finally {
                _isLoading.value = false
            }
        }
    }
}

2)、在Actvivity/Fragment中使用ViewModel

class UserFragment : Fragment() {
    private lateinit var userViewModel: UserViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 在onCreate中通过ViewModelProvider方法初始化ViewModel
        //如果有参数传入可以重写Factory接口
        userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 观察数据变化
        observeViewModel()
    }

    private fun observeViewModel() {
        // 通过observe观察者模式观察用户数据
        userViewModel.userData.observe(viewLifecycleOwner) { user ->
            // 更新UI
            updateUserUI(user)
        }
    }
}

3)、使用Factory接口实现带有参数的ViewModel

若想传入数据,可以重写Factory接口,ViewModel默认使用无参数创建。

// 用户相关ViewModel
class UserViewModel(private val userRepository: UserRepository) : BaseViewModel() {

    // 可以放入用户数据
    private val _userData = MutableLiveData<User>()
    val userData: LiveData<User> = _userData

    // 通过异步处理加载用户数据
    fun loadUserData(userId: String) {
        viewModelScope.launch {
            try {
                _isLoading.value = true
                val user = userRepository.getUser(userId)
                _userData.value = user
            } catch (e: Exception) {
                _error.value = e.message
            } finally {
                _isLoading.value = false
            }
        }
    }
    
    // 自定义的 ViewModel 工厂类,用于创建需要依赖项的 UserViewModel
    class Factory(
        // 通过构造函数注入 UserRepository 依赖
        private val repository: UserRepository
    ) : ViewModelProvider.Factory { // 实现官方 Factory 接口

        /**
         * 核心创建方法,由 ViewModelProvider 调用
         * @param modelClass 请求的 ViewModel 类型(由系统自动推断)
         * @return 创建好的 ViewModel 实例
         * @throws IllegalArgumentException 当请求的 ViewModel 类型不匹配时抛出
         */
        @Suppress("UNCHECKED_CAST") // 抑制类型转换警告
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            // 类型安全检查:确保请求的是 UserViewModel 或其子类
            if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
                // 通过构造函数创建 ViewModel 并注入依赖
                return UserViewModel(repository) as T // 显式类型转换
            }
            // 防御性编程:拦截非法 ViewModel 类型请求
            throw IllegalArgumentException("未知的 ViewModel 类型: ${modelClass.name}")
        }
    }
}

在Activity/Fragment中

class UserFragment : Fragment() {
    private lateinit var userViewModel: UserViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 1. 正确获取 UserRepository 实例
        val userRepository = UserRepository()
        // 2. 创建工厂实例,传入依赖
        val factory = UserViewModel.Factory(userRepository)
        // 3. 使用 ViewModelProvider 获取 UserViewModel
        userViewModel = ViewModelProvider(
            this, // 也可用 requireActivity() 作为 ViewModelStoreOwner
            factory
        ).get(UserViewModel::class.java)
    }

    // 后续可通过 userViewModel 操作数据
}

4)、其他方式创建ViewModel,实际都是通过ViewModelProvider创建的

通过KTX扩展库方式创建ViewModel,需使用到下面ktx库中的方法:

 implementation "androidx.activity:activity-ktx:1.2.2"
 implementation "androidx.fragment:fragment-ktx:1.3.3"

我们通过这2个扩展库,使用viewModels就可以非常快速的创建出我们所需要的ViewModel

//Activity中
class MainActivity:AppCompatActivity() {
 private val viewModel:MainViewModel by viewModels()
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel.mUser.observe(this) {
            Log.d("mUser","$it")
        }
    }
}
//Fragment中
class HomeFragment:Fragment() {
    val activityViewModel:MainViewModel by activityViewModels()
    //或者
    val viewModel:MainViewModel by viewModels()
}

原理大家还是阅读大佬的文章吧。

二、ViewModel解析

``

ViewModel怎么感知生命周期

正如官网介绍的一样,ViewModel也是有生命周期的,如图:

​编辑

从官网中我们也了解到ViewModel是通过Lifecycle来感知生命周期的

ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 Lifecycle。ViewModel 将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消。

ViewModel 存储于 ViewModelStore,其存活周期与 ViewModelStoreOwner(Activity/Fragment)保持一致。当 owner 调用 onDestroy 且非配置变更销毁时,系统会清理 ViewModelStore 并调用 ViewModel.onCleared()。

先看ViewModel源码:

public abstract class ViewModel {

    // 抑制Lint的"WeakerAccess"警告,说明此protected访问级别是设计需要
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
        // 默认空实现,子类按需重写
    }

    // 声明此方法必须在主线程调用(Android UI线程)
    @MainThread
    // final修饰符确保子类不能重写此方法,维护框架内部逻辑的稳定性
    final void clear() {
        ....
        
        // 调用模板方法,执行开发者自定义的清理逻辑
        onCleared();
    }
    .... 
}

在看看ViewModelProvider的源码:

public open class ViewModelProvider(
    // 主构造函数参数:ViewModel存储仓库(负责缓存ViewModel实例)
    private val store: ViewModelStore,
    // ViewModel工厂接口(负责具体实例化ViewModel对象)
    private val factory: Factory
) {

    // 次构造函数1:通过ViewModelStoreOwner简化初始化
    public constructor(
        owner: ViewModelStoreOwner // 生命周期所有者(如Activity/Fragment)
    ) : this(
        owner.viewModelStore, // 从owner获取关联的ViewModelStore
        defaultFactory(owner) // 根据owner类型选择默认工厂(代码中未展示实现细节)
    )

    // 次构造函数2:允许自定义Factory的初始化方式
    public constructor(
        owner: ViewModelStoreOwner, 
        factory: Factory // 自定义工厂实例
    ) : this(
        owner.viewModelStore, // 从owner获取ViewModelStore
        factory // 使用传入的自定义工厂
    )
    ......
}

ViewModelProvider有两个参数,一个是ViewModelStore,另一个是Factory。ViewModelStore:正如其名,就是用来存储ViewModel对象的,通过内部维护一个HashMap来实现对ViewMoel对象的存储与管理工作。而Factory则是工厂接口的,用来实例化ViewModel。

在实例化ViewModel时:

val userViewModel = ViewModelProvider(
        this, // 也可用 requireActivity() 作为 ViewModelStoreOwner
        factory
    ).get(UserViewModel::class.java)
}

this就是指当前所在的Activity,也对应着构造函数中的ViewModelStore

在ViewModelStoreOwner接口中可知:

/**
 * 拥有 [ViewModelStore] 的作用域接口(生命周期核心接口).
 *
 * 实现此接口的类有以下生命周期职责:
 * 1. 在配置变更(如屏幕旋转)期间保留持有的 ViewModelStore 实例
 * 2. 当该作用域即将被永久销毁时,调用 [ViewModelStore.clear] 清理资源
 *
 * @see ViewTreeViewModelStoreOwner  // 视图树级别的 ViewModel 存储扩展
 */
public interface ViewModelStoreOwner {

    /**
     * 获取关联的 ViewModel 存储实例(生命周期关键方法)
     * 
     * 生命周期特性:
     * - 返回值与实现类的生命周期绑定(如 Activity/Fragment)
     * - 当所有者被永久销毁时,内部会触发 ViewModelStore 的清理操作
     */
    public val viewModelStore: ViewModelStore // 必须通过实现类提供具体的存储实例
}

而在Activity/Fragment 中实现了ViewModelStoreOwner 接口,并实现了getViewModelStore()方法来得到 ViewModelStore

ComponnetActivity.kt

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner
	...... {

	public ViewModelStore getViewModelStore() {
            //检查一下当前 mViewModelStore 是否为空
	    if (mViewModelStore == null) {
		//检索一下是否有先前保存的非配置实例数据
	        NonConfigurationInstances nc =
	                (NonConfigurationInstances) getLastNonConfigurationInstance();
	        if (nc != null) {
                    //从先前保存的非配置实例数据中取出 ViewModelStore
	            mViewModelStore = nc.viewModelStore;
	        }
	        if (mViewModelStore == null) {
                    //先前没有保存过非配置实例数据,则新建一个 ViewModelStore
	            mViewModelStore = new ViewModelStore();
	        }
	    }

	    return mViewModelStore;
	}

}

简单概括:在获取 ViewModelStore 这一步中,会先去检索一下是否有先前保存的非配置实例数据,如果有保存,则取出其中的 ViewModelStore; 反之,如果之前没有保存过,则新创建一个 ViewModelStore

ViewModelProvider 的构造函数中通过调用 owner.viewModelStore 方法来获取到 viewModelStore,这一步其实就是取在 Activity | FragmentgetViewModelStore() 方法中创建的 ViewModelStore,也就是我们刚刚所介绍的。

viewModelStore 与 Lifecycle关系:

public ComponentActivity() {
    ......

    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {

            if (event == Lifecycle.Event.ON_DESTROY) {
                //检查一下是否发生配置变更
                if (!isChangingConfigurations()) {
                    //调用ViewModelStore中的clear()方法来清除其ViewModel
                    getViewModelStore().clear();
                }
            }
        }
    });

    ......

}

首先我们知道ViewModel是存储在ViewModelStore中的,当我们进入Activity时,会自动创建一个ViewModelStore,当我们在onCreate()方法中调用ViewModelProvider.get()方法时,就会将创建的ViewModel存到该ViewModelStore中。通过Lifecycle来得知Activity的生命周期,当Activity处于销毁时,检查一下是否发生配置变更,如果未发生配置变更,则调用clear()方法来清除ViewModelStore及其保存的ViewModel

ViewModel不会因Configuration Change(如屏幕旋转)而销毁

Activity 被杀死并重新创建或资源内存不足导致低优先级的 Activity 被杀死时,需要数据恢复,而数据恢复一般有三种方式。这里我直接放图对比,详细还请自行了解:

​编辑

在官方设计之初就倾向于在配置改变时进行数据的恢复。考虑到数据恢复时的效率,官方最终采用了 onRetainNonConfigurationInstance 的方式来恢复 ViewModel 。

在 Androidx 中的 Activity 的最新代码中,官方重写了 onRetainNonConfigurationInstance 方法,在该方法中保存了 ViewModelStore (ViweModelStore 中存储了 ViewModel ),进而也保存了 ViewModel。

/**
 * 保留非配置变更相关数据的核心方法(在Activity因配置变更销毁前调用)
 * 
 * 作用:在配置变更(如屏幕旋转)时保存ViewModelStore等数据,
 * 保证Activity重建后能恢复这些数据
 * 
 * @return 包含需要保留数据的NonConfigurationInstances对象
 */
public final Object onRetainNonConfigurationInstance() {
    // 向后兼容处理:获取子类可能保留的自定义数据(旧版API的兼容逻辑)
    Object custom = onRetainCustomNonConfigurationInstance();

    // 尝试获取当前ViewModelStore实例
    ViewModelStore viewModelStore = mViewModelStore;
    
    // 若当前没有ViewModelStore,尝试从上次保留的实例中获取
    if (viewModelStore == null) {
        // 通过getLastNonConfigurationInstance获取之前保存的非配置实例
        NonConfigurationInstances nc = 
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore; // 复用之前保留的ViewModelStore
        }
    }

    // 数据为空时直接返回null,避免创建无用对象
    if (viewModelStore == null && custom == null) {
        return null;
    }

    // 创建新的保留数据容器(当有需要保留的数据时)
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;          // 存入自定义兼容数据
    nci.viewModelStore = viewModelStore; // 存入ViewModel存储
    return nci;
}

/**
 * 非配置实例数据容器(静态内部类避免内存泄漏)
 * 
 * 设计要点:
 * 1. 静态类不持有外部引用,防止Activity实例泄漏
 * 2. 仅存储必要数据,生命周期与Activity重建过程关联
 */
static final class NonConfigurationInstances {
    Object custom;          // 兼容旧版API的自定义数据存储位
    ViewModelStore viewModelStore; // ViewModel存储核心容器
}

当新的 Activity 重新创建,并调用ViewModelProviders.of(this).get(xxxModel.class) 时,又会在 getViewModelStore() 方法中获取老 Activity 保存的 ViewModelStore。那么也就拿到了 ViewModel。

    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            //👇获取保存的NonConfigurationInstances,
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                //👇从该对象中获取ViewModelStore
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

最后会先通过 getLastNonConfigurationInstance() 方法来获取先前创建的非配置实例数据,如果先前没有创建过非配置实例数据,则新创建一个,并返回。

Activity.java

NonConfigurationInstances mLastNonConfigurationInstances;

public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

final void attach(NonConfigurationInstances lastNonConfigurationInstances ...) {
    ...
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    ...
}

static final class NonConfigurationInstances {
    Object activity;
    HashMap<String, Object> children;
    FragmentManagerNonConfig fragments;
    ArrayMap<String, LoaderManager> loaders;
    VoiceInteractor voiceInteractor;
}

而在Fragment中,ViewModel 是存储在 FragmentManagerViewModel 中的,而 FragmentManagerViewModel 是存储在宿主 Activity 中的 ViewModelStore 中,又因 Activity 中 ViewModelStore不会因配置改变而销毁,故 Fragment 中 ViewModel 也不会因配置改变而销毁。

​编辑

多个 Fragment 可以共享同一 ViewModel

假如我们想 Fragment D 获取 Fragment A 中的数据,那么我们只有在 Activity 中的 ViewModelStore 下添加 ViewModel。当在 Fragment 中使用 ViewModelProvider(requireActivity()) 获取时,所有 Fragment 都访问相同的 ViewModelStore,从而共享同一 ViewModel 实例。

// 1. 首先定义需要共享数据的 ViewModel 类
class SharedViewModel : ViewModel() {
    // 使用 LiveData 保持数据,便于观察变化
    private val _sharedData = MutableLiveData<String>()
    val sharedData: LiveData<String> get() = _sharedData

    // 更新共享数据的方法
    fun updateData(newData: String) {
        _sharedData.value = newData
    }
}

// 2. Fragment A(数据生产者)
class FragmentA : Fragment() {
    private lateinit var viewModel: SharedViewModel

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 通过宿主 Activity 获取共享的 ViewModel
        viewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)

        // 模拟数据更新(例如按钮点击事件)
        button.setOnClickListener {
            viewModel.updateData("Data from Fragment A")
        }
    }
}

// 3. Fragment D(数据消费者)
class FragmentD : Fragment() {
    private lateinit var viewModel: SharedViewModel

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 同样通过宿主 Activity 获取 ViewModel
        viewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)

        // 观察数据变化
        viewModel.sharedData.observe(viewLifecycleOwner) { data ->
            // 当数据变化时更新 UI
            textView.text = "Received: $data"
        }
    }
}

// 4. 宿主 Activity(容器)
class HostActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_host)
        
        // 添加 FragmentA 和 FragmentD 的逻辑...
    }
}


总结

ViewModel 是 Jetpack 架构组件的基石,通过它可以实现更清晰的架构分层、简化配置变更数据恢复,并促进 UI 与业务逻辑的解耦。结合 LiveData能够构建响应式、稳定且易于测试的 Android 应用。