Android Jetpack(4) ViewModel源码阅读记录

发现问题

  1. 数据丢失问题:Activity/Fragment 因配置变更(如屏幕旋转)被销毁重建时,临时数据丢失需重新初始化。
  2. 生命周期耦合:异步操作(如网络请求)中直接操作 UI 可能导致内存泄漏或崩溃。
  3. 职责混杂:业务逻辑和 UI 代码耦合在 Activity/Fragment 中,违反单一职责原则。
  4. 状态冗余:多 Fragment 共享同一数据时需通过复杂回调或 EventBus 传递。

解决思路

核心需求

  1. 生命周期感知的数据持有:在配置变更(如旋转屏幕)时保留数据,避免重复加载。
  2. UI控制器解耦:将业务逻辑与数据存储从 Activity/Fragment 中分离,降低耦合度。
  3. 资源自动释放:在关联的 UI 控制器完全销毁时自动清理资源,避免内存泄漏。
  4. 跨组件通信:支持 Fragment 之间或组件间安全共享数据。

关键思路

  1. 工厂方法模式
    Why?
    • 灵活实例化:通过 ViewModelProvider.Factory 接口,允许自定义 ViewModel 的创建逻辑(如依赖注入)。
    • 统一入口:ViewModelProvider 封装了 ViewModel 的获取逻辑,调用方无需关心实例的创建细节。
      实现类:
    • NewInstanceFactory:默认反射创建无参数 ViewModel 实例。
    • AndroidViewModelFactory:支持带 Application 参数的 ViewModel。
    • SavedStateViewModelFactory:支持带 SavedStateHandle 参数的 ViewModel。
  2. 代理模式
    Why?
    • 生命周期代理:通过 ReportFragment代理 Activity 的生命周期事件,使得ViewModelStore 与 Activity/Fragment 的生命周期绑定,能够在 onDestroy() 时自动清除(非配置变更场景)。
    • 逻辑解耦:Activity 无需直接处理 ViewModel 的生命周期,由系统框架自动管理。
  3. 状态管理
    Why?
    • 资源释放:在 ViewModel#onCleared() 中释放资源(如取消网络请求),避免内存泄漏。
    • 集中管理:ViewModelStore 作为容器,通过 HashMap 缓存 ViewModel 实例,确保同一作用域内实例唯一。
    • 事件拦截:通过 SavedStateHandle 保存/恢复临时状态(如屏幕旋转时的输入框内容)。
    • ViewModelStore:内部维护 HashMap<String, ViewModel>。
    • ViewModelStoreOwner:接口(如 Activity/Fragment),提供 ViewModelStore。

解决方案(核心源码)

核心类与接口

  1. ViewModel(Impl)
    作为业务逻辑和界面状态的容器,其生命周期与 ViewModelStoreOwner(如 Activity、Fragment)绑定,确保数据在配置变更(如屏幕旋转)时不被销毁。支持ViewModel持有实现Closeable接口的资源,ViewModelStoreOwner会帮助自动释放Closeable资源。开发者通过继承该类实现自定义逻辑,并通过 onCleared() 释放资源,但是onCleared()无法释放Closeable资源,除非手动关闭。

  2. 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。

  3. ViewModelStoreOwner 接口
    定义 ViewModelStore 的持有者,如 ComponentActivity、Fragment 或 NavBackStackEntry(用于导航组件)。ViewModel 的作用域由其 ViewModelStoreOwner 的生命周期决定。

  4. ViewModelStore
    存储 ViewModel 实例的容器,当 ViewModelStoreOwner 因为销毁时分发ON_DESTROY事件,如果是非配置变更导致的销毁,那么就会触发 ViewModelStore 的清理逻辑,释放所有ViewModel资源,包括其中的Closeable资源。

  5. SavedStateHandle
    用于在进程重建(如系统杀死应用后恢复)时临时保存数据。通过键值对存储数据,支持通过LiveData 、 StateFlow 订阅数据。SavedStateHandle 的数据仅在进程重建时有效,应用彻底关闭后需依赖持久化存储(如数据库)

  6. SavedStateViewModelFactory
    SavedStateViewModelFactory 是用于创建支持状态临时保存的 ViewModel 的工厂类,其核心目标是让 ViewModel 能够通过 SavedStateHandle 访问和保存进程重建后的状态数据。

源码流程图(关键流程)

vm_flow

时序图

ViewModelStore、Bundle的保存与恢复

viewstore_bundle_s_r.mmd_wm

ViewModel组件库运作

vm_timeorder.mmd_wm

注意

工厂模式在 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 的异同?

相同点

  1. 职责定位:都是位于 UI(View)与数据(Model)之间的“中介”,负责将 Model 转换为 View 能直接使用的形式,并处理部分展示逻辑。
  2. 解耦效果:都能让 View 保持轻量、可测试,将业务或展示逻辑剥离到独立的组件中。
  3. 易于单元测试:由于不直接依赖具体的 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部分没有解释,大模型也是一问一个不吱声,也是没想到能写这么久。