要在可组合函数之间共享数据时,可以通过参数传递显式地调用,这通常是最简单和最好的方式。
但随着参数越来越多,组件也越来越多,并且有些数据还需要保持私有性,这时这种方式就会显得很繁琐臃肿,难以维护。 对于这些情况,Compose 提供了CompositionLocals来作为一种隐式方式在组合函数之间传递数据。说白了就是在 Composable 树中提供的一种共享数据的方式(例如主题配置)。
MaterialTheme 是如何实现的

需要注意的是,此时传入的 content 参数其实是声明在 Theme 中的自定义布局系统,其类型是一个带有 Composable 注解的 lambda 。
我们所关注的 colors 被 remember 修饰后赋值为 rememberedColors。如果 MaterialTheme 这个 Composable 发生 recompose 时便会检查 colors 是否发生了改变从而决定更新。
接下来使用 CompositionLocalProvider 方法,通过中缀表达式 providers 将 rememberedColors 提供给了 LocalColors。让我们回到自己的 Composable 中,看看我们是如何通过 MaterialTheme 获取到当前主题配色的。
@Composable
fun MyCard(text: String) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
) {
Text(text = text, color = MaterialTheme.colors.primary)
}
}
这里使用的 MaterialTheme.colors 实际上被定义在 MaterialTheme object 单例对象中:
object MaterialTheme {
val colors: Colors
@Composable
@ReadOnlyComposable
get() = LocalColors.current
val typography: Typography
@Composable
@ReadOnlyComposable
get() = LocalTypography.current
val shapes: Shapes
@Composable
@ReadOnlyComposable
get() = LocalShapes.current
}
MaterialTheme 类单例的 colors 属性,间接使用了 LocalColors
总的来说,我们在自定义 Theme 使用的是 MaterialTheme 函数为 LocalColors 赋值,而在获取时使用的是 MaterialTheme 类单例,间接从 LocalColors 中获取到值。那 LocalColors 又是什么呢?
internal val LocalColors = staticCompositionLocalOf { lightColors() }
实际上它是一个 CompositionLocal,其初始值是 lightColors() 返回的 colors 配置。
MaterialTheme 方法中通过 CompositionLocalProvider 方法为我们的自定义视图 Composable 提供了一些 CompositionLocal,包含了所有的主题配置信息。
CompositionLocal 介绍
CompositionLocals本质上是分层的。当CompositionLocal的值需要被限定于组合的特定子层次结构时,它们是有意义的。
CompositionLocals 可以被限定在以某个 Composable 作为根结点的子树中,其默认会向下传递的,当然当前子树中的某个 Composable 可以对该 CompositionLocals 进行覆盖,从而使得新值会在这个 Composable 中继续向下传递。
总的来说,CompositionLocal 它有以下特性:
- 具备函数穿透功能的局部变量,不需要显示的传递的函数参数,
- 多用于提供:上下文/主题 等,方便透传
- 它的着重点在于提供某种上下文,如果是某个函数需要某个参数多数情况应该使用函数参数直接传
要使用 CompositionLocal 必须创建一个 CompositionLocal 实例,消费者可以静态地引用该实例。CompositionLocal 实例本身不保存任何数据,可以将其视为在组合树中向下传递的数据的类型安全标识符。
要创建一个 CompositionLocal 的实例通过调用 compositionLocalOf 方法来实现:
import androidx.compose.runtime.compositionLocalOf
var LocalString = compositionLocalOf { "Jetpack Compose" }
compositionLocalOf 后面的 lambda 中返回的是一个默认值,这个 lambda 其实是一个工厂函数,其中还可以配置没有提供值的情况下的警告信息,以提醒使用者:
val LocalBackground = compositionLocalOf<Color> { error("LocalBackground没有提供值") }
compositionLocalOf的返回值是一个 ProvidableCompositionLocal 对象(它继承了 CompositionLocal 类)。
然后,在 Composable 树的某个地方,我们可以使用 CompositionLocalProvider 方法为 CompositionLocal 提供一个值。通常情况下位于 Composable 树的根部,但也可以位于任何位置,还可以在多个位置使用,以覆盖子树能够获取到的值。
val LocalUserName = compositionLocalOf<String> { "" }
class CompositionLocalProviderActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CompositionLocalProvider(LocalUserName provides "Jetpack Compose") {
User()
}
}
}
}
@Composable
fun User() {
Column {
Text(text = LocalUserName.current) // 通过current当前用户名
}
}
比如可以通过这种方式来为 Composable 提供当前 Activity 的实例:
val LocalActivity = compositionLocalOf<Activity> { error("LocalActivity没有提供值") }
class CompositionLocalProviderActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CompositionLocalProvider(LocalActivity provides this) {
User() // 范围内都可以使用LocalActivity变量访问当前Activity对象
}
}
}
}
其中 provides 是 ProvidableCompositionLocal 抽象类中定义的一个 infix 中缀表达式:
abstract class ProvidableCompositionLocal<T> internal constructor(defaultFactory: () -> T) : CompositionLocal<T> (defaultFactory) {
infix fun provides(value: T) = ProvidedValue(this, value, true)
}
以下示例是在任意子 Composable 中嵌套使用 CompositionLocalProvider:
// Top Level
val LocalBackground = compositionLocalOf { Color.Magenta }
class CompositionLocalProviderActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column {
SomethingtWithBackground() // 默认背景色
CompositionLocalProvider(LocalBackground provides Color.Red ) {
SomethingtWithBackground() // 红色
CompositionLocalProvider(LocalBackground provides Color.Green) {
SomethingtWithBackground() // 绿色
CompositionLocalProvider(LocalBackground provides Color.Blue) {
SomethingtWithBackground() // 蓝色
}
SomethingtWithBackground() // 绿色
}
SomethingtWithBackground() // 红色
}
SomethingtWithBackground() // 默认背景色
}
}
}
}
@Composable
fun SomethingtWithBackground() {
Box(
modifier = Modifier
.fillMaxWidth()
.background(LocalBackground.current)
.padding(20.dp)
) {
Text(text = "我是有背景的人!", color = Color.White, fontSize = 22.sp)
}
}

