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

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui