Jetpack Compose中没有Class Component
的概念,所以的组件都是Compose Function
,当state变化时通过重复执行Compose Function
刷新UI。
理想情况下Compose Function
应该以纯函数的形式运行,函数的执行结果只依赖入参。
@Composable
fun MyView(someData: Data) {
...
}
如上,someData可能是存放在上级的某个state,需要所有的子组件参照。
当组件嵌套过深时,各级Compose Function
都要在函数签名中增加此参数;而且当这样的参数多起来后会成为一种灾难。因此Compose提供了Ambient,可以进行实例对象的注入和获取,避免依赖参数传递
通过ambientOf
创建一个Ambient
val exampleViewModelAmbient = ambientOf<ExampleViewModel>()
在父组件中通过Ambient注入实例,子组件通过Ambient获取实例:
val exampleViewModelAmbient = ambientOf<ExampleViewModel>()
@Composable
fun ExampleApp() {
val viewModel = ExampleViewModel()
Providers(exampleViewModelAmbient provides viewModel) {
MyView()
}
}
@Composable
private fun MyView() {
val viewModel = exampleViewModelAmbient.current
...
}
如上,一个基本的注入和使用完成了,代码很简单。
简单了解一下内部实现:
Providers(exampleViewModelAmbient provides viewModel)
其中provides
方法是一个中缀,返回一个ProvideValue
abstract class ProvidableAmbient<T> internal constructor(defaultFactory: (() -> T)?) :
Ambient<T> (defaultFactory) {
infix fun provides(value: T) = ProvidedValue(this, value)
}
ProvideValue
非常简单,其实是一个Ambient和实例的关联组合
class ProvidedValue<T> internal constructor(val ambient: Ambient<T>, val value: T)
Providers也非常简单,三件事:
@Composable
fun Providers(vararg values: ProvidedValue<*>, children: @Composable() () -> Unit) {
currentComposer.startProviders(values)
children()
currentComposer.endProviders()
}
当前栈上有一个AmbientMap
,入栈相当于将ProvideValue
存入Map
出栈相当于从AmbientMap
中删除
val viewModel = exampleViewModelAmbient.current
通过Ambient获取实例,current是个Custom Getter,遍历AmbientMap
上的ProvideValue
,最终获取实例
Ambient本身不持有value,只是作为一个Key来索引Map中真正的实例。相对于普通的Key,Ambient声明时的泛型,可以让他类型安全的获取实例
sealed class Ambient<T> constructor(defaultFactory: (() -> T)? = null) {
@Composable
inline val current: T get() = currentComposer.consume(this)
}
为什么要通过Providers函数实现这样的Stack逻辑呢?,用一个全局Map作为IoC容器不就可以了吗?
这样设计的目的是为了实现分Scope管理
Ambients by their nature are hierarchical. They make sense when the value of the ambient needs to be scoped to a particular sub-hierarchy of the composition.
Composable函数的执行相当于一个树的DFS,每一层都有自己的AmbientMap,管理当前的实例,同一个Ambient在不同Scope可以对应不同实例,如下:
@Composable
fun App() {
val viewModel = AppViewModel()
Providers(exampleViewModelAmbient provides viewModel) {
Parent()
}
}
@Composable
fun Parent() {
Providers(exampleViewModelAmbient provides ParentViewModel()
) {
Child()
}
//获取的是AppViewModel而非ParentViewModel
viewModel = exampleViewModelAmbient.current
}
@Composable
private fun Child() {
// 获取的是ParentViewModel
val viewModel = exampleViewModelAmbient.current
...
}
Providers包裹是必须的,否则汇报运行时异常
@Composable
fun ExampleApp() {
val viewModel = ExampleViewModel()
Providers(exampleViewModelAmbient provides viewModel) {
...
}
// runtime error
MyView()
}
当有个多个Providers
方法时,最近的一个有效,因为当前Scope的AmbientMap
已经被改写
fun ExampleApp() {
val viewModel = ExampleViewModel()
Providers(exampleViewModelAmbient provides viewModel) {
val viewModel2 = ExampleViewModel()
Providers(exampleViewModelAmbient provides viewModel2) {
// 此时获取的是viewModel2
MyView()
}
}
}
Compose的内部使用了很多Ambient子类来传递全局性的环境变量,例如ContextAmbient
、CoroutineContextAmbien
等
参考
https://developer.android.com/reference/kotlin/androidx/compose/Ambient