一、ViewModel 不是全局数据仓库

在 Android 开发中,ViewModel 主要用于保存和管理 页面相关的 UI 状态

例如:

class ProfileViewModel : ViewModel() {
    val userName = MutableLiveData<String>()
}

ViewModel 的生命周期不是全局的,而是绑定到某个作用域上的。

常见作用域包括:

Activity 作用域
Fragment 作用域
Navigation Graph 作用域

也就是说,ViewModel 能不能共享,关键不在于类名,而在于它绑定到了哪个 ViewModelStoreOwner


二、SharedViewModel 不是特殊类型

开发中经常会看到 SharedViewModel 这个名字,但它并不是 Android 提供的特殊 ViewModel。

例如:

class SharedViewModel : ViewModel() {
    val selectedItem = MutableLiveData<String>()
}

这里的 SharedViewModel 只是开发者自己起的名字。

真正让它“共享”的是作用域,比如:

private val viewModel: SharedViewModel by activityViewModels()

如果多个 Fragment 都使用 activityViewModels(),那么它们拿到的是同一个 Activity 作用域下的 ViewModel。

例如:

MainActivity
 ├── ListFragment
 └── DetailFragment

两个 Fragment 都这样写:

private val viewModel: SharedViewModel by activityViewModels()

这时它们共享的是同一个 ViewModel 实例。

但是如果两个 Fragment 都这样写:

private val viewModel: SharedViewModel by viewModels()

那么它们拿到的是各自独立的 ViewModel 实例。

所以可以记住一句话:

SharedViewModel 不是特殊 ViewModel;
它只是普通 ViewModel + 更大的作用域。

三、AndroidViewModel 也不是 Application 作用域

有些人会想到 AndroidViewModel

class MyViewModel(application: Application) : AndroidViewModel(application)

AndroidViewModel 的特点是可以拿到 Application 对象。

但是它本身并不代表 Application 作用域。

如果在 Activity 中使用:

private val viewModel: MyViewModel by viewModels()

它仍然是 Activity 作用域。

如果在 Fragment 中使用:

private val viewModel: MyViewModel by viewModels()

它仍然是 Fragment 作用域。

所以:

AndroidViewModel = 可以拿到 Application Context 的 ViewModel
不是 = Application 级别的全局 ViewModel

四、以前为什么很多共享数据直接放 Application?

以前很多 Android 项目确实喜欢把共享数据直接放在 Application 里。

例如:

class MyApp : Application() {
    var token: String? = null
    var currentUser: User? = null
}

然后在各个地方这样使用:

val app = application as MyApp
val user = app.currentUser

这种写法确实可以工作。

早期项目中,常见结构比较简单:

Activity
Fragment
Application
AsyncTask
Handler

那时 ViewModel、Repository、Room、DataStore、Hilt 等架构组件还没有现在这么普及,所以 Application 很容易被当成“全局变量箱子”。

常见放进去的数据包括:

登录状态
token
currentUser
全局配置
SDK 管理对象
购物车缓存
定位信息
设备信息

五、为什么现在不推荐把业务数据直接放 Application?

Application 的生命周期接近整个应用进程。

如果把大量可变业务数据都放在 Application 中,会带来很多问题。

例如:

class MyApp : Application() {
    var currentUser: User? = null
}

这种写法的问题是:

谁都可以改
什么时候改的不清楚
什么时候清理不清楚
测试困难
页面之间耦合严重
进程被杀后数据会丢失

比如 Activity A 修改了 currentUser,Activity B 也修改了 currentUser,Service 也可能修改它。

时间久了就很难判断:

这个数据是谁改的?
什么时候改的?
为什么现在是这个值?
进程重启后怎么办?

所以现在更推荐把 Application 当成:

全局初始化入口
依赖容器入口
全局 Context 提供者

而不是直接当作业务数据仓库。


六、Application 更适合做什么?

Application 适合做一些全局初始化工作。

例如:

class MyApp : Application() {

    override fun onCreate() {
        super.onCreate()

        // 初始化日志库
        // 初始化崩溃收集 SDK
        // 初始化推送 SDK
        // 初始化数据库
        // 初始化依赖注入框架
    }
}

也可以作为依赖容器的入口:

class MyApp : Application() {
    lateinit var appContainer: AppContainer
}
class AppContainer(context: Context) {
    val userRepository = UserRepository(...)
    val authRepository = AuthRepository(...)
}

这种方式比直接把 currentUsertoken 放在 Application 里更清晰。


七、Repository 是什么?

Repository 不是具体的存储位置。

它更像是一个“数据管家”。

它负责统一管理数据来源。

数据可能来自:

内存缓存
DataStore
Room 数据库
网络接口
文件
其他 SDK

ViewModel 不需要关心数据到底来自哪里,只需要向 Repository 要数据。

结构通常是:

Activity / Fragment
        ↓
ViewModel
        ↓
Repository
        ↓
DataStore / Room / Network / Memory Cache

八、currentUser 放在 Repository,具体存在哪里?

