前面两节中 Snackbar 和 NavigationDrawer 这两个组件都是通过在原来的 Compose 树中添加/删除来实现的,组件本身会解析成 LayoutNode 添加到 AndroidComposeView 的 root 根节点中。
Compose 中还有两个弹窗效果的组件,Popup 和 Dialog。 他们两个都是基于 AbstractComposeView 将 @Composable content 在另一个 Compose 树中解析显示的,这章我们先来好好了解 AbstractComposeView ,下一章再来研究 Popup 和 Dialog。
AbstractComposeView 作用是在原生 View 中使用 Jetpack Compose UI 的 View 基类。继承 ViewGroup ,其实现子类是原生 View 体系中 Jetpack Compose UI 的持有者,是原生 View 体系和 Compose 之间的桥梁。
其实现子类是原生 View 体系中 Jetpack Compose UI 的持有者的意思是:
-
原生 View 体系中 addView(view :AbstractComposeView)
-
AbstractComposeView 通过 setContent 方法来解析 @Composable content
AbstractComposeView 在 Compose 中有三个实现类,ComposeView 、PopupLayout 和 DialogLayout。
ComposeView 应用在 ComponentActivity#setContent ,后两个分别用来实现 Popup 和 Dialog。
通过 Compose 初始组合流程来了解 AbstractComposeView 是如何将原生 View 与 Compose UI 联系在一起的,以及整个 Compose 环境是如何一步步完善的。
ComponentActivity#setContent
每一次我们新建 Compose 工程,IDE 都会自动在 MainActivity#onCreate 中自动加上下面的代码,然后我们就开始开心的往 setContent 中添加 @Compose fun 的代码了。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ComponentActivity.setContent
setContent {
//实现 Compose UI
}
}
}
ComponentActivity#setContent 源码也很简单
public fun ComponentActivity.setContent(
parent: CompositionContext? = null,
content: @Composable () -> Unit
) {
//val existingComposeView = null
val existingComposeView = window.decorView
.findViewById<ViewGroup>(android.R.id.content)
.getChildAt(0) as? ComposeView
if (existingComposeView != null) with(existingComposeView) {
//existingComposeView = null 不进 if 分支
setParentCompositionContext(parent)
setContent(content)
} else ComposeView(this).apply { //1 创建 ComposeView 对象
setParentCompositionContext(parent) //parent = null
setContent(content)//2 将 @Composable content 保存到 ComposeView 中
setOwners()
//3 setContentView 不在使用 xml 而是添加 ComposeView 对象
//将 ComposeView 作为 ContentView 添加到 Activity 的 Windwow.decorView中
setContentView(this, DefaultActivityContentLayoutParams)
}
}
/**
这个方法单纯就是为了兼容 AppCompat 1.3 以前的版本。确保 Compose 中使用 1,2,3 处时不会报错
*/
private fun ComponentActivity.setOwners() {
val decorView = window.decorView
//Lifecycle 生命周期感知能力
if (ViewTreeLifecycleOwner.get(decorView) == null) {
ViewTreeLifecycleOwner.set(decorView, this)
}
//从 ViewModelStore 缓存/获取 ViewModel
if (ViewTreeViewModelStoreOwner.get(decorView) == null) {
ViewTreeViewModelStoreOwner.set(decorView, this)
}
//使用 SavedStateRegistry 来缓存/获取 Bundle 类型的状态数据
if (ViewTreeSavedStateRegistryOwner.get(decorView) == null) {
ViewTreeSavedStateRegistryOwner.set(decorView, this)
}
}
//Compose View 默认是 WRAP_CONTENT
private val DefaultActivityContentLayoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
方法的作用:
创建 ComposeView 对象,将 @Composable content 的 Compose UI 赋值给 ComposeView 的 content 属性,然后将其作为 ContentView 设置到 Activity 中。
再来看一下 ComposeView 源码
/**
一个可以持有 Jetpack Compose UI content 的 Android 原生 View
* A [android.view.View] that can host Jetpack Compose UI content.
使用 setContent 方法为 View 设置 composable content
* Use [setContent] to supply the content composable function for the view.
*/
class ComposeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {
private val content = mutableStateOf<(@Composable () -> Unit)?>(null)
@Suppress("RedundantVisibilityModifier")
protected override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
private set
@Composable
override fun Content() {
content.value?.invoke()
}
override fun getAccessibilityClassName(): CharSequence {
return javaClass.name
}
fun setContent(content: @Composable () -> Unit) {
shouldCreateCompositionOnAttachedToWindow = true
this.content.value = content
if (isAttachedToWindow) {
createComposition()
}
}
}
ComposeView 的 content 属性的类型是 MutableState<@Composable () -> Unit> ,ComposeView#Content() 方法调用时才会真正执行 @Composable 函数。
ComposeView#setContent() 方法:
-
ComposeView 在 onAttachedToWindow 时创建组合 Composition,
-
将 @Composable content 函数赋值给 MutableState 类型的属性 content,
-
判断 ComposeView 是否已经 AttachedToWindow ,来决定是否创建组合 Composition。
经过 ComponentActivity#setContent() 后 Activity UI 结构如下
ComponentActivity#setContent() 发生在 Activity#onCreate() ,此时并不会执行 createComposition()。
当 Activity 开始测绘流程时 ViewRootImp 会调用 DecorView 的 dispatchAttachedToWindow(),原生 View 树中所有的 View 都会触发 onAttachedToWindow() 回调。
override fun onAttachedToWindow() {
super.onAttachedToWindow()
previousAttachedWindowToken = windowToken
//ComposeView#setContent() 时设置为 true
if (shouldCreateCompositionOnAttachedToWindow) {
ensureCompositionCreated()
}
}
接着 ensureCompositionCreated() 开启初始组合流程
ComposeView#ensureCompositionCreated
@Suppress("DEPRECATION") // Still using ViewGroup.setContent for now
private fun ensureCompositionCreated() {
if (composition == null) { //composition = null
try {
creatingComposition = true
// resolveParentCompositionContext = Recomposer
composition = setContent(resolveParentCompositionContext()) {
Content()
}
} finally {
creatingComposition = false
}
}
}
方法中先判断 composition 是否已经存在,存在就代表已经初始组合过了,不需要再进行组合流程,更新 UI 走重组流程。这就是我们在第一章 Compose 基础概念中说的在 Composeable 的生命周期中会进行 1 次初始组合和 0 到 n 次重组的原因。
刚刚触发 onAttachedToWindow() 回调,显然 composition 依旧是 null ,接下来会执行两个方法调用。
ComposeView#resolveParentCompositionContext
private fun resolveParentCompositionContext() = parentContext//null
?: findViewTreeCompositionContext()?.cacheIfAlive()//null
?: cachedViewTreeCompositionContext?.get()?.takeIf { it.isAlive }//null
?: windowRecomposer.cacheIfAlive() //Recomposer
parentContext 在 ComponentActivity#setContent 时并没有传值,使用的是默认值 null 。
findViewTreeCompositionContext() 和 cachedViewTreeCompositionContext 取的都是创建 parentContext 时的缓存值,此时还没有创建过所以并不存在缓存。创建 parentContext 就只能交给最后一行代码来负责了。
windowRecomposer.cacheIfAlive() 要分成两步来看:
- ComposeView.getWindowRecomposer() : 创建 Recomposer 对象
- Recomposer.cacheIfAlive(): 将 Recomposer 对象的弱引用保存到 ComposeView 的 cachedViewTreeCompositionContext 属性中
- Recomposer 是 CompositionContext 的实现类
- ComposeView 的 parentContext 是 Recomposer 对象
- Recomposer 对象在创建时会保存到 Activity ContentView 中,所以默认情况下一个 Activity 中所有的 ComposeView 都使用同一个 Recomposer 对象作为 parentContext
有了 CompositionContext 这个参数后,就开始调用 AbstractComposeView 拓展 setContent() 方法开始初始组合。
AbstractComposeView#setContent
internal fun AbstractComposeView.setContent(
parent: CompositionContext,
content: @Composable () -> Unit
): Composition {
//GlobalSnapshotManager 还没深入了解
GlobalSnapshotManager.ensureStarted()
//创建 composeView
val composeView =
if (childCount > 0) {
getChildAt(0) as? AndroidComposeView
} else {
removeAllViews(); null
} ?: AndroidComposeView(context).also { addView(it.view, DefaultLayoutParams) }
// 进行初始组合并返回 composition
return doSetContent(composeView, parent, content)
}
这是一个拓展方法,在方法中只是生成 AndroidComposeView 类型的 composeView ,并将其添加到 ComposeView 中。
AndroidComposeView 我们都不陌生了前面分析事件传递、测绘的时候都有提到过。所有的 Compose 组件都会解析成 LayoutNode 保存到 AndroidComposeView root 的根节点中。
doSetContent
private fun doSetContent(
owner: AndroidComposeView,
parent: CompositionContext,
content: @Composable () -> Unit
): Composition {
if (inspectionWanted(owner)) {
owner.setTag(
R.id.inspection_slot_table_set,
Collections.newSetFromMap(WeakHashMap<CompositionData, Boolean>())
)
enableDebugInspectorInfo()
}
//创建 composition ,注意 这里还创建了 UiApplier 对象
val original = Composition(UiApplier(owner.root), parent)
//为 composition 添加生命周期感知能力
val wrapped = owner.view.getTag(R.id.wrapped_composition_tag)
as? WrappedComposition
?: WrappedComposition(owner, original).also {
owner.view.setTag(R.id.wrapped_composition_tag, it)
}
//开启初始组合
wrapped.setContent(content)
return wrapped
}
- 创建 UiApplier 对象
- 创建 CompositionImpl 对象
- 为 CompositionImpl 对象包装生命周期感知能力,并缓存到 AndroidComposeView 中
- 开启初始组合
- 将包装后的 CompositionImpl 对象,返回给 ComposeView#ensureCompositionCreated 方法
wrapped.setContent() 调用流程如下:
总结
从 ComponentActivity#setContent 创建 ComposeView 到 AbstractComposeView#setContent 初始组合结束,这个过程中 ComposeView 中的属性一步步被赋值,Activity UI 也一步步实例化,Compose 运行环境也一步步完善。
过程中涉及到了下面几个重要的类型,抛开与 View 、Compose 函数解析相关的类型 ,CompositionContext(Compose 运行环境)和 Composition (Compose 组合信息)在整个 Compose 中都有着重要的作用。我们后面继续分析