今天大概会聊到的范围
@Composable
- compose compiler & runtime
爆个雷:今天的文章只会讲前半段,还不会回答 为什麽 remember 是 composable function?
今天第 15 天,刚好走到铁人赛的一半,也算认真看 Compose 这个主题好一段时间了。但我一直有个疑惑:到底 @Composable
是什麽东西?
这个问题听起来可能很蠢,不就是我们在写 Compose UI 时,那些 UI element 都要加上 Composable ,所以 @Composable
应该是标示某一个 function 将会被视为 View 对吧?
一开始我也是这样想,直到我看到 remember 的定义:
@Composable
inline fun <T> remember(calculation: @DisallowComposableCalls () -> T): T =
currentComposer.cache(false, calculation)
等等!remember
为什麽是一个 composable function? 他不是 UI element 啊?
也许,remember
会操控到 UI 的内容勉强还说得过去。但是另一个例子:当我们需要 dp/sp/px 大小单位转换时,我们需要 LocalDenesity.current
,但 LocalDensity.current
的 getter 长这个样子
inline val current: T
@ReadOnlyComposable
@Composable
get() = currentComposer.consume(this)
为什麽你也是 composable function ???
@Composable
这个 Annotation 做了什麽简单来说,@Compoable
这个 annotation 改变了这个 function 的性质。有一个很好的比喻的就是 suspend function ,加上 suspend
关键字的 function 会有与一般不同的行为,suspend
function 只能在特定的 scope 或是别的 suspend function 中呼叫。@Composable
也是,加上 @Composable
这个 annotation 的 function 会有不同的行为,而且 @Composable
只能在别的 @Composable
function 中呼叫。
@Composable
fun composableFunc()
@Composable
fun anotherCompFunc() {
composableFunc() // ok
}
fun normalFunc(){
composableFunc() // not allow
}
在我们用到 compose 相关的功能时,我们的专案都会执行 compose compiler。compose compiler 基本上是一个 gradle plugin 在 compile time 加入我们专案的建置。这个 plugin 会找到所有有标上
@Composable
annotation 的东西 source 并且检查这些 composable 是否是在别的 composable function 中呼叫的 source 。
@Composalbe
并不会触发 annotation processor,而是可以视之为一个 keyword,就如同上面举例的 suspend 一样。他的目的是让 compiler 知道这个是 composable function,需要特别判断与处理
既然 composable function 一定要在别的 composable function 内才能呼叫,那就会有一个鸡生蛋蛋生鸡的问题:第一个 composable function 要在哪里呼叫?
就和 suspend
function 需要再 coroutine scope 中呼叫一样,composable function 也须要有一个 entry point。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeApp()
}
}
}
ComponentActivity
的 setContent
function 就是一个 entry point。这个 function 本身不是 composable 但却接收了一个 composable function,因此,这个 function 内部并没有直接“呼叫”这个 content ( 这个 composable function ) 而是将它继续往下传递。经过很多层包装後,最终会
- 传递链大概是:
START: ComponentActivity.setContent
=> ComposeView.setContent
=> ViewGroup.setContent
=> WrappedComposition.setContent- 在 ViewGroup.setContent 中,会建立一个
Composition
。并将Composition
和 content 都往下传递WrappedComposition.setContent
会将 content 放进 Composition 中并执行 (所以实际执行是在Composition
)Composition
中会 new 出一个ComposerImpl
(CompserImpl
是Composer
的实体)Composer
中,最後会将composable
传递到invokeCompoable
执行。这个 function 特别将 composable 是为一般 function 执行,成为一般 function 和 composable function 的交界点
internal fun invokeComposable(composer: Composer, composable: @Composable () -> Unit) {
@Suppress("UNCHECKED_CAST")
val realFn = composable as Function2<Composer, Int, Unit>
realFn(composer, 1)
}
以下引用 Leland Richardson 的图。接下来大部分的的解释与理来都来自这个文章:https://medium.com/androiddevelopers/under-the-hood-of-jetpack-compose-part-2-of-2-37b2c20c6cdd
找到 Composable function 的初始执行点了,那这个神秘的 Composer
到底做了什麽呢?在 compose 开始时,可以想像在 compose 启动开始时,Composer
会建立一个很大的空阵列。
当 Composer 走访各个 Composable 时,会一一将各个 Composable 放入这个阵列。并保留阵列尾端的空位
当今天有某个 Composable 触发了 recomposition 时,composer 就会从头走访整个阵列。
Composer 取得每个位置的 composable 时,可以依照资料做决定:有可能决定完全不动(假设第一个方形完全没有改变),有可能决定改变其中的某个参数,但是不影响上下 ( ex. Text ) ( 假设圆形需要改变颜色 )。
当今天某个 recomposition 需要触发 layout 改变增加 child composable 时,composer 会将尾端的空位直接移上来。
从这里开始,重新将新的 composable 加入阵列中存放。
这样存放 composable 的好处,所有的行动(加入 composable、改变 composable 资料、删除 composable )都是固定时间的 (O(1)) 。唯一耗时的是移动空位 (O(n))。但移动空位通常是有重大 UI 改变时才会触发,而且每次触发都是一整个区块在做改变,因此,Compose UI 团队才会做这个设计和选择。
今天的问题还是没有被回答到。"为什麽 remember 是 composable function?"。但在回答这个之前,我们先碰触到了 Compose 整个 framework 运作核心的冰山一角。明天预计顺着这个逻辑,继续说明当今天有 State 在 composable tree 中时,composer 怎麽存放资料,state 又怎麽影响 composable。
Reference:
>>: Day 14 JavaScript innerText vs textContent
Q_Q .. 把 props 传入 styled-components import styled...
昨天介绍了如何抓取 API,今天来介绍如何根据 JSON 写一个 struct。 为了接收 API ...
或许看到本系列文章会产生的第一个疑问大概就是:「为什麽是 AVR?」。的确,现在潮潮都用 x86,不...
好的,连假最後一天,我们来个小篇章,就是Span啦,Span可以做到的事情有很多,如 *更改特定位...
《30天带你上完 Google Data Analytics Certificate 课程》系列将...