SharedViewModel 深度指南:跨组件数据共享核心方案

1 阅读4分钟

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 间的数据通信问题

适用场景:

  1. 主从界面架构(Master-Detail)
  2. 底部导航(BottomNavigationView)的页面状态同步
  3. 多步骤表单(Multi-step form)
  4. 共享数据加载状态(如全局加载指示器)
  5. 跨 Fragment 的用户会话管理

2. 使用方式与代码实现

基础实现(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 多 Fragmentby activityViewModels()
Fragment 自身作用域单个 Fragment 内部使用by viewModels()
Navigation 图作用域在 Navigation 组件范围内共享by navGraphViewModels(R.id.nav_graph)
Application 作用域全局共享(需自定义)使用 ViewModelProvider.AndroidViewModelFactory

Navigation 组件中的应用:

// 在 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()

安全准则:

  1. 避免上下文引用​:SharedViewModel 绝不应持有 Context

    // 错误做法❌
    class SharedViewModel(app: Application) : AndroidViewModel(app) {
        val context: Context = app // 危险!
    }
    
    // 正确做法✅ 使用资源ID代替
    fun getString(@StringRes resId: Int) = getApplication<Application>().getString(resId)
    
  2. 内存泄漏防护​:

    • Fragment 中使用 viewLifecycleOwner 替代 this
    • Activity 销毁自动清除 ViewModel
  3. 多线程安全​:

    class 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
            }
        }
    }
    
  4. 数据封装原则​:

    data 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:事件总线替代方案

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:响应式状态管理

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:跨进程边界通信(通过接口)

// 定义合约接口
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. 性能优化策略

优化技巧:

  1. 使用 StateFlow 替代 LiveData​:

    private val _data = MutableStateFlow("")
    val data: StateFlow<String> = _data.asStateFlow()
    
    // 更新值
    fun updateValue(newVal: String) {
        _data.value = newVal
    }
    
  2. 防止过度更新​:

    // 使用 distinctUntilChanged()
    val distinctData = sharedViewModel.data
        .distinctUntilChanged()
        .stateIn(
            scope = viewLifecycleOwner.lifecycleScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = ""
        )
    
  3. 批量更新优化​:

    // 使用 StateFlow.update 进行批量修改
    fun addItems(newItems: List<Item>) {
        _state.update { current ->
            current.copy(
                items = current.items + newItems,
                updatedAt = System.currentTimeMillis()
            )
        }
    }
    
  4. 使用 ViewModel 缓存​:

    class 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)

单元测试示例:

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. 反模式与常见问题

应避免的反模式:

  1. 滥用全局状态​:

    graph LR
        A[FragmentA] --> G[GlobalState]
        B[FragmentB] --> G
        C[FragmentC] --> G
        D[ActivityA] --> G
        E[ActivityB] --> G
    

    问题​:导致状态不可控,增加调试难度

  2. 直接暴露可变状态​:

    // 错误做法❌
    class SharedViewModel : ViewModel() {
        val mutableList = mutableListOf<String>() // 直接暴露可变集合
    }
    
  3. 忽略生命周期​:

    // 内存泄漏风险
    sharedViewModel.data.observe(this) { /* ... */ } 
    // 正确应使用viewLifecycleOwner
    

常见问题解决方案:

问题现象根本原因解决方案
数据更新后UI不刷新LiveData被多个观察者覆盖使用SharingStarted.WhileSubscribed()或SingleLiveEvent
旋转后数据重置Fragment重建时未保存状态使用SavedStateHandle保留状态
内存占用过高大数据集未及时清理实现onCleared()清理资源
重复事件消费事件总线模式未使用单次事件使用SharedFlow + replay=0

SavedStateHandle 集成示例​:

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 结构的数据流管理。遵循上述最佳实践和模式,可以在保证性能和安全的同时,实现优雅的组件间通信。