currentUser 放在 Repository,并不代表一定要放数据库。

它可能有几种存储方式。


九、方式一:只存在内存中

适合 App 运行期间的临时缓存。

class UserRepository {

    private val _currentUser = MutableStateFlow<User?>(null)
    val currentUser: StateFlow<User?> = _currentUser

    fun setCurrentUser(user: User) {
        _currentUser.value = user
    }

    fun logout() {
        _currentUser.value = null
    }
}

这种方式简单,但缺点是:

App 进程被杀后,数据就没了。

所以只适合临时状态。


十、方式二:存到 DataStore

DataStore 适合保存轻量级数据。

例如:

token
refreshToken
userId
登录状态
用户设置
是否首次启动

示例:

class AuthRepository(
    private val dataStore: DataStore<Preferences>
) {

    val tokenFlow: Flow<String?> = dataStore.data.map { prefs ->
        prefs[stringPreferencesKey("token")]
    }

    suspend fun saveToken(token: String) {
        dataStore.edit { prefs ->
            prefs[stringPreferencesKey("token")] = token
        }
    }

    suspend fun clearToken() {
        dataStore.edit { prefs ->
            prefs.remove(stringPreferencesKey("token"))
        }
    }
}

这种方式适合保存登录凭证、用户 ID 等轻量数据。


十一、方式三:存到 Room 数据库

Room 适合保存结构化数据。

例如:

用户资料
联系人
订单
聊天记录
收藏列表
文章缓存

用户信息可以设计成实体类:

@Entity
data class UserEntity(
    @PrimaryKey val id: Long,
    val name: String,
    val avatar: String
)

Repository 中统一管理:

class UserRepository(
    private val userDao: UserDao,
    private val api: UserApi
) {

    fun observeCurrentUser(userId: Long): Flow<UserEntity?> {
        return userDao.observeUser(userId)
    }

    suspend fun refreshUser(userId: Long) {
        val user = api.getUser(userId)
        userDao.insert(user.toEntity())
    }
}

这种方式适合保存比较完整的用户资料。


十二、真实项目中常见组合

实际项目里,通常不是只用一种方式,而是组合使用。

比较常见的设计是:

DataStore 保存 token、refreshToken、userId
Room 保存用户详细资料
Network 获取最新用户信息
Repository 统一管理这些数据

结构大概是:

AuthRepository
 └── DataStore
      ├── token
      ├── refreshToken
      └── userId

UserRepository
 ├── Room
 │    └── UserEntity
 └── Network
      └── getUserInfo()

登录成功后:

1. 保存 token 到 DataStore
2. 保存 userId 到 DataStore
3. 请求用户信息
4. 把用户信息缓存到 Room
5. 页面通过 Flow 自动收到 currentUser

退出登录时:

1. 清除 DataStore 里的 token、userId
2. 清除 Room 里的用户缓存
3. currentUser 变成 null

十三、currentUser 可以不是一个变量,而是一个数据流

现代写法中,currentUser 不一定是一个普通变量。

它可以是一个 Flow

例如:

class UserRepository(
    private val authRepository: AuthRepository,
    private val userDao: UserDao
) {

    val currentUser: Flow<User?> =
        authRepository.userIdFlow.flatMapLatest { userId ->
            if (userId == null) {
                flowOf(null)
            } else {
                userDao.observeUser(userId)
            }
        }
}

这个逻辑表示:

先从 DataStore 获取当前登录用户的 userId
再根据 userId 从 Room 查询用户信息
最后暴露成 currentUser

这样 ViewModel 只需要订阅:

val currentUser = userRepository.currentUser

而不用关心数据到底来自 DataStore、Room 还是网络。


十四、ViewModel、Application、Repository 的职责区别

位置

适合放什么

不适合放什么

Application

SDK 初始化、全局容器、全局 Context、依赖入口

页面状态、频繁变化的业务数据

ViewModel

页面 UI 状态、页面业务逻辑、临时页面数据

跨整个 App 的全局数据

Repository

登录状态、用户信息、缓存、数据库、网络数据统一入口

直接操作 View

DataStore

token、userId、用户配置、轻量持久化数据

大量复杂结构化数据

Room

用户资料、订单、聊天记录、列表缓存等结构化数据

简单配置项

内存缓存

临时数据、运行期缓存

必须长期保存的数据


十五、总结

以前很多项目会把共享数据直接放在 Application 里,这种方式简单直接,但容易造成全局变量滥用。

现在更推荐的方式是:

Application 负责全局初始化和依赖入口;
ViewModel 负责页面状态;
Repository 负责统一管理数据;
DataStore / Room / Network 负责具体数据来源。

关于 currentUser,可以这样记:

临时用户状态:可以放内存
token / userId:适合放 DataStore
完整用户资料:适合放 Room
最新用户信息:从 Network 获取
统一管理入口:放在 Repository

一句话总结:

Repository 是数据管家,不是数据本体;
Application 是应用入口,不是全局变量箱子;
ViewModel 是页面状态管理者,不是全局数据仓库。