Compose原理简易实现

本文仅探究Google Compose原理 不包含ui绘制实现

Compose原理

先看as compose项目模板生成的基础demo

kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MyApplicationTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
        
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    var currentTime by remember { mutableStateOf(System.currentTimeMillis()) }
    LaunchedEffect(Unit) {
        while (true) {
            currentTime = System.currentTimeMillis()
            delay(1000)
            Log.e("zjw", currentTime.hashCode().toString())
        }
    }
    val timeFormat = remember { SimpleDateFormat("HH:mm:ss", Locale.getDefault()) }
    val timeStr = timeFormat.format(Date(currentTime))

    Text(
        text = "Hello $name! Current Time: $timeStr",
        modifier = modifier
    )
}

很简单的代码不做细究,要探究Compose重组UI逻辑,先上反编译手段看看@Composable Greeting会变成什么 jadx工具反编译MainActivity

setContentView设置了一个ComposeView 这就是Compose接入到android View系统的桥梁了,同时Compose接入

这里省略部分调用细节,简单来说就是ComponentActivity setContent不光给android.R.id.content添加了子ComposeView还添加一个Choreographer.FrameCallback和recompose机制绑定

注意到有一个lambda ComponentActivityKt.setContent$default(this, null, ComposableSingletons$MainActivityKt.INSTANCE.m8049getLambda3$app_debug

jadx双击跳转到这个lambda方法所在源码继续看ComposableSingletons$MainActivityKt

php 复制代码
public final class ComposableSingletons$MainActivityKt {
    public static final ComposableSingletons$MainActivityKt INSTANCE = new ComposableSingletons$MainActivityKt();

    /* renamed from: lambda-1  reason: not valid java name */
    public static Function3<PaddingValues, Composer, Integer, Unit> f79lambda1 = ComposableLambdaKt.composableLambdaInstance(1855199361, false, new Function3<PaddingValues, Composer, Integer, Unit>() { // from class: com.zjw.compose.myapplication.ComposableSingletons$MainActivityKt$lambda-1$1
        @Override // kotlin.jvm.functions.Function3
        public /* bridge */ /* synthetic */ Unit invoke(PaddingValues paddingValues, Composer composer, Integer num) {
            invoke(paddingValues, composer, num.intValue());
            return Unit.INSTANCE;
        }

        public final void invoke(PaddingValues innerPadding, Composer $composer, int $changed) {
            Intrinsics.checkNotNullParameter(innerPadding, "innerPadding");
            ComposerKt.sourceInformation($composer, "C42@1665L139:MainActivity.kt#72esb0");
            int $dirty = $changed;
            if (($changed & 6) == 0) {
                $dirty |= $composer.changed(innerPadding) ? 4 : 2;
            }
            if (($dirty & 19) != 18 || !$composer.getSkipping()) {
                if (ComposerKt.isTraceInProgress()) {
                    ComposerKt.traceEventStart(1855199361, $dirty, -1, "com.zjw.compose.myapplication.ComposableSingletons$MainActivityKt.lambda-1.<anonymous> (MainActivity.kt:42)");
                }
                MainActivityKt.Greeting("Android", PaddingKt.padding(Modifier.INSTANCE, innerPadding), $composer, 6, 0);
                if (ComposerKt.isTraceInProgress()) {
                    ComposerKt.traceEventEnd();
                    return;
                }
                return;
            }
            $composer.skipToGroupEnd();
        }
    });

    /* renamed from: lambda-2  reason: not valid java name */
    public static Function2<Composer, Integer, Unit> f80lambda2 = ComposableLambdaKt.composableLambdaInstance(184410672, false, new Function2<Composer, Integer, Unit>() { // from class: com.zjw.compose.myapplication.ComposableSingletons$MainActivityKt$lambda-2$1
        @Override // kotlin.jvm.functions.Function2
        public /* bridge */ /* synthetic */ Unit invoke(Composer composer, Integer num) {
            invoke(composer, num.intValue());
            return Unit.INSTANCE;
        }

        public final void invoke(Composer $composer, int $changed) {
            ComposerKt.sourceInformation($composer, "C41@1583L239:MainActivity.kt#72esb0");
            if (($changed & 3) != 2 || !$composer.getSkipping()) {
                if (ComposerKt.isTraceInProgress()) {
                    ComposerKt.traceEventStart(184410672, $changed, -1, "com.zjw.compose.myapplication.ComposableSingletons$MainActivityKt.lambda-2.<anonymous> (MainActivity.kt:41)");
                }
                ScaffoldKt.m2866ScaffoldTvnljyQ(SizeKt.fillMaxSize$default(Modifier.INSTANCE, 0.0f, 1, null), null, null, null, null, 0, 0L, 0L, null, ComposableSingletons$MainActivityKt.INSTANCE.m8047getLambda1$app_debug(), $composer, 805306374, 510);
                if (ComposerKt.isTraceInProgress()) {
                    ComposerKt.traceEventEnd();
                    return;
                }
                return;
            }
            $composer.skipToGroupEnd();
        }
    });

    /* renamed from: lambda-3  reason: not valid java name */
    public static Function2<Composer, Integer, Unit> f81lambda3 = ComposableLambdaKt.composableLambdaInstance(749891428, false, new Function2<Composer, Integer, Unit>() { // from class: com.zjw.compose.myapplication.ComposableSingletons$MainActivityKt$lambda-3$1
        @Override // kotlin.jvm.functions.Function2
        public /* bridge */ /* synthetic */ Unit invoke(Composer composer, Integer num) {
            invoke(composer, num.intValue());
            return Unit.INSTANCE;
        }

        public final void invoke(Composer $composer, int $changed) {
            ComposerKt.sourceInformation($composer, "C40@1546L290:MainActivity.kt#72esb0");
            if (($changed & 3) != 2 || !$composer.getSkipping()) {
                if (ComposerKt.isTraceInProgress()) {
                    ComposerKt.traceEventStart(749891428, $changed, -1, "com.zjw.compose.myapplication.ComposableSingletons$MainActivityKt.lambda-3.<anonymous> (MainActivity.kt:40)");
                }
                ThemeKt.MyApplicationTheme(false, false, ComposableSingletons$MainActivityKt.INSTANCE.m8048getLambda2$app_debug(), $composer, 384, 3);
                if (ComposerKt.isTraceInProgress()) {
                    ComposerKt.traceEventEnd();
                    return;
                }
                return;
            }
            $composer.skipToGroupEnd();
        }
    });

    /* renamed from: lambda-4  reason: not valid java name */
    public static Function2<Composer, Integer, Unit> f82lambda4 = ComposableLambdaKt.composableLambdaInstance(1718125071, false, new Function2<Composer, Integer, Unit>() { // from class: com.zjw.compose.myapplication.ComposableSingletons$MainActivityKt$lambda-4$1
        @Override // kotlin.jvm.functions.Function2
        public /* bridge */ /* synthetic */ Unit invoke(Composer composer, Integer num) {
            invoke(composer, num.intValue());
            return Unit.INSTANCE;
        }

        public final void invoke(Composer $composer, int $changed) {
            ComposerKt.sourceInformation($composer, "C132@3832L19:MainActivity.kt#72esb0");
            if (($changed & 3) == 2 && $composer.getSkipping()) {
                $composer.skipToGroupEnd();
                return;
            }
            if (ComposerKt.isTraceInProgress()) {
                ComposerKt.traceEventStart(1718125071, $changed, -1, "com.zjw.compose.myapplication.ComposableSingletons$MainActivityKt.lambda-4.<anonymous> (MainActivity.kt:132)");
            }
            MainActivityKt.Greeting("Android", null, $composer, 6, 2);
            if (ComposerKt.isTraceInProgress()) {
                ComposerKt.traceEventEnd();
            }
        }
    });

    /* renamed from: getLambda-1$app_debug  reason: not valid java name */
    public final Function3<PaddingValues, Composer, Integer, Unit> m8047getLambda1$app_debug() {
        return f79lambda1;
    }

    /* renamed from: getLambda-2$app_debug  reason: not valid java name */
    public final Function2<Composer, Integer, Unit> m8048getLambda2$app_debug() {
        return f80lambda2;
    }

    /* renamed from: getLambda-3$app_debug  reason: not valid java name */
    public final Function2<Composer, Integer, Unit> m8049getLambda3$app_debug() {
        return f81lambda3;
    }
}

其实也就是ComposableSingletons$MainActivityKt这个类下面几个Function2 xxxlambda调用,先说结论每个@Composeable方法编译后对应一个Function2<Composer, Integer, Unit>类型xxxlambda,

MainActivity 调用setContent的lambda函数 反编译ComposableSingletons <math xmlns="http://www.w3.org/1998/Math/MathML"> M a i n A c t i v i t y K t . I N S T A N C E . m 8049 g e t L a m b d a 3 MainActivityKt.INSTANCE.m8049getLambda3 </math>MainActivityKt.INSTANCE.m8049getLambda3app_debug 对应ComposableSingletons <math xmlns="http://www.w3.org/1998/Math/MathML"> M a i n A c t i v i t y K t 类 81 l a m b d a 3 − > 调用 i n v o k e 方法中的 C o m p o s a b l e S i n g l e t o n s MainActivityKt类 81lambda3 -> 调用invoke方法中的 ComposableSingletons </math>MainActivityKt类81lambda3−>调用invoke方法中的ComposableSingletonsMainActivityKt.INSTANCE.m8048getLambda2$app_debug()对应 f80lambda2调用invoke 不再继续

其实对应就是compose的嵌套调用过程

ini 复制代码
setContent {  //f81lambda3
    MyApplicationTheme { //f80lambda2
        Scaffold(modifier = Modifier.fillMaxSize()) {//f79lambda1   innerPadding ->
            Greeting(
                name = "Android",
                modifier = Modifier.padding(innerPadding)
            )
        }
    }
}

接下来来看一下Greeting方法会变成什么样

kotlin 复制代码
public final class MainActivityKt {
    /* JADX INFO: Access modifiers changed from: private */
    public static final Unit Greeting$lambda$5(String str, Modifier modifier, int i, int i2, Composer composer, int i3) {
        Greeting(str, modifier, composer, RecomposeScopeImplKt.updateChangedFlags(i | 1), i2);
        return Unit.INSTANCE;
    }

    /* JADX INFO: Access modifiers changed from: private */
    public static final Unit GreetingPreview$lambda$6(int i, Composer composer, int i2) {
        GreetingPreview(composer, RecomposeScopeImplKt.updateChangedFlags(i | 1));
        return Unit.INSTANCE;
    }


    public static final void Greeting(final String name, Modifier modifier, Composer $composer, final int $changed, final int i) {
        Object obj;
        int i2;
        int $dirty;
        final Modifier modifier2;
        Intrinsics.checkNotNullParameter(name, "name");
        Composer $composer2 = $composer.startRestartGroup(-1745234003);
        ComposerKt.sourceInformation($composer2, "C(Greeting)P(1)103@3008L55,104@3089L117,104@3068L138,110@3228L62,113@3351L93,119@3475L50,124@3688L11,118@3450L277:MainActivity.kt#72esb0");
        int $dirty2 = $changed;
        if ((i & 1) != 0) {
            $dirty2 |= 6;
        } else if (($changed & 6) == 0) {
            $dirty2 |= $composer2.changed(name) ? 4 : 2;
        }
        int i3 = i & 2;
        if (i3 != 0) {
            $dirty2 |= 48;
            obj = modifier;
        } else if (($changed & 48) == 0) {
            obj = modifier;
            $dirty2 |= $composer2.changed(obj) ? 32 : 16;
        } else {
            obj = modifier;
        }
        if (($dirty2 & 19) != 18 || !$composer2.getSkipping()) {
            Modifier.Companion modifier3 = i3 != 0 ? Modifier.INSTANCE : obj;
            if (ComposerKt.isTraceInProgress()) {
                ComposerKt.traceEventStart(-1745234003, $dirty2, -1, "com.zjw.compose.myapplication.Greeting (MainActivity.kt:102)");
            }
            $composer2.startReplaceGroup(293979466);
            ComposerKt.sourceInformation($composer2, "CC(remember):MainActivity.kt#9igjgp");
            Object rememberedValue = $composer2.rememberedValue();
            if (rememberedValue == Composer.INSTANCE.getEmpty()) {
                i2 = 0;
                Object mutableStateOf$default = SnapshotStateKt.mutableStateOf$default(Long.valueOf(System.currentTimeMillis()), null, 2, null);
                $composer2.updateRememberedValue(mutableStateOf$default);
                rememberedValue = mutableStateOf$default;
            } else {
                i2 = 0;
            }
            MutableState currentTime$delegate = (MutableState) rememberedValue;
            $composer2.endReplaceGroup();
            Unit unit = Unit.INSTANCE;
            $composer2.startReplaceGroup(293982120);
            ComposerKt.sourceInformation($composer2, "CC(remember):MainActivity.kt#9igjgp");
            Object rememberedValue2 = $composer2.rememberedValue();
            if (rememberedValue2 == Composer.INSTANCE.getEmpty()) {
                $dirty = $dirty2;
                Object obj2 = (Function2) new MainActivityKt$Greeting$1$1(currentTime$delegate, null);
                $composer2.updateRememberedValue(obj2);
                rememberedValue2 = obj2;
            } else {
                $dirty = $dirty2;
            }
            $composer2.endReplaceGroup();
            EffectsKt.LaunchedEffect(unit, (Function2) rememberedValue2, $composer2, 6);
            $composer2.startReplaceGroup(293986513);
            ComposerKt.sourceInformation($composer2, "CC(remember):MainActivity.kt#9igjgp");
            Object rememberedValue3 = $composer2.rememberedValue();
            if (rememberedValue3 == Composer.INSTANCE.getEmpty()) {
                Object simpleDateFormat = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
                $composer2.updateRememberedValue(simpleDateFormat);
                rememberedValue3 = simpleDateFormat;
            }
            SimpleDateFormat timeFormat = (SimpleDateFormat) rememberedValue3;
            $composer2.endReplaceGroup();
            String timeStr = timeFormat.format(new Date(Greeting$lambda$1(currentTime$delegate)));
            TextKt.m3151Text4IGK_g("Hello " + name + "! Current Time: " + timeStr, modifier3, 0L, 0L, (FontStyle) null, (FontWeight) null, (FontFamily) null, 0L, (TextDecoration) null, (TextAlign) null, 0L, 0, false, 0, 0, (Function1<? super TextLayoutResult, Unit>) null, (TextStyle) null, $composer2, $dirty & 112, 0, 131068);
            modifier2 = modifier3;
            $composer2 = $composer2;
           
        ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();
        if (endRestartGroup != null) {
            endRestartGroup.updateScope(new Function2() { // from class: com.zjw.compose.myapplication.MainActivityKt$$ExternalSyntheticLambda1
                @Override // kotlin.jvm.functions.Function2
                public final Object invoke(Object obj3, Object obj4) {
                    Unit Greeting$lambda$5;
                    Greeting$lambda$5 = MainActivityKt.Greeting$lambda$5(name, modifier2, $changed, i, (Composer) obj3, ((Integer) obj4).intValue());
                    return Greeting$lambda$5;
                }
            });
        }
    }

public static final void Greeting(final String name, Modifier modifier, Composer $composer, final int $changed, final int i) {

其实大概意思就是 通过入参 <math xmlns="http://www.w3.org/1998/Math/MathML"> c h a n g e d 与位运算结合 changed与位运算 结合 </math>changed与位运算结合composer2.changed(obj)判断是否dirty ,然后

ruby 复制代码
if(dirty){
     //调用该composable lambda中invoke 逻辑
     TextKt.m3151Text4IGK_g("Hello " + name + "! Current Time: " ...
}else{
     $composer2.skipToGroupEnd()
}

这里 $composer2就是 Composer类型,它有个changed方法负责判断composeable方法入参是否发生变化,如果发生变化就走composeable invoke逻辑发生重组或者说重绘,否则就skipToGroupEnd 不发生重组,这也是compose保证高效的原理之一 参数不发生变化不进行重组

以demo中 text文本发生变化来具体探究compose提供的基础组件 Text是怎么重组的

ini 复制代码
 Text(
        text = "Hello $name! Current Time: $timeStr",
        modifier = modifier
    )

//Text 这里被翻译成 TextKt.m3151Text4IGK_g("Hello " + name + "! Current Time: " ...

kotlin 复制代码
/* compiled from: Text.kt */

package androidx.compose.material3;

    /* renamed from: Text--4IGK_g  reason: not valid java name */
    public static final void m3151Text4IGK_g(final String text, Modifier modifier, long color, long fontSize, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, long letterSpacing, TextDecoration textDecoration, TextAlign textAlign, long lineHeight, int overflow, boolean softWrap, int maxLines, int minLines, Function1<? super TextLayoutResult, Unit> function1, TextStyle style, Composer $composer, final int $changed, final int $changed1, final int i) {
        Object obj;
        Modifier modifier2;
        int i2;
        long color2;
        long fontSize2;
        Object fontStyle2;
        Object fontWeight2;
        int $dirty;
        int $dirty1;
        int i3;
        int i4;
        int $dirty12;
        int i5;
        boolean softWrap2;
        int $dirty13;
        TextAlign textAlign2;
        int overflow2;
        int maxLines2;
        int minLines2;
        Function1 onTextLayout;
        FontFamily fontFamily2;
        TextDecoration textDecoration2;
        TextStyle style2;
        FontStyle fontStyle3;
        long fontSize3;
        FontWeight fontWeight3;
        long letterSpacing2;
        long lineHeight2;
        int $dirty2;
        long textColor;
        Composer $composer2;
        final boolean softWrap3;
        final long color3;
        final Modifier modifier3;
        final TextAlign textAlign3;
        final int maxLines3;
        final Function1 onTextLayout2;
        final int maxLines4;
        final TextStyle style3;
        final FontWeight fontWeight4;
        final FontStyle fontStyle4;
        final FontFamily fontFamily3;
        final long letterSpacing3;
        final TextDecoration textDecoration3;
        final long lineHeight3;
        final int minLines3;
        final long fontSize4;
        int i6;
        Composer $composer3 = $composer.startRestartGroup(-2055108902);
        ComposerKt.sourceInformation($composer3, "C(Text)P(14,9,0:c#ui.graphics.Color,2:c#ui.unit.TextUnit,3:c#ui.text.font.FontStyle,4!1,5:c#ui.unit.TextUnit,16,15:c#ui.text.style.TextAlign,6:c#ui.unit.TextUnit,11:c#ui.text.style.TextOverflow,12)108@5620L7,113@5732L530:Text.kt#uh7d8r");
        int $dirty3 = $changed;
        if ((i & 1) != 0) {
            $dirty3 |= 6;
            obj = text;
        } else if (($changed & 6) == 0) {
            obj = text;
            $dirty3 |= $composer3.changed(obj) ? 4 : 2;
        } else {
            obj = text;
        }
        //省略后续代码

翻译一下就是 把入参text传入 $composer3.changed判断是否与上次记录的值发生变化 Composer源码如下

上次text的值就存在Composer里成员变量slotTable对象的slots变量中,值怎么存的可以在SlotTable set方法打断点

可以看到slots中存储了整个ComposeView中所有入参值是以Array数组的形式,这里就涉及到Composer changed方法为什么要调用nextSlot()和Composerable方法反编译后为什么都有如下3个方法调用

ini 复制代码
 Composer $composer2 = $composer.startRestartGroup(-1745234003)
 //省略代码
 $composer.skipToGroupEnd()
//省略代码
 ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();

因为SlotTable存储的是整个ComposeView组成composeable函数UI树的所有入参Array,扁平化存储,所以需要以Composeable要是使用Group分组,使用"指针"来锚定当前执行到的composeable函数当前入参,指针寻址这里不做展开说明有兴趣可以自行去看,Composer::changed方法判断本次value和slots存储的对应text是否变化,变化就发生重组逻辑。

至此我们就已经基本探究完Compose机制了,总结一下就是Compose是使用开发者写的@Composeable函数关系来描述这个UI树的,不同于Android View系统使用的ViewGroup/View对象Tree来描述UI树;而UI的更新 Compose使用的入参判断变化来更新,而Android View系统使用的是命令式比如显示调用View.requstLayout

Compose实现

原理搞懂了,接下来就来实现一个Compose,其实就5个核心类,让AI直接给出Compose简易实现,这里就不贴代码了,结尾有Github Compose简易实现源码链接,以下是核心类功能介绍

一、SlotTable

功能:

  • 核心"记忆仓库",线性存储所有 Composable 的状态(remember 值、参数值、lambda 等)
  • 管理 index 指针,支持顺序访问
  • 管理 Group:startGroup / endGroup / skipGroup
  • startGroup:记录 group 起始 index
  • endGroup:计算 group 占用 slot 数量
  • skipGroup:重组时快速跳过整个 group

原理:

  • 每次调用 next() → 返回当前 slot 并 index++
  • 每次调用 update() → 修改 index-1 对应的 slot

二、Composer

功能:

  • SlotTable 封装器:提供更高层的接口
  • changed(value) → 判断当前 slot 是否和传入值不同,返回 Boolean
  • remember(factory) → 读取 slot,如果为空执行 lambda 并写入 slot
  • Group 管理:startGroup() / endGroup() / skipGroup()

原理:

  • 将 Composable 函数的运行与 SlotTable 状态绑定
  • 控制 skip / 重组逻辑

三、State

功能:

  • 可变状态持有者
  • value 变化时触发回调 → 触发重组

原理:

  • 内部存储当前值 _value
  • set(value) → 值改变 → 调用 onChange()
  • mutableStateOf 是创建 State 的工厂函数

四、Composition

功能:

  • 管理整个 Composable 树的入口
  • setContent() → 设置顶层 Composable
  • recompose() → 重组整个树
  • 内部持有 SlotTable + Composer

原理:

  • 每次 recompose() → reset SlotTable → 执行顶层 Composable → 根据 changed/skip 更新 slot
  • 可由 State 改变触发

五、Composable 函数

功能:

  • 业务逻辑 + UI 表达
  • 使用 composer.changed() 判断参数是否变化

Android View系统 对比 Compose

特性 View 系统 Compose
UI架构 View对象组成的UI Tree Composable 函数描述UI Tree
渲染对象 每个 View 占用对象内存 SlotTable 记录状态、重组时决定是否绘制
Draw / Canvas 每个 View 负责绘制自己 Applier(类似 Canvas)统一提交 UI 树变更
重绘策略 requestLayout/invalidate() → 递归绘制 State 改变 → Recomposer → 重组 → 重绘

源码

github.com/zjw-swun/Co...

相关推荐
青莲8432 小时前
Kotlin Flow 深度探索与实践指南——中部:实战与应用篇
android·前端
建群新人小猿3 小时前
陀螺匠企业助手-我的日程
android·大数据·运维·开发语言·容器
_李小白3 小时前
【Android FrameWork】第三十九天:DeviceStorageManagerService
android
不急不躁1234 小时前
Android16 给应用默认获取权限
android·java
用户41659673693554 小时前
拒绝 Race Condition:深入理解 StateFlow 的取值与更新
android
青莲8435 小时前
Kotlin Flow 深度探索与实践指南——上部:基础与核心篇
android·前端
恋猫de小郭5 小时前
2025 年终醒悟,AI 让我误以为自己很强,未来程序员的转型之路
android·前端·flutter
2501_915918415 小时前
iOS 开发中证书创建与管理中的常见问题
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张6 小时前
IOScer 开发环境证书包括哪些,证书、描述文件与 App ID 的协同管理实践
android·ios·小程序·https·uni-app·iphone·webview