Android现代组件通信:以ViewModel为核心的Activity与Fragment交互指南

235 阅读3分钟

一句话总结:

Fragment 和 Activity 的通信就像两个人打电话:Fragment 拨号(定义接口),Activity 接电话(实现接口);而更现代的方式是,Activity 直接喊话(调用 Fragment 方法),或者通过“共享的智能中间人”(ViewModel)来传递和同步消息。


一、范式革命:为什么共享 ViewModel 是现代架构的首选?

在现代 Android 开发中,共享的 ViewModel 不仅仅是一种通信方式,它是官方推荐的、用于解耦 UI 控制器并管理 UI 状态的核心组件。通信是其天然的副产品。

  • 核心思想:Activity 和其内部的多个 Fragment 共享同一个 ViewModel 实例。它们之间不直接对话,而是通过修改和观察 ViewModel 中的数据(通常是 LiveDataStateFlow)来间接通信。

  • 优势

    1. 完全解耦:Fragment 和 Activity 都不需要持有对方的引用。
    2. 生命周期安全ViewModel 能在配置变更(如屏幕旋转)中存活,数据不会丢失。
    3. 数据唯一源:状态数据集中存储在 ViewModel 中,避免了数据不一致的问题。

实现步骤:

  1. 创建共享的 ViewModel

    class SharedViewModel : ViewModel() {
        private val _selectedItem = MutableLiveData<Item>()
        val selectedItem: LiveData<Item> = _selectedItem
    
        fun selectItem(item: Item) {
            _selectedItem.value = item
        }
    }
    
  2. 在 Activity 和 Fragment 中获取 ViewModel 实例:

    关键在于,它们都使用 Activity 作为 ViewModelStoreOwner 来获取 ViewModelProvider,从而确保获取到的是同一个实例。

    // 在 Activity 中
    val viewModel: SharedViewModel by viewModels()
    
    // 在 Fragment 中
    val viewModel: SharedViewModel by activityViewModels() // 使用 KTX 扩展,等同于 ViewModelProvider(requireActivity()).get(...)
    
  3. 通信

    // FragmentA (发送方)
    button.setOnClickListener {
        viewModel.selectItem(someItem)
    }
    
    // Activity 或 FragmentB (接收方)
    viewModel.selectedItem.observe(viewLifecycleOwner) { item ->
        // 当数据变化时,更新UI
        updateUiWith(item)
    }
    

二、事件与结果传递:Fragment Result API

对于一次性的事件或结果传递(例如,一个对话框 Fragment 将用户的“确定/取消”选择结果返回),使用 ViewModelLiveData 会有些笨重。为此,Jetpack 提供了更轻量级的 Fragment Result API

  • 核心思想:它就像一个组件间的“事件总线”,通过一个 requestKey 来发送和接收带 Bundle 数据的事件。

实现步骤:

  1. 接收方 (Activity 或 Fragment) 注册监听器

    // 在 Activity 的 onCreate 或 Fragment 的 onViewCreated 中
    supportFragmentManager.setFragmentResultListener("requestKey", this) { requestKey, bundle ->
        // 当有结果返回时,此回调被触发
        val result = bundle.getString("bundleKey")
        // 处理结果
    }
    
  2. 发送方 (Fragment) 发送结果

    button.setOnClickListener {
        val resultBundle = Bundle().apply {
            putString("bundleKey", "This is a result")
        }
        // 使用相同的 requestKey 发送结果
        setFragmentResult("requestKey", resultBundle)
        // (可选) 关闭当前 Fragment
        parentFragmentManager.popBackStack()
    }
    

结论Fragment Result API 是对传统接口回调模式的现代化、官方级替代方案。


三、传统但仍有效的通信模式

1. 初始化数据传递:BundlenewInstance 工厂方法

这是唯一推荐的向 Fragment 传递初始数据的方式。因为它能保证在 Fragment 被系统销毁并重建时,参数能够被正确恢复。

class DetailFragment : Fragment() {
    companion object {
        fun newInstance(itemId: String): DetailFragment {
            return DetailFragment().apply {
                arguments = Bundle().apply {
                    putString("ITEM_ID", itemId)
                }
            }
        }
    }
}
// Activity 中调用: supportFragmentManager.commit { replace(R.id.container, DetailFragment.newInstance("123")) }

2. 遗留方案:接口回调

虽然样板代码多,但在一些简单或遗留项目中仍然可见。它的主要问题是导致 Fragment 与其宿主(通常是 Activity)的强耦合。


四、选型指南:在何时使用何种通信方式?

通信场景首选方案备选/特定场景方案
共享UI状态、数据流 (如用户登录状态、列表数据)共享 ViewModel-
一次性事件/结果 (如Dialog选择、表单提交)Fragment Result API接口回调 (不推荐)
向 Fragment 传递初始配置数据Bundle + newInstance()-
Fragment 间的兄弟通信共享 ViewModelFragment Result API
对 Fragment 的直接命令式控制 (如 fragment.reset())直接调用方法 (慎用)尽量通过 ViewModel 暴露事件来驱动