可以看到在不同嵌套层级范围内相同的 CompositionLocal 获取到的值可以不一样,也就是说可以在某个子树范围中被覆盖,但是出了这个子树范围后,还是原来的值。
注意,CompositionLocalProvider 中的第一个参数是一个可变参数,也就是可以提供多个值,例如:
// Top Level
val LocalUserName = compositionLocalOf { "张三" }
Column {
SomethingtWithBackground()
CompositionLocalProvider(
LocalBackground provides Color.Red,
LocalUserName provides "李四"
) {
SomethingtWithBackground()
CompositionLocalProvider(LocalBackground provides Color.Green) {
SomethingtWithBackground()
CompositionLocalProvider(
LocalBackground provides Color.Blue,
LocalUserName provides "小明"
) {
SomethingtWithBackground()
}
SomethingtWithBackground()
}
SomethingtWithBackground()
}
SomethingtWithBackground()
}
@Composable
fun SomethingtWithBackground() {
Box(
modifier = Modifier
.fillMaxWidth()
.background(LocalBackground.current)
.padding(20.dp)
) {
Text(text = LocalUserName.current, color = Color.White, fontSize = 22.sp)
}
}

compositionLocalOf 与 staticCompositionLocalOf 区别
当需要创建 CompositionLocal 时,除了可以使用 compositionLocalOf 方法,在 Compose 中还有一个 staticCompositionLocalOf 方法,那么这两者有什么区别呢?
-
当我们选择使用
staticCompositionLocalOf时,实际上创建了个StaticProvidableCompositionLocal实例,与compositionLocalOf不同,编译器不会跟踪staticCompositionLocalOf的读取,一旦它的值改变时,它所提供范围内的所有内容都会重组,而不仅仅是组合中使用局部值的地方。也就是说它不会进行智能重组,每次改变值时都是强制所有人重组。 -
如果我们选择使用
compositionLocalOf,实际上创建了个DynamicProvidableCompositionLocal实例,当其所提供的值改变时,仅会导致它所提供范围内的依赖当前CompositionLocal的那个 Composable 触发重组。
下面是一个分别使用 staticCompositionLocalOf 与 compositionLocalOf 创建 CompositionLocal 的对照效果示例:
val LocalCounter = compositionLocalOf { 0 }
val LocalCounterStatic = staticCompositionLocalOf { 0 }
@Composable
fun CompositionLocalExample() {
var counter by remember { mutableStateOf(0) }
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = { counter++ }) {
Text(text = "change LocalCounter")
}
CompositionLocalProvider(LocalCounter provides counter) {
ThreeBox("CompositionLocal") {
Text("counter: ${LocalCounter.current}", color = Color.White,
modifier = Modifier.background(getRandomColor()).padding(5.dp))
}
}
Spacer(Modifier.height(10.dp))
CompositionLocalProvider(LocalCounterStatic provides counter) {
ThreeBox("StaticCompositionLocal") {
Text("counter: ${LocalCounterStatic.current}", color = Color.White,
modifier = Modifier.background(getRandomColor()).padding(5.dp))
}
}
}
}
@Composable
fun ThreeBox(type: String, content: @Composable () -> Unit) {
Box(
modifier = Modifier
.size(300.dp)
.background(getRandomColor())
) {
Box(modifier = Modifier
.padding(50.dp)
.fillMaxSize()
.background(getRandomColor())
) {
Box(modifier = Modifier
.padding(50.dp)
.fillMaxSize()
.background(getRandomColor()),
contentAlignment = Alignment.Center
) {
content()
}
}
Text(type, color = Color.White, fontSize = 18.sp,
modifier = Modifier.padding(top=15.dp).align(Alignment.TopCenter))
}
}
运行效果:

