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

相关推荐
Kapaseker3 小时前
你不看会后悔的2025年终总结
android·kotlin
alexhilton6 小时前
务实的模块化:连接模块(wiring modules)的妙用
android·kotlin·android jetpack
ji_shuke6 小时前
opencv-mobile 和 ncnn-android 环境配置
android·前端·javascript·人工智能·opencv
sunnyday04268 小时前
Spring Boot 项目中使用 Dynamic Datasource 实现多数据源管理
android·spring boot·后端
幽络源小助理10 小时前
下载安装AndroidStudio配置Gradle运行第一个kotlin程序
android·开发语言·kotlin
inBuilder低代码平台10 小时前
浅谈安卓Webview从初级到高级应用
android·java·webview
豌豆学姐10 小时前
Sora2 短剧视频创作中如何保持人物一致性?角色创建接口教程
android·java·aigc·php·音视频·uniapp
白熊小北极10 小时前
Android Jetpack Compose折叠屏感知与适配
android
HelloBan10 小时前
setHintTextColor不生效
android
洞窝技术12 小时前
从0到30+:智能家居配网协议融合的实战与思考
android