SharedViewModel 深度指南:跨组件数据共享核心方案
1. 核心概念与适用场景
什么是 SharedViewModel?
graph TD
A[Activity] --> B[Fragment 1]
A --> C[Fragment 2]
A --> D[Fragment 3]
B --> E[SharedViewModel]
C --> E
D --> E
定义 :在同一个宿主 Activity 作用域内共享的 ViewModel 实例
目的:解决多 Fragment 或 Activity-Fragment 间的数据通信问题
适用场景:
- 主从界面架构(Master-Detail)
- 底部导航(BottomNavigationView)的页面状态同步
- 多步骤表单(Multi-step form)
- 共享数据加载状态(如全局加载指示器)
- 跨 Fragment 的用户会话管理
2. 使用方式与代码实现
基础实现(Kotlin 委托方式):
kotlin
// SharedViewModel.kt
class SharedViewModel : ViewModel() {
private val _sharedData = MutableLiveData<String>()
val sharedData: LiveData<String> get() = _sharedData
fun updateData(newValue: String) {
_sharedData.value = newValue
}
}
// MasterFragment.kt
class MasterFragment : Fragment() {
// 获取宿主Activity作用域的ViewModel
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
btnSend.setOnClickListener {
sharedViewModel.updateData("数据来自MasterFragment")
}
}
}
// DetailFragment.kt
class DetailFragment : Fragment() {
// 获取同一个ViewModel实例
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
sharedViewModel.sharedData.observe(viewLifecycleOwner) { data ->
textView.text = "接收到的数据: $data"
}
}
}
3. 作用域管理(核心技术)
ViewModel 作用域比较:
作用域 | 应用场景 | 获取方式 |
---|---|---|
Activity 作用域 | 单 Activity 多 Fragment | by activityViewModels() |
Fragment 自身作用域 | 单个 Fragment 内部使用 | by viewModels() |
Navigation 图作用域 | 在 Navigation 组件范围内共享 | by navGraphViewModels(R.id.nav_graph) |
Application 作用域 | 全局共享(需自定义) | 使用 ViewModelProvider.AndroidViewModelFactory |
Navigation 组件中的应用:
xml
// 在 navigation XML 中定义共享作用域
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_nav"
app:viewModelScope="global"> <!-- 关键属性 -->
<fragment android:id="@+id/master" .../>
<fragment android:id="@+id/detail" .../>
</navigation>
// Fragment 中获取
val sharedViewModel: SharedViewModel by navGraphViewModels(R.id.main_nav)
4. 生命周期与安全注意事项
生命周期图示:
sequenceDiagram
participant A as Activity
participant F1 as Fragment1
participant SV as SharedViewModel
participant F2 as Fragment2
A->>A: onCreate()
A->>SV: 创建ViewModel实例
A->>F1: onAttach()/onCreate()
F1->>SV: 获取实例
A->>F2: replace() Fragment2
F2->>SV: 获取同一个实例
F2->>SV: 观察数据变化
F1->>SV: 更新数据
SV->>F2: 通知数据变更
A->>A: onDestroy()
A->>SV: onCleared()
安全准则:
-
避免上下文引用:SharedViewModel 绝不应持有 Context
less// 错误做法❌ class SharedViewModel(app: Application) : AndroidViewModel(app) { val context: Context = app // 危险! } // 正确做法✅ 使用资源ID代替 fun getString(@StringRes resId: Int) = getApplication<Application>().getString(resId)
-
内存泄漏防护:
- Fragment 中使用
viewLifecycleOwner
替代this
- Activity 销毁自动清除 ViewModel
- Fragment 中使用
-
多线程安全:
kotlinclass SharedViewModel : ViewModel() { private val _data = MutableStateFlow<List<String>>(emptyList()) val data: StateFlow<List<String>> = _data.asStateFlow() // 确保线程安全 fun addItem(item: String) { _data.update { currentList -> currentList + item } } }
-
数据封装原则:
kotlindata class UiState( val items: List<String> = emptyList(), val loading: Boolean = false, val error: String? = null ) class SharedViewModel : ViewModel() { private val _state = MutableStateFlow(UiState()) val state: StateFlow<UiState> = _state.asStateFlow() }
5. 高级应用模式
模式 1:事件总线替代方案
kotlin
class SharedEventViewModel : ViewModel() {
// 单次消费事件队列
private val _events = MutableSharedFlow<Event>()
val events = _events.asSharedFlow()
sealed class Event {
data class ShowToast(val message: String) : Event()
object NavigateToProfile : Event()
}
fun postEvent(event: Event) {
viewModelScope.launch {
_events.emit(event)
}
}
}
// 接收方(Fragment/Activity)
sharedViewModel.events
.onEach { event ->
when (event) {
is ShowToast -> showToast(event.message)
is NavigateToProfile -> navigateToProfile()
}
}
.launchIn(lifecycleScope)
模式 2:响应式状态管理
kotlin
class SharedStateViewModel : ViewModel() {
private val _userState = MutableStateFlow(UserState())
val userState: StateFlow<UserState> = _userState.asStateFlow()
// 公开原子操作方法
fun login(user: User) {
_userState.update { it.copy(
isLoggedIn = true,
currentUser = user
)}
}
fun logout() {
_userState.update { UserState() }
}
}
// 登录状态同步组件
class LoginStatusBar : Fragment() {
private val viewModel: SharedStateViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.userState
.onEach { state ->
if (state.isLoggedIn) {
showUserProfile(state.currentUser)
} else {
showLoginButton()
}
}
.launchIn(viewLifecycleOwner.lifecycleScope)
}
}
模式 3:跨进程边界通信(通过接口)
kotlin
// 定义合约接口
interface SharedContract {
fun onDataShared(data: Bundle)
}
// SharedViewModel 中实现
class SharedViewModel : ViewModel() {
private val receivers = mutableSetOf<SharedContract>()
fun registerReceiver(receiver: SharedContract) {
receivers.add(receiver)
}
fun unregisterReceiver(receiver: SharedContract) {
receivers.remove(receiver)
}
fun broadcastData(data: String) {
val bundle = bundleOf("key" to data)
receivers.forEach { it.onDataShared(bundle) }
}
}
// Fragment 实现接口
class ReceiverFragment : Fragment(), SharedContract {
override fun onAttach(context: Context) {
super.onAttach(context)
sharedViewModel.registerReceiver(this)
}
override fun onDetach() {
super.onDetach()
sharedViewModel.unregisterReceiver(this)
}
override fun onDataShared(data: Bundle) {
val value = data.getString("key")
// 处理数据
}
}
6. 性能优化策略
优化技巧:
-
使用 StateFlow 替代 LiveData:
kotlinprivate val _data = MutableStateFlow("") val data: StateFlow<String> = _data.asStateFlow() // 更新值 fun updateValue(newVal: String) { _data.value = newVal }
-
防止过度更新:
ini// 使用 distinctUntilChanged() val distinctData = sharedViewModel.data .distinctUntilChanged() .stateIn( scope = viewLifecycleOwner.lifecycleScope, started = SharingStarted.WhileSubscribed(5000), initialValue = "" )
-
批量更新优化:
kotlin// 使用 StateFlow.update 进行批量修改 fun addItems(newItems: List<Item>) { _state.update { current -> current.copy( items = current.items + newItems, updatedAt = System.currentTimeMillis() ) } }
-
使用 ViewModel 缓存:
kotlinclass SharedViewModel : ViewModel() { private val cache = mutableMapOf<String, Bitmap>() fun getImage(url: String): Bitmap? { return cache[url] ?: run { val bitmap = loadImage(url) // 耗时的图像加载 cache[url] = bitmap bitmap } } override fun onCleared() { cache.clear() // 及时释放资源 super.onCleared() } }
7. 测试策略(JUnit + MockK)
单元测试示例:
kotlin
class SharedViewModelTest {
private lateinit var viewModel: SharedViewModel
@Before
fun setup() {
viewModel = SharedViewModel()
}
@Test
fun `当更新数据时 应通知所有观察者`() = runTest {
// 准备测试观察者
val observer1 = mockk<Observer<String>>(relaxed = true)
val observer2 = mockk<Observer<String>>(relaxed = true)
// 注册观察者
viewModel.sharedData.observeForever(observer1)
viewModel.sharedData.observeForever(observer2)
// 执行操作
viewModel.updateData("Test Value")
// 验证
verify(timeout = 100) {
observer1.onChanged("Test Value")
observer2.onChanged("Test Value")
}
// 清理
viewModel.sharedData.removeObserver(observer1)
viewModel.sharedData.removeObserver(observer2)
}
@Test
fun `多个Fragment应获取相同的ViewModel实例`() {
// 模拟场景
val activity = mockk<FragmentActivity>()
val factory = ViewModelProvider.NewInstanceFactory()
val fragment1 = TestFragment()
val fragment2 = TestFragment()
// 通过相同activity获取ViewModel
val vm1 = ViewModelProvider(activity, factory).get(SharedViewModel::class.java)
val vm2 = ViewModelProvider(activity, factory).get(SharedViewModel::class.java)
// 验证是同一个实例
assertSame(vm1, vm2)
}
}
8. 反模式与常见问题
应避免的反模式:
-
滥用全局状态:
graph LR A[FragmentA] --> G[GlobalState] B[FragmentB] --> G C[FragmentC] --> G D[ActivityA] --> G E[ActivityB] --> G问题:导致状态不可控,增加调试难度
-
直接暴露可变状态:
kotlin// 错误做法❌ class SharedViewModel : ViewModel() { val mutableList = mutableListOf<String>() // 直接暴露可变集合 }
-
忽略生命周期:
javascript// 内存泄漏风险 sharedViewModel.data.observe(this) { /* ... */ } // 正确应使用viewLifecycleOwner
常见问题解决方案:
问题现象 | 根本原因 | 解决方案 |
---|---|---|
数据更新后UI不刷新 | LiveData被多个观察者覆盖 | 使用SharingStarted.WhileSubscribed()或SingleLiveEvent |
旋转后数据重置 | Fragment重建时未保存状态 | 使用SavedStateHandle保留状态 |
内存占用过高 | 大数据集未及时清理 | 实现onCleared()清理资源 |
重复事件消费 | 事件总线模式未使用单次事件 | 使用SharedFlow + replay=0 |
SavedStateHandle 集成示例:
kotlin
class SharedViewModel(private val state: SavedStateHandle) : ViewModel() {
// 使用保存的状态处理配置变更
private val _searchQuery = state.getLiveData<String>("searchKey", "")
val searchQuery: LiveData<String> = _searchQuery
fun setQuery(query: String) {
// 保存状态
state.set("searchKey", query)
_searchQuery.value = query
}
}
SharedViewModel 是现代化 Android 架构的核心组件,合理使用时能大幅简化复杂 UI 结构的数据流管理。遵循上述最佳实践和模式,可以在保证性能和安全的同时,实现优雅的组件间通信。