Android Jetpack(4) ViewModel源码阅读记录
发现问题
- 数据丢失问题:Activity/Fragment 因配置变更(如屏幕旋转)被销毁重建时,临时数据丢失需重新初始化。
- 生命周期耦合:异步操作(如网络请求)中直接操作 UI 可能导致内存泄漏或崩溃。
- 职责混杂:业务逻辑和 UI 代码耦合在 Activity/Fragment 中,违反单一职责原则。
- 状态冗余:多 Fragment 共享同一数据时需通过复杂回调或 EventBus 传递。
解决思路
核心需求
- 生命周期感知的数据持有:在配置变更(如旋转屏幕)时保留数据,避免重复加载。
- UI控制器解耦:将业务逻辑与数据存储从 Activity/Fragment 中分离,降低耦合度。
- 资源自动释放:在关联的 UI 控制器完全销毁时自动清理资源,避免内存泄漏。
- 跨组件通信:支持 Fragment 之间或组件间安全共享数据。
关键思路
- 工厂方法模式
Why?- 灵活实例化:通过
ViewModelProvider.Factory接口,允许自定义 ViewModel 的创建逻辑(如依赖注入)。 - 统一入口:
ViewModelProvider封装了 ViewModel 的获取逻辑,调用方无需关心实例的创建细节。
实现类: NewInstanceFactory:默认反射创建无参数 ViewModel 实例。AndroidViewModelFactory:支持带Application参数的ViewModel。SavedStateViewModelFactory:支持带SavedStateHandle参数的ViewModel。
- 灵活实例化:通过
- 代理模式
Why?- 生命周期代理:通过
ReportFragment代理 Activity 的生命周期事件,使得ViewModelStore 与 Activity/Fragment 的生命周期绑定,能够在onDestroy()时自动清除(非配置变更场景)。 - 逻辑解耦:Activity 无需直接处理 ViewModel 的生命周期,由系统框架自动管理。
- 生命周期代理:通过
- 状态管理
Why?- 资源释放:在
ViewModel#onCleared()中释放资源(如取消网络请求),避免内存泄漏。 - 集中管理:
ViewModelStore作为容器,通过 HashMap 缓存 ViewModel 实例,确保同一作用域内实例唯一。 - 事件拦截:通过
SavedStateHandle保存/恢复临时状态(如屏幕旋转时的输入框内容)。 ViewModelStore:内部维护HashMap<String, ViewModel>。ViewModelStoreOwner:接口(如 Activity/Fragment),提供ViewModelStore。
- 资源释放:在
解决方案(核心源码)
核心类与接口
-
ViewModel(Impl)
作为业务逻辑和界面状态的容器,其生命周期与ViewModelStoreOwner(如 Activity、Fragment)绑定,确保数据在配置变更(如屏幕旋转)时不被销毁。支持ViewModel持有实现Closeable接口的资源,ViewModelStoreOwner会帮助自动释放Closeable资源。开发者通过继承该类实现自定义逻辑,并通过onCleared()释放资源,但是onCleared()无法释放Closeable资源,除非手动关闭。 -
ViewModelProvider(Impl)
用于获取ViewModel实例的工具类,确保同一作用域内多次请求返回同一实例,避免重复创建。例如:val viewModel = ViewModelProvider(activity)[MyViewModel::class.java] val viewModel = ViewModelProvider(activity).get(MyViewModel::class.java)它通过
ViewModelStore管理ViewModel实例缓存。每次这样创建
ViewModel时,都会创建一个ViewModelProvider,由于创建时会把ViewModelStoreOwner传入,所以实际上每个ViewModelProvider都是操纵同一个ViewModelStore。 -
ViewModelStoreOwner 接口
定义ViewModelStore的持有者,如ComponentActivity、Fragment或NavBackStackEntry(用于导航组件)。ViewModel的作用域由其ViewModelStoreOwner的生命周期决定。 -
ViewModelStore
存储ViewModel实例的容器,当ViewModelStoreOwner因为销毁时分发ON_DESTROY事件,如果是非配置变更导致的销毁,那么就会触发ViewModelStore的清理逻辑,释放所有ViewModel资源,包括其中的Closeable资源。 -
SavedStateHandle
用于在进程重建(如系统杀死应用后恢复)时临时保存数据。通过键值对存储数据,支持通过LiveData、StateFlow订阅数据。SavedStateHandle的数据仅在进程重建时有效,应用彻底关闭后需依赖持久化存储(如数据库) -
SavedStateViewModelFactory
SavedStateViewModelFactory是用于创建支持状态临时保存的ViewModel的工厂类,其核心目标是让ViewModel能够通过SavedStateHandle访问和保存进程重建后的状态数据。
源码流程图(关键流程)