在上面代码中,三个 Box 及最里面的 Text 组件都设置了随机背景色,这样一旦它们发生重组,我们就能观察到。可以看到,点击修改 counter 状态值时,staticCompositionLocalOf 与 compositionLocalOf 创建的本地共享变量有着明显不同的表现:前者强制其范围内的所有组件重组(不管是否从CompositionLocal中读取值),后者仅重组了从其读取值的组件。
Composable 中的常见 CompositionLocals 创建流程分析
通过前面部分的对比示例可见,使用 staticCompositionLocalOf 主要目的是为了某种全局性的配置,例如开头分析的 MaterialTheme 中的主题颜色都是通过 staticCompositionLocalOf 实现的,因为这些主题是整个应用中共有的属性,需要做到一改全改的效果。此外,我们常用的 LocalContext 等带Localxxx前缀的实现方式都是 staticCompositionLocalOf。


当我们在 Activity、Fragment 或 ComposeView 调用 setContent 的时候,会创建 Composition (组合)对象,在创建该对象时会先创建一个 AndroidComposeView 来作为 LayoutNode 组合树的跟节点的 Owner,然后这个 AndroidComposeView 被附加到 Android 的 View 视图层次结构中,以便可以根据需要执行 invalidate 操作(作为与Compose 视图树的集成点)。

然后会创建一个 WrappedComposition 对象,WrappedComposition 是一个装饰器,它知道如何将 Composition 链接到一个 AndroidComposeView,以便将其直接连接到 Android View 系统。它启动受控效果来跟踪诸如键盘可见性更改或 accessibility 之类的内容,并将关于 Android Context 的信息以 CompositionLocals 的形式传输暴露给 Composition 。

WrappedComposition 设置这些 CompositionLocals 具体是通过调用 ProvideAndroidCompositionLocals 方法:

然后在 ProvideAndroidCompositionLocals 方法中,我们就能够看到例如:Context本身、配置、当前LifecycleOwner、当前savedStateRegistryOwner或Owner的View等等,这些是如何被 CompositionLocalProvider 提供给 LocalXXX类的:
@Composable
@OptIn(ExperimentalComposeUiApi::class)
internal fun ProvideAndroidCompositionLocals(owner: AndroidComposeView, content: @Composable () -> Unit) {
val view = owner
val context = view.context
var configuration by remember {
mutableStateOf(
context.resources.configuration,
neverEqualPolicy()
)
}
owner.configurationChangeObserver = { configuration = it }
val uriHandler = remember { AndroidUriHandler(context) }
val viewTreeOwners = owner.viewTreeOwners ?: throw IllegalStateException(
"Called when the ViewTreeOwnersAvailability is not yet in Available state"
)
val saveableStateRegistry = remember {
DisposableSaveableStateRegistry(view, viewTreeOwners.savedStateRegistryOwner)
}
DisposableEffect(Unit) {
onDispose {
saveableStateRegistry.dispose()
}
}
val imageVectorCache = obtainImageVectorCache(context, configuration)
CompositionLocalProvider(
LocalConfiguration provides configuration,
LocalContext provides context,
LocalLifecycleOwner provides viewTreeOwners.lifecycleOwner,
LocalSavedStateRegistryOwner provides viewTreeOwners.savedStateRegistryOwner,
LocalSaveableStateRegistry provides saveableStateRegistry,
LocalView provides owner.view,
LocalImageVectorCache provides imageVectorCache
) {
ProvideCommonCompositionLocals(
owner = owner,
uriHandler = uriHandler,
content = content
)
}
}
这就是为何这些内容在我们所有的 Composable 函数当中是隐式可用的。

CompositionLocal 的替代方案
某些场景下,CompositionLocal 可能不合适,甚至过度使用。此时可采取如下方案:
-
显式参数:在极简单逻辑情况,应尽量使用显示参数传递,且只传递有效参数,避免造成参数过多。
-
控制反转:另一种避免参数过多或无效参数的方法就是控制反转。一些逻辑可以不在子级页面进行,而应该转移到父级页面来进行。
例如下面的例子中,在子级页面使用了
viewModel调用loadData
@Composable
fun MyComposable(myViewModel: MyViewModel = viewModel()) {
// ...
MyDescendant(myViewModel)
}
@Composable
fun MyDescendant(myViewModel: MyViewModel) {
Button(onClick = { myViewModel.loadData() }) {
Text("Load data")
}
}
MyDescendant 可能需要承担很多逻辑,将 MyViewModel 作为参数传递可能会降低 MyDescendant 的可重用性,因此可以考虑控制反转来优化这个代码:
@Composable
fun MyComposable(myViewModel: MyViewModel = viewModel()) {
// ...
ReusableLoadDataButton(
onLoadClick = {
myViewModel.loadData()
}
)
}
@Composable
fun ReusableLoadDataButton(onLoadClick: () -> Unit) {
Button(onClick = onLoadClick) {
Text("Load data")
}
}
在某些场景下控制反转可以将子级脱离出来,达到高度复用,可以更灵活。同样,可以用 lambda 表达式来实现:
@Composable
fun MyComposable(myViewModel: MyViewModel = viewModel()) {
// ...
ReusablePartOfTheScreen(
content = {
Button(
onClick = {
myViewModel.loadData()
}
) {
Text("Confirm")
}
}
)
}
@Composable
fun ReusablePartOfTheScreen(content: @Composable () -> Unit) {
Column {
// ...
content()
}
}



















