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

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 委托方式):

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

安全准则:

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

    less 复制代码
    // 错误做法❌
    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. 多线程安全​:

    kotlin 复制代码
    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. 数据封装原则​:

    kotlin 复制代码
    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:事件总线替代方案

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. 性能优化策略

优化技巧:

  1. 使用 StateFlow 替代 LiveData​:

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

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

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

    kotlin 复制代码
    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)

单元测试示例:

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

应避免的反模式:

  1. 滥用全局状态​:

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

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

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

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

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

相关推荐
开发之奋斗人生3 小时前
android关于pthread的使用过程
android·pthread
wu_android4 小时前
Android 视图系统入门指南
android
淡淡的香烟4 小时前
Android11 Launcher3实现去掉抽屉改为单层
android
火柴就是我4 小时前
每日见闻之THREE.PerspectiveCamera的含义
android
小书房5 小时前
Android的Dalvik和ART
android·aot·jit·art·dalvik
夏日玲子5 小时前
Monkey 测试的基本概念及常用命令(Android )
android
whysqwhw6 小时前
Transcoder代码学习-项目构建
android
夕泠爱吃糖6 小时前
Linux 文件内容的查询与统计
android·linux·c#
yzpyzp6 小时前
Kotlin的MutableList和ArrayList区别
android·kotlin
用户2018792831677 小时前
故事:《安卓公司的消息快递系统》
android