时序图
ViewModelStore、Bundle的保存与恢复

ViewModel组件库运作

注意
工厂模式在 ViewModel 中的应用?为什么不能直接实例化ViewModel?
有哪些类型的Factory?
问题一、二:
ViewModel 由系统管理
ViewModel 的设计初衷是与 Activity/Fragment 的生命周期解耦,当 Activity 因配置变更被销毁重建时,ViewModel 会被保留、重用。使用工厂创建ViewModel时,保证所有 ViewModel 的生成都有一致的存放点ViewModelStore,实现可以复用的效果,同时方便后续保留ViewModelStore,从而恢复ViewModel。
如果你直接 new MyViewModel(),每次 Activity 重建时都会得到一个新的实例,失去了 ViewModel 的价值。
保存和恢复状态
通过工厂生成的 ViewModel 可以支持 SavedStateHandle 注入,实现进程被系统回收后恢复数据,减少手动注入产生的模板代码。
如果你自己 new 出来的 ViewModel,需要自己创建、管理SavedStateHandle。
问题三:
AndroidX 中常见的几种 Factory 类型:
NewInstanceFactory:默认工厂,只能创建无参构造的 ViewModel
AndroidViewModelFactory:用于创建继承自 AndroidViewModel(带 Application 参数)的 ViewModel
SavedStateViewModelFactory:支持注入 SavedStateHandle,用于进程恢复场景。
Hilt生成的Factory、自定义Factory。
ViewModelStore 的内部数据结构是什么?
public open class ViewModelStore {
//核心就是 map 键值对
private val map = mutableMapOf<String, ViewModel>()
//...
}
ViewModel的生命周期与Activity有何不同?
为什么ViewModel可以保存屏幕旋转时的数据/解释ViewModelStore的存储原理?
严格来说ViewModel的生命周期与Activity相比,在不同情况生存时长是不一样的。
用户主动退出、系统销毁程序时,ViewModel的生命周期和Activity一致。
在配置变更时,ViewModel的生命周期比Activity长。因为Activity会经历先销毁后重建,而ViewModel会被保存在NonConfigurationInstances中,直到Activity重建时,又会获取对应的ViewModel。
ViewModel可以保存配置变更时的数据,是因为ViewModel被保存在ViewModelStore中,ViewModelStore在销毁前会被保存到NonConfigurationInstances中,NonConfigurationInstances又被保存到ActivityClientRecord中,后续重建时则会从ActivityClientRecord一步步提取出来,给重建的Activity恢复数据。
Fragment创建ViewModel的流程?
通过FragmentManager 进行add/replace Fragment后。
fragmentManager.beginTransaction()
.add(R.id.container, MyFragment.class, args)
.commit();
会执行attach方法。
//attach->performAttach->attachController->onAttach
void attachController(@NonNull FragmentHostCallback<?> host,
@NonNull FragmentContainer container, @Nullable final Fragment parent) {
// Get the FragmentManagerViewModel
//嵌套 Fragment:子 Fragment 复用父 FragmentManager 的 non-config
if (parent != null) {
mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
} else if (host instanceof ViewModelStoreOwner) {
// 根级 Fragment:从宿主 Activity/FragmentActivity(实现了 ViewModelStoreOwner)拿到它的 ViewModelStore
ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
// 在宿主的 ViewModelStore 中创建/获取一个 FragmentManagerViewModel
mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
} else {
//极少见的情况:宿主不支持 ViewModelStore,则创建一个临时的 FragmentManagerViewModel
//false表示不会跨配置存活
mNonConfig = new FragmentManagerViewModel(false);
}
}
@NonNull
static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
//把 FragmentManagerViewModel 放进了宿主的 ViewModelStore:
//去该 store 中查找已有的 FragmentManagerViewModel 实例,若不存在就新建;
//因为宿主的 ViewModelStore 会在配置变化时保留,所以这个 FragmentManagerViewModel 也就得以跨配置存活。
ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,FACTORY);
return viewModelProvider.get(FragmentManagerViewModel.class);
}
@NonNull
ViewModelStore FragmentManagerViewModel#getViewModelStore(@NonNull Fragment f) {
//当你在某个 Fragment 中调用 new ViewModelProvider(this)(this 实现了 ViewModelStoreOwner)
//底层会先走到 Fragment.getViewModelStore()
//再委托给它对应的 FragmentManagerViewModel.getViewModelStore(thisFragment),也就是这个方法
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
if (viewModelStore == null) {
//每个 Fragment 第一次请求时「新创建」一个私有的 ViewModelStore,
//并缓存到 mViewModelStores 里。
//后续同一个 Fragment 再取,就能拿到同一个容器。
viewModelStore = new ViewModelStore();
mViewModelStores.put(f.mWho, viewModelStore);
//这个子 Fragment 自己的 ViewModelStore 并不是直接从宿主拿过来的,
//而是由宿主的 FragmentManagerViewModel “工厂”按需生成并管理
}
return viewModelStore;
}
FragmentManagerViewModel由FragmentManager管理,被保存在Host(Activity)的ViewModelStore中,FragmentManagerViewModel内有K:Fragment to V:ViewModelStore的map,也就是每一个Fragment都有对应的ViewModelStore。
ViewModel如何避免内存泄漏?
ViewModel 中启动协程需要注意什么?ViewModel 与 LiveData/Flow 的最佳实践?
为什么 ViewModel 不直接持有 Context?AndroidViewModel与普通ViewModel的区别?
问题一:
//ComponentActivity构造函数
public ComponentActivity() {
//订阅ComponentActivity的生命周期事件,如果ON_DESTROY
//同时非配置变化,那么释放ViewModelStore占用的内存
//
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
mReportFullyDrawnExecutor.activityDestroyed();
}
}
});
}
//ViewModelStore#clear
public fun clear() {
for (vm in map.values) {
vm.clear()
}
map.clear()
}
//ViewModel#clear
@MainThread
internal actual fun clear() {
impl?.clear()
onCleared()
}
//ViewModelImpl#clear
@MainThread
fun clear() {
if (isCleared) return
isCleared = true
//lock是对象实例锁
synchronized(lock) {
//关闭用Key注册的资源
for (closeable in keyToCloseables.values) {
closeWithRuntimeException(closeable)
}
//关闭没有用Key注册的资源
for (closeable in closeables) {
closeWithRuntimeException(closeable)
}
//关闭和清除不同
//仅仅清除没有键的资源的引用,以防止意外重新创建资源。
//例如,懒加载的属性,如果一次都没使用过,在close时会意外创建
//所以需要clear,让其被正常GC
closeables.clear()
//不清除用Key注册的资源,是因为由于使用Key的原因,是不会重新创建资源的
//因为在put进keyToCloseables时,会自动关闭老的资源
}
}
//ViewModelImpl#addCloseable
fun addCloseable(key: String, closeable: AutoCloseable) {
//虽然在用户调用 onCleared() 后不应该做任何逻辑,
//但我们将确保如果它已经被调用
//立即关闭尝试添加的 closeable,以确保不会有泄漏。
if (isCleared) {
closeWithRuntimeException(closeable)
return
}
val oldCloseable = synchronized(lock) { keyToCloseables.put(key, closeable) }
//关闭老资源
closeWithRuntimeException(oldCloseable)
}
public val ViewModel.viewModelScope: CoroutineScope
get() = synchronized(VIEW_MODEL_SCOPE_LOCK) {
getCloseable(VIEW_MODEL_SCOPE_KEY)
?: createViewModelScope().also { scope -> addCloseable(VIEW_MODEL_SCOPE_KEY, scope) }
}
private val VIEW_MODEL_SCOPE_LOCK = SynchronizedObject()
由上述代码得知,viewModelScope也是Closeable资源,通过VIEW_MODEL_SCOPE_KEY注册,会在 ViewModel 清理时被close,从而自动取消作用域下的所有协程。
所以在ViewModel中启动协程,尽可能使用viewModelScope方便管理。否则,必须在onCleared方法中手动cancel协程。
在ViewModel 中使用 LiveData/Flow 的最佳实践,尽可能以只读的形式向外发布数据。保证数据的单向流动。比如:
class MyViewModel : ViewModel() {
private val _userData = MutableLiveData<User>()
val userData: LiveData<User> = _userData
fun fetchUser() {
viewModelScope.launch {
_userData.value = repository.getUser()
}
}
}
想要修改/更新_userData只能通过ViewModel的方法,而不是在UI层直接操作数据进行修改。
问题三:
ViewModel 的设计初衷就是为了把与界面(Activity/Fragment)强耦合的逻辑和状态剥离出去,让它能在配置变化(比如旋转屏幕)时依然存活。
Activity/Fragment 的 Context 生命周期与本身相同,和 ViewModel 的生命周期比则短。如果 ViewModel 保存了对 Activity 的引用,那么当 Activity 被销毁(例如用户离开该界面,或者配置变化触发的重建),ViewModel对Activity的引用会阻止其垃圾回收,导致内存泄漏。
在单元测试里,通常希望能够脱离 Android 系统环境直接测试 ViewModel。若其依赖了 Context,就失去了快速、本地化单元测试的优势。
如果确实需要拿到 Context,优先使用 ApplicationContext。因为ApplicationContext 的生命周期与整个应用进程相同,也就是说,它比任何 Activity/Fragment 的生命周期都要长,也就不会像持有 Activity 那样容易产生内存泄漏。注意配合AndroidViewModel使用,AndroidViewModel适用于需要使用Context的ViewModel。
class ResourceViewModel(app: Application) : AndroidViewModel(app) {
fun loadWelcomeMessage(): String {
// 从 res/values/strings.xml 里读取
return getApplication<Application>().getString(R.string.welcome)
}
}
ViewModel在什么时候会被销毁?什么时候不被销毁?
首先,我们需要知道ViewModel2.0之前呢,其原理是在Activity上add一个HolderFragment,然后设置setRetainInstance(true)方法就能让这个Fragment在Activity重建时存活下来,所有通过 ViewModelProvider 创建的 ViewModel 对象都会放到这个 retained Fragment 的一个 Map<Class, ViewModel> 里,也就保证了ViewModel能够跨配置存活。
2.0之后,其实是用到了Activity的onRetainNonConfigurationInstance()和getLastNonConfigurationInstance()这两个方法,相当于在配置变化的时候会保存ViewModelStore的实例,然后从其中恢复ViewModel,所以也就保证了ViewModel的存活。
所以在非跨配置的情况下,而导致Activity/Fragment销毁时,ViewModel也是会被销毁的。
ViewModel的参数传递有哪些方式?
如何实现Fragment间共享ViewModel?
为什么导航时传参,可以注入到SavedStateHandle中?
ViewModel如何与Navigation组件配合使用?
在 MVVM 架构下,ViewModel 默认是由框架(ViewModelProvider/viewModels)统一管理的,而它的无参构造也正是为了让框架能够“按需”创建。如果 ViewModel 需要依赖外部参数(比如一个 ID、一组配置、或者仓库对象等),就必须告诉框架如何“注入”这些参数──也就是自定义一个 Factory。
自定义 ViewModelProvider.Factory
class MyViewModel(
private val userId: String
) : ViewModel() {
// …业务逻辑
}
class MyViewModelFactory(
private val userId: String
) : ViewModelProvider.Factory {
//override create
override fun <T : ViewModel> create(modelClass: Class<T>): T {
//是MyViewModel是modelClass的父类
if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return MyViewModel(userId) as T
}
throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
}
利用 Navigation 与 SavedStateHandle 传递参数
可以把「路由参数/Fragment 参数」直接通过 SavedStateHandle 传递给 ViewModel。官方也已经定义了SavedStateViewModelFractory,不需要自定义一个新的ViewModelFractory。
// ListFragment to ProfileFragment
findNavController().navigate(
R.id.action_list_to_profile,
bundleOf(
"userId" to "user-123",
"showDetails" to true
)
)
class MyViewModel(
private val state: SavedStateHandle
) : ViewModel() {
// 通过 key 读取 state 里的参数
val userId: String = state.get<String>("userId")!!
}
导航时传入Bundle,框架会帮你把 userId 写入 SavedStateHandle,你就可以直接取到。
使用 Hilt / Dagger2 自动注入
@HiltViewModel
class MyViewModel @Inject constructor(
private val state: SavedStateHandle,
private val repo: UserRepository
) : ViewModel() {
val userId: String = state["userId"]!!
// …
}
//带有 @AndroidEntryPoint 注解的 activity 或 fragment
//就可以使用 ViewModelProvider 或 by viewModels() KTX 扩展照常获取 ViewModel 实例
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
private val myViewModel: MyViewModel by viewModels()
...
}
和利用 Navigation 与 SavedStateHandle 传递参数实现原理差不多(见问题四),都是通过委托的方式把传入的bundle传递给SavedStateHandle ,再能在ViewModel中获取。
只不过Navigation只能处理具有SavedStateHandle、Application这两个参数或其一的ViewModel;而使用Hilt可以处理多个参数,只需要合理标记@Inject注解到其构造函数即可,Hilt会在编译期自动生成对应的Factory。
问题三:
当你在 Fragment 中调用 by viewModels()(或 by navGraphViewModels())时,底层会取当前的 NavBackStackEntry 作为 ViewModel 的宿主。
//NavController#navigate -> NavBackStackEntry#create
private fun navigate(
node: NavDestination,
//传入的bundle
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
){
...
val backStackEntry = NavBackStackEntry.create(
context,
node,
//传入的bundle
finalArgs,
hostLifecycleState,
viewModel
)
...
}
//NavBackStackEntry#create -> NavBackStackEntry构造函数
//注意NavBackStackEntry 实现了 SavedStateRegistryOwner,
//HasDefaultViewModelProviderFactory
public class NavBackStackEntry :
SavedStateRegistryOwner,
HasDefaultViewModelProviderFactory{
public fun create(
context: Context?,
destination: NavDestination,
//bundle
arguments: Bundle? = null,
hostLifecycleState: Lifecycle.State = Lifecycle.State.CREATED,
viewModelStoreProvider: NavViewModelStoreProvider? = null,
id: String = UUID.randomUUID().toString(),
savedState: Bundle? = null
): NavBackStackEntry =
NavBackStackEntry(
context,
destination,
//bundle
//构造参数中arguments被赋值给
//NavBackStackEntry的属性immutableArgs
arguments,
hostLifecycleState,
viewModelStoreProvider,
id,
savedState
)
}
}
//immutableArgs被赋值给arguments
public val arguments: Bundle?
get() =
if (immutableArgs == null) {
null
} else {
Bundle(immutableArgs)
}
//defaultViewModelCreationExtras 会被用到在创建ViewModel时
//详见下面navGraphViewModels/viewModels
override val defaultViewModelCreationExtras: CreationExtras
get() {
val extras = MutableCreationExtras()
(context?.applicationContext as? Application)?.let { application ->
extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] = application
}
extras[SAVED_STATE_REGISTRY_OWNER_KEY] = this
extras[VIEW_MODEL_STORE_OWNER_KEY] = this
//可以看到arguments被通过DEFAULT_ARGS_KEY放入CreationExtras
arguments?.let { args -> extras[DEFAULT_ARGS_KEY] = args }
return extras
}
//navGraphViewModels()/viewModels() ->
//CreationExtras#createSavedStateHandle()
@MainThread
public fun CreationExtras.createSavedStateHandle(): SavedStateHandle {
//...
//DEFAULT_ARGS_KEY,导航时传入的Bundle
//bundle
val defaultArgs = this[DEFAULT_ARGS_KEY]
val key = this[VIEW_MODEL_KEY] ?: throw IllegalArgumentException(
"CreationExtras must have a value by `VIEW_MODEL_KEY`"
)
return createSavedStateHandle(
savedStateRegistryOwner, viewModelStateRegistryOwner, key,
//bundle
defaultArgs
)
}
//SavedStateHandleSupport#createSavedStateHandle->SavedStateHandle#createHandle
private fun createSavedStateHandle(
savedStateRegistryOwner: SavedStateRegistryOwner,
viewModelStoreOwner: ViewModelStoreOwner,
key: String,
defaultArgs: Bundle?
): SavedStateHandle {
//...
return viewModel.handles[key] ?: SavedStateHandle.createHandle(
provider.consumeRestoredStateForKey(key),
//bundle
defaultArgs
).also { viewModel.handles[key] = it }
}
//SavedStateHandle#createHandle
fun createHandle(restoredState: Bundle?, defaultState: Bundle?): SavedStateHandle {
if (restoredState == null) {
return if (defaultState == null) {
SavedStateHandle()
} else {
val state: MutableMap<String, Any?> = HashMap()
for (key in defaultState.keySet()) {
//bundle
state[key] = defaultState[key]
}
SavedStateHandle(state)
}
}
//...
}
//如果通过navGraphViewModels/viewModels 委托创建ViewModel
//就会传递defaultViewModelCreationExtras
//使得ViewModel可以获得传入的Bundle
@MainThread
public inline fun <reified VM : ViewModel> Fragment.navGraphViewModels(
@IdRes navGraphId: Int,
noinline extrasProducer: (() -> CreationExtras)? = null,
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
val backStackEntry by lazy { findNavController().getBackStackEntry(navGraphId) }
val storeProducer: () -> ViewModelStore = { backStackEntry.viewModelStore }
return createViewModelLazy(
VM::class,
storeProducer,
{ extrasProducer?.invoke() ?: backStackEntry.defaultViewModelCreationExtras },
factoryProducer ?: { backStackEntry.defaultViewModelProviderFactory }
)
}
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline extrasProducer: (() -> CreationExtras)? = null,
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(
VM::class,
{ viewModelStore },
factoryPromise,
{ extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras }
)
}
问题四:
属于同一个导航子图,就能共享 SharedViewModel。
Navigation 组件为导航图(NavGraph)生成了一个特殊的 NavBackStackEntry,它本身就是一个 ViewModelStoreOwner。
当在 Fragment 中写
val vm: SharedViewModel by navGraphViewModels(R.id.my_nav_graph)
委托会调用
findNavController().getBackStackEntry(R.id.my_nav_graph)
拿到那个NavGraph级别的NavBackStackEntry,并使用它的 ViewModelStore。这样,所有同一个NavGraph内的 Fragment 都“挂”在同一个NavBackStackEntry上,自然共享同一份ViewModelStore。
同时,在 MVVM 架构中,不推荐直接在 ViewModel 中发起导航操作,但可以通过状态流(如 StateFlow)或事件机制将导航指令传递给 UI 层执行。
1.使用密封类定义导航事件
// 定义导航事件
sealed class NavigationEvent {
data class ToDestination(val route: String) : NavigationEvent()
object PopBackStack : NavigationEvent()
data class ShowDialog(val message: String) : NavigationEvent()
}
class MyViewModel : ViewModel() {
// 使用 SharedFlow 避免事件重放
private val _navigationEvents = MutableSharedFlow<NavigationEvent>()
val navigationEvents = _navigationEvents.asSharedFlow()
fun onButtonClick() {
viewModelScope.launch {
_navigationEvents.emit(NavigationEvent.ToDestination("detail_screen"))
}
}
}
UI 层处理导航事件
// Activity/Fragment 中
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigationEvents.collect { event ->
when (event) {
is NavigationEvent.ToDestination -> {
findNavController().navigate(event.route)
}
NavigationEvent.PopBackStack -> {
findNavController().popBackStack()
}
is NavigationEvent.ShowDialog -> {
showDialog(event.message)
}
}
}
}
}
再或者是,2.使用事件包装类(避免事件重放问题)
class Event<out T>(private val content: T) {
var isHandled = false
private set
fun getContentIfNotHandled(): T? {
return if (isHandled) null else {
isHandled = true
content
}
}
}
class MyViewModel : ViewModel() {
private val _navigateEvent = MutableStateFlow<Event<String>?>(null)
val navigateEvent: StateFlow<Event<String>?> = _navigateEvent
fun triggerNavigation() {
_navigateEvent.value = Event("detail_screen")
}
}
// UI 层处理
lifecycleScope.launch {
viewModel.navigateEvent
.filterNotNull()
.collect { event ->
event.getContentIfNotHandled()?.let { route ->
findNavController().navigate(route)
}
}
}
懒加载、委托创建ViewModel的原理?
//TODO
其实算是语法糖了,看看源码就能知道,over。
ViewModel的Scope机制(Activity级 vs Fragment级)?
| 场景 | 推荐 Scope | 使用方法 |
|---|---|---|
| 单个 Fragment 内部状态管理 | Fragment 级 | viewModels() |
| 多 Fragment 以及 Activity共享数据 | Activity 级 | activityViewModels() |
| 同一导航图内的 Fragment共享数据 | NavGraph级 | navGraphViewModels() |
多线程环境下操作 ViewModel 是否安全?
不安全。
ViewModel 自身不是线程安全容器。ViewModel 只是一个普通的对象;它内部的数据结构(MutableLiveData、MutableStateFlow、自建集合等)也各有线程约束。
对于多线程环境,注意使用JUC框架、Livedata、MutableStateFlow、协程切换上下文,保证尽可能的减少竞态。
对比 ViewModel 与 Presenter 的异同?
相同点
- 职责定位:都是位于 UI(View)与数据(Model)之间的“中介”,负责将 Model 转换为 View 能直接使用的形式,并处理部分展示逻辑。
- 解耦效果:都能让 View 保持轻量、可测试,将业务或展示逻辑剥离到独立的组件中。
- 易于单元测试:由于不直接依赖具体的 View 实现,Presenter 和 ViewModel 都可以通过模拟接口/绑定进行单元测试。
差异
1. 与视图的通信方式
- Presenter
- 主动控制视图:通过接口(如
IView)直接调用视图方法更新UI(例:view.showData(data))。需为每个视图定义接口,模板代码较多。 - 强耦合:需要手动绑定/解绑视图引用(在
onDestroy()中解绑避免泄漏)。
- 主动控制视图:通过接口(如
- ViewModel
- 数据驱动:通过可观察数据(如
LiveData、StateFlow)暴露状态,视图主动订阅数据变化。与LiveData/Flow结合,使用观察者模式,减少模板代码。 - 解耦:不持有视图引用,由框架自动管理生命周期(避免内存泄漏)。
- 数据驱动:通过可观察数据(如
2. 生命周期管理
- Presenter
- 需手动处理生命周期:在
onCreate()中初始化,在onDestroy()中销毁,配置变更(如旋转屏幕)时需自行恢复数据。
- 需手动处理生命周期:在
- ViewModel
- 内置生命周期感知:由于有官方提供的ViewModel框架,能够自动关联 Activity/Fragment 生命周期,配置变更时保留实例(无需手动恢复数据)。
- 通过
ViewModelStore管理,在Activity/Fragment主动销毁时自动清理。
3. 数据保存能力
- Presenter
- 通常不负责数据持久化,需依赖外部组件保存状态。
- ViewModel
- 配合
SavedStateHandle支持临时状态保存:在配置变更期间保留数据(例如用户输入的表单内容)。
- 配合
ViewModel 在 Jetpack Compose 中的应用变化?
hilt + compose_navigation 的使用。
如果使用NavAppBar(类似的底部导航UI组件)进行导航,对初始导航页面(底部 Tab),应该使用hiltViewModel()委托创建ViewModel,同时传入 NavBackStackEntry,把ViewModel绑到根导航图上,这样初始导航页面的ViewModel就不会因为来回切换Tab,而被销毁;
NavHost(...){
// 拿到根图对应的 BackStackEntry
val rootEntry = navController.getBackStackEntry(navController.graph.id)
composable(...){
HomeScreen(hiltViewModel(rootEntry))
}
}
同时注意:在navController.navigate时,是否要saveState、restoreState,决定 Compose 局部 UI 状态在导航进出后是否能够恢复。(saveState、restoreState与SavedStateHandle无关,这是Compose的SaveableStateRegistry机制)
保存、恢复的只有可组合函数状态,例如: LazyListState、TextField 的输入内容等;如果要保存其他非可组合函数状态,请放入ViewModel中,以及SavedStateHandle。
否则UI状态会不见,导致体验变差。
第二层的导航层(初始导航层(底部 Tab)的子页面):
| 作用域 | 何时选用 |
|---|---|
| 根导航图 | 当“初始导航层(底部 Tab)”和它的所有子页面都要共享同一份数据时,用 hiltViewModel(owner = rootEntry) 。这会将 VM 绑定到根导航图的 ViewModelStore。 |
| 子页面 NavBackStackEntry | 如果子页面逻辑和数据只在该页面内使用,且无须其他兄弟页面/Tab 拉取同一份 VM,就用默认的 hiltViewModel()。它会把 VM 存到「当前路由」对应的 BackStackEntry。 |
参考
https://developer.android.com/training/dependency-injection/hilt-jetpack?hl=zh-cn#kotlin
https://juejin.cn/post/7072541180667363358
后记
算是难产了,ViewModel的源码阅读记录,写了快两个月,中间又去写了SavedStateHandle的源码阅读记录,网上对SavedStateHandle的解析不是很多,或者不够详细,对于Leacy部分没有解释,大模型也是一问一个不吱声,也是没想到能写这么久。
- 发现问题
- 解决思路
- 解决方案(核心源码)
- 注意
- 工厂模式在 ViewModel 中的应用?为什么不能直接实例化ViewModel?
- 有哪些类型的Factory?
- ViewModelStore 的内部数据结构是什么?
- ViewModel的生命周期与Activity有何不同?
- 为什么ViewModel可以保存屏幕旋转时的数据/解释ViewModelStore的存储原理?
- Fragment创建ViewModel的流程?
- ViewModel如何避免内存泄漏?
- ViewModel 中启动协程需要注意什么?ViewModel 与 LiveData/Flow 的最佳实践?
- 为什么 ViewModel 不直接持有 Context?AndroidViewModel与普通ViewModel的区别?
- ViewModel在什么时候会被销毁?什么时候不被销毁?
- ViewModel的参数传递有哪些方式?
- 如何实现Fragment间共享ViewModel?
- 为什么导航时传参,可以注入到SavedStateHandle中?
- ViewModel如何与Navigation组件配合使用?
- 懒加载、委托创建ViewModel的原理?
- ViewModel的Scope机制(Activity级 vs Fragment级)?
- 多线程环境下操作 ViewModel 是否安全?
- 对比 ViewModel 与 Presenter 的异同?
- ViewModel 在 Jetpack Compose 中的应用变化?
- 参考
- 后记