Compose 中 viewModel() 函数分析

核心问题

在 Fragment 中使用 ComposeView 时,Compose 代码中调用 viewModel() 获取的 ViewModel,其 Owner 是谁?

结论

Owner 就是该 Fragment 本身

详细分析

1. Fragment 自动设置 ViewTreeViewModelStoreOwner

在 AndroidX Fragment 库中,Fragment 会在创建 View 时自动设置:

csharp 复制代码
// Fragment.java (AndroidX Fragment)
void performCreateView(...) {
    mViewLifecycleOwner = new ViewLifecycleOwner(this);
    
    // 关键:设置 ViewTreeViewModelStoreOwner
    ViewTreeLifecycleOwner.set(mView, mViewLifecycleOwner);
    ViewTreeViewModelStoreOwner.set(mView, mViewLifecycleOwner);
    // mViewLifecycleOwner 实际上就是 Fragment 自己
}

这意味着:Fragment 的整个 View hierarchy 都能通过 ViewTree 找到 Fragment。

2. ComposeView 如何找到 ViewModelStoreOwner

AndroidX Compose UI 的实现:

kotlin 复制代码
// AbstractComposeView.kt
public abstract class AbstractComposeView : ViewGroup {
    
    internal val viewModelStoreOwner: ViewModelStoreOwner? by lazy {
        findViewTreeViewModelStoreOwner() // 核心:遍历 View Tree 查找
    }
}

findViewTreeViewModelStoreOwner() 方法的实现原理:

java 复制代码
// ViewTreeViewModelStoreOwner.java
public static ViewModelStoreOwner get(View view) {
    // 向上遍历 View Tree
    ViewParent parent = view.getParent();
    while (parent != null) {
        if (parent instanceof ViewModelStoreOwner) {
            return (ViewModelStoreOwner) parent;
        }
        // 检查 View 的 tag
        if (parent instanceof View) {
            Object tag = ((View) parent).getTag(R.id.view_tree_view_model_store_owner);
            if (tag instanceof ViewModelStoreOwner) {
                return (ViewModelStoreOwner) tag;
            }
        }
        parent = parent.getParent();
    }
    return null;
}

3. ComposeView.setContent() 设置 LocalViewModelStoreOwner

当调用 ComposeView.setContent() 时,内部会:

kotlin 复制代码
public fun setContent(content: @Composable () -> Unit) {
    val owner = findViewTreeViewModelStoreOwner() // 找到 Fragment
    CompositionContext {
        providers(LocalViewModelStoreOwner provides owner) {
            content() // 你的 Compose 代码在这里执行
        }
    }
}

4. viewModel() 如何使用 LocalViewModelStoreOwner

kotlin 复制代码
// Compose ViewModel.kt
@Composable
public inline fun <reified VM : ViewModel> viewModel(
    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
    },
    key: String? = null,
    factory: ViewModelProvider.Factory? = null
): VM = viewModel(VM::class, viewModelStoreOwner, key, factory)

默认参数 LocalViewModelStoreOwner.current 就是之前 setContent() 设置的 Fragment。

完整调用链

markdown 复制代码
1. Fragment.performCreateView()
   ↓
2. Fragment 设置 ViewTreeViewModelStoreOwner = Fragment
   ↓
3. inflater.inflate() 创建 ComposeView
   ↓
4. composeView.setContent { YourScreen() }
   ↓
5. setContent 内部调用 findViewTreeViewModelStoreOwner()
   ↓
6. 找到 Fragment(因为 Fragment 设置了 ViewTree tag)
   ↓
7. 设置 CompositionLocal: LocalViewModelStoreOwner = Fragment
   ↓
8. YourScreen() 执行
   ↓
9. viewModel() 读取 LocalViewModelStoreOwner.current
   ↓
10. 得到 Fragment
   ↓
11. 从 Fragment 的 ViewModelStore 获取 ViewModel

实际应用

在 Fragment 中

kotlin 复制代码
class MyFragment : Fragment() {
    private val myViewModel by viewModels<MyViewModel>()
    
    override fun onCreateView(...): View {
        return ComposeView(requireContext()).apply {
            setContent {
                MyScreen()
            }
        }
    }
}

在 Compose 中

kotlin 复制代码
@Composable
fun MyScreen() {
    val myViewModel: MyViewModel = viewModel()
    // 这里的 myViewModel 和 Fragment 中的 myViewModel 是同一个实例
}

关键要点

  1. Fragment 和 Compose 共享同一个 ViewModel ****实例

    1. Fragment 中的 viewModels()
    2. Compose 中的 viewModel()
    3. 使用相同的 ViewModelStoreOwner(Fragment)
  2. 数据状态保持一致

    1. 在 Fragment 生命周期内,ViewModel 数据不会丢失
    2. 配置更改(如屏幕旋转)时数据自动恢复
  3. CompositionLocal 的自动设置

    1. 无需手动设置 LocalViewModelStoreOwner
    2. AndroidX 框架自动完成所有设置

扩展:如果是 Activity 中的 ComposeView?

同样的原理:

  • Activity 实现了 ViewModelStoreOwner 接口
  • findViewTreeViewModelStoreOwner() 会找到 Activity
  • LocalViewModelStoreOwner.current = Activity

参考文档

相关推荐
BoomHe7 小时前
Android Framework 文件应用移植到 AndroidStudio
android
传奇开心果编程9 小时前
【Jetpack Compose基础语法学与练】第8课 rememberSaveable,页面旋转/系统重建保留状态
android·学习·ui·kotlin·android jetpack
>Andre<9 小时前
UFS5.0标准中文全译·卷一:范围、术语与架构
android·linux·嵌入式硬件
事圆则缓10 小时前
Java 8 Lambda、Stream、Optional 与 Android 边界:从回调语法到运行时兼容
android·java
mmsx10 小时前
Android 地图十万要素不卡顿:空间网格 + 渐进式加载的移动端实践
android·大数据·opengl
Godikov12 小时前
Android 工业终端保活实战:前台服务 + 开机自启 + 更新自启的三重保障
android
字节暗面12 小时前
SO加固强度怎么量化?腾讯ACE与FairGuard静态分析实测
android·逆向
终端安全笔记13 小时前
描述文件、企业证书、MDM 不是三个名字,是三层
android·ios·智能手机
字节暗面14 小时前
一次游戏安全SO静态分析记录:腾讯ACE与FairGuard加固强度对比
android·逆向工程·游戏加固
传奇开心果编程14 小时前
【Jetpack Compose基础语法学与练】第9课 SideEffect副作用 + LaunchedEffect,处理异步任务和生命周期
android·学习·ui·kotlin·android jetpack