![]()
2024年Android开发者调查里,一个数据挺扎眼:67%的ViewModel实现存在类型安全漏洞,但编译器一声不吭。问题出在领域层和表现层的接缝处——编译期类型信息在这里自然蒸发,像热水倒进雪地。
谷歌工程师Pietro Maggi在RandomPokemon仓库里埋了一套解法,用了三年才被社区挖出来。核心武器是自引用泛型(Self-Referential Generics)配合实化类型参数(Reified Type Parameters),让运行期类型校验和编译期静态检查握手言和。
ResultState的设计:故意把类型信息抹掉
先看领域层的输出结构。这是一个密封类(Sealed Class),三个状态分支:
Running和Error不携带业务数据,Complete用协变泛型把产出类型O包进去。注意out关键字——ResultState是数据的来源方,你只能从中读取O,从不写入。
UseCase的抽象类设计更有意思。in标记表示逆变:UseCase消费O来更新状态,但从不把O交还给调用方。protected fun O?.update()把成功结果塞进_state,而暴露给外界的state属性类型是StateFlow。
这里的类型擦除是设计意图,不是缺陷。O在UseCase内部是具体的,越过边界就变成Any?。ViewModel拿到useCase.state时,编译器已经不知道Complete里装的是什么。
自引用泛型:让子类自己指认自己
BaseViewModel的签名长这样:
abstract class BaseViewModel>
这种"自己引用自己"的写法,在Java时代叫CRTP(Curiously Recurring Template Pattern),Kotlin里换个名字继续用。目的是把运行时才能确定的类型信息,提前锁进编译期的类型系统。
配合实化类型参数(Reified Type Parameters),你可以在inline函数里用T::class.java拿到Class对象,而不是看着泛型被擦成Object。Kotlin的reified关键字本质是编译器帮你把类型信息从调用点抄进方法体,代价是方法必须内联。
Maggi的实现把两者焊在一起:BaseViewModel的子类通过自引用泛型声明"我是什么类型",collectUseCase()用reified inline函数在运行时核对这个声明是否属实。
运行期校验:类型错配当场爆炸
关键代码在collectUseCase扩展函数里。它接收一个UseCase<*>,但要求调用方用泛型指定期望的输出类型Expected。
函数内部做三件事:启动Flow收集、收到Complete时检查actualData is Expected、类型不匹配直接抛IllegalStateException。这套机制把"编译期应该发现但没发现"的错误,推迟到运行期首次触发时引爆,而不是带着脏数据流进生产环境。
对比传统做法:要么用unchecked cast强行压制警告,把风险埋进代码;要么在ViewModel里写一堆when分支手动拆箱,重复劳动还容易漏。Maggi的方案让错误在边界处显形,且只显形一次。
协变和逆变的搭配也有讲究。Complete保证你只读不写,UseCase保证你只写不读,两者通过ResultState?这个"最小公分母"对接。如果你把in和out写反了,编译器会在数据流断裂的地方标红——这比文档管用。
代价:这套把戏不是免费的
inline函数的字节码膨胀是明账。每个调用点都会复制一份collectUseCase的实现,类型检查越复杂,膨胀越严重。RandomPokemon的代码量小,感知不强;大规模项目需要权衡。
更隐蔽的成本在心智负担。团队里得有人真懂reified和自引用泛型的交互,而不是照搬模板。Cargo-culting(盲目模仿)在这里尤其危险——抄了代码没抄懂约束条件,运行期异常照样冒出来,只是换了个姿势。
Maggi在提交注释里写得很克制:"This enforces a single, standardized way to consume use case output." 没有"革命性",没有"最佳实践",就是一个边界处的类型安全补丁。
这套模式能被社区挖出来,某种程度上说明Android架构的痛点足够普遍。Jetpack ViewModel本身不解决领域层到表现层的类型映射问题,把空白留给开发者填。填法有很多种,Maggi选了最保守的一种:承认编译器帮不了,就在运行期加一道安检。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.