@Compose 注解到底做了什么?了解一下~

本文深入探讨了Kotlin Compose中的注解,揭示了它们如何通过编译插件工作,实现函数转换和功能增强。与注解处理器不同,Compose注解通过Kotlin编译插件实现,提供了更快的编译速度和代码修改能力。文中介绍了注解的注册、添加参数等关键步骤,展示了Compose如何在函数中插入参数以实现智能重组和状态跟踪。文章总结了Compose注解处理的复杂性和优势,并提到了替代方案KSP。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

了解过Compose的同学都知道,只需要添加一个@Compose注解就可以将函数转化成Compose函数,同时Compose函数也只能在Compose函数中运行。这看起来似乎跟协程比较像,@Compose是不是也像协程一样,往函数中添加了一些参数呢?

我们就一起来看下,@Compose到底做了什么,又是怎么做到的。

前置知识

一看到@Compose注解,我们很容易就想到注解处理器,但是@Compose的解析并不是通过注解处理器来实现的,因为注解处理器只能生成代码,不能修改代码
KCPKotlin Compiler Plugin):即kotlin编译插件,支持跨平台,android开发可以将它类比为kapt+transform机制,既可以生成代码,也可以修改代码

@Compose注解的解析就是通过KCP来实现的

什么是KCP

Kotlin编译过程简单来说,就是将Kotlin源码编译成字节码的过程,具体步骤如下所示:

Kotlin编译期插件则在编译过程中提供Hook时机,让我们可以解析符号,修改字节码生成结果等。
Kotlin库中的不少语法糖都用到了KCP,比如Kotlin-android-extension@Parcelize等,@Compose注解同样也是通过KCP解析的

相比KAPTKCP主要有以下优点:

  1. KAPT是基于注解处理器的,它需要将Kotlin代码转化成Stub再解析注解生成代码,常常转化成Stub的时间比生成代码还要长,而KCP则是直接解析Kotlin的符号,因此在编译速度上KCPKAPT要强的多
  2. KAPT只能生成代码,不能修改代码,而KCP不仅可以生成代码,也可以修改代码,可以看作是kapt+transorm机制

KCP的缺点则在于,KCP 的开发成本太高,涉及 Gradle PluginKotlin Plugin 等的使用,API 涉及一些编译器知识的了解,一般开发者很难掌握。
因此如果只是需要处理注解生成代码,不需要修改代码,通常使用KSP就足够了,KSP是对KCP的一个封装,如果对KSP的使用详情感兴趣可参见:告别KAPT!使用 KSP 为 Kotlin 编译提速

KCP的基本概念

上面也说到了,KCP的开发成本较高,主要包括以下内容:

  • PluginGradle 插件用来读取 Gradle 配置传递给 KCPKotlin Plugin
  • Subplugin:为 KCP 提供自定义 KPmaven 库地址等配置信息
  • CommandLineProcessor:负责将Plugin传过来的参数转换并校验
  • ComponentRegistrar:负责将用户自定义的各种Extension注册到KP中,并在合适时机调用

ComponentRegistrar是核心入口,所有的KCP自定义功能都需要通过这个类注册一些Extension接口来实现。

下面列举一些常用的Extension接口,大家可以根据需求选用:

  • IrGenerationExtension,用于增/删/改/查代码
  • DiagnosticSuppressor,用于抑制语法错误,Jetpack Compose有使用
  • StorageComponentContainerContributor,用于实现IOC

@Compose注解的作用

上面介绍了KCP的基本概念,下面看下在Jetpack Compose@Compose注解到底是怎么解析的,又起了什么作用

注册IrGenerationExtension

上面我们介绍了ComponentRegistrar是核心入口,负责将用户自定义的各种Extension注册到KP中,并在合适时机调用,而IrGenerationExtension可以用于修改代码
Compose插件的入口为ComposePlugin,其中也包括一个ComposeComponentRegistrarIrGenerationExtension的注册就是在这里完成的

class ComposeComponentRegistrar : ComponentRegistrar {
    override fun registerProjectComponents(
        project: MockProject,
        configuration: CompilerConfiguration
    ) {
        registerProjectExtensions(
            project as Project,
            configuration
        )
    }
    
    fun registerProjectExtensions(
            project: Project,
            configuration: CompilerConfiguration
    ) {
	    IrGenerationExtension.registerExtension(
            project,
            ComposeIrGenerationExtension(
              //...
            )
        )

    }

} 

如上所示,注册了IrGenerationExtension,接下来IrGenerationExtension会调用ComposerParamTransformer的相关方法,完成参数的填充,后续的主要处理工作都是在ComposerParamTransformer中处理了

添加$Composer

上文说到,后续在函数中添加参数的工作主要是在ComposerParamTransformer中完成的,具体调用了IrFunction.withComposerParamIfNeeded

 private fun IrFunction.withComposerParamIfNeeded(): IrFunction {
        // 如果不是`Compose`函数,则直接返回,后续不再处理
        if (!this.hasComposableAnnotation()) {
            return this
        }

        // 如果此函数是作为参数的`Lambda`,并且不是`Compose`函数,则直接返回
        if (isNonComposableInlinedLambda()) return this

        // 不处理expect函数
        if (isExpect) return this

        // 缓存转换的结果
        return transformedFunctions[this] ?: copyWithComposerParam()
    } 

如上所示,主要就是判断一下函数是否有@Compose注解,如果没有则直接返回不再处理,有则继续处理并缓存结果,后续调用copyWithComposerParam方法

 private fun IrFunction.copyWithComposerParam(): IrSimpleFunction {
    	//...

        return copy().also { fn ->
            // $composer
            val composerParam = fn.addValueParameter {
                name = KtxNameConventions.COMPOSER_PARAMETER
                type = composerType.makeNullable()
                origin = IrDeclarationOrigin.DEFINED
                isAssignable = true
            }

           //...
        }
    } 

如上所示,在所有Compose函数中插入了一个$composer,这有效地使Composer可用于任何子树,提供实现Composable树并保持更新所需的所有信息。

添加$changed

我们知道Compose存在智能重组机制,当输入完全相同时允许跳过重组,而编译器除了$composer,还会注入$changed 参数。 此参数用于提供有关当前 Composable 的输入参数与一次发生组件后是否相同,如果相同则允许跳过重组。

 private fun IrFunction.copyWithComposerParam(): IrSimpleFunction {
    	//...

        return copy().also { fn ->
            // $changed[n]
            val changed = KtxNameConventions.CHANGED_PARAMETER.identifier
            //changedparamCount,计算$changed数量
            for (i in 0 until changedParamCount(realParams, fn.thisParamCount)) {
                fn.addValueParameter(
                    if (i == 0) changed else "$changed$i",
                    context.irBuiltIns.intType
                )
            }            

           //...
        }
    } 

如上所示,添加的是个$changed[n],这个n是从何而来呢?这是因为每个参数的状态有5种情况,compose中定义了一个枚举,如下所示:

enum class ParamState(val bits: Int) {
    Uncertain(0b000),
    Same(0b001),
    Different(0b010),
    Static(0b011),
    Unknown(0b100),
    Mask(0b111);
} 

如上所示,$changed通过位运算的方式来表示参数是否发生变化:

  1. $changedInt类型,一个占32位
  2. 每个参数有5种类型,因此一个参数需要3位来表示
  3. 因此一个$changed可以表示10个参数是否发生变化,如果超出则需要再添加一个$changed参数

编译器注入$changed之后效果如下所示:

 @Composable
    fun A(x: Int, $composer: Composer<*>, $changed: Int) {
        var $dirty = $changed
        if ($changed and 0b0110 === 0) {
            $dirty = $dirty or if ($composer.changed(x)) 0b0010 else 0b0100
        }
        if (%dirty and 0b1011 !== 0b1010 || !$composer.skipping) {
            f(x)
        } else {
            $composer.skipToGroupEnd()
        }
    } 

添加$default

Kotlin 支持的默认参数不适用于可组合函数的参数,因为可组合函数需要在函数的作用域(生成的组)内为其参数执行默认表达式。 为此,Compose 提供了默认参数解析机制的替代实现。即在Compose方法中添加$defaulut

 private fun IrFunction.copyWithComposerParam(): IrSimpleFunction {
		//...

       // $default[n]
       if (oldFn.requiresDefaultParameter()) {
           val defaults = KtxNameConventions.DEFAULT_PARAMETER.identifier
           for (i in 0 until defaultParamCount(realParams)) {
               fn.addValueParameter(
                   if (i == 0) defaults else "$defaults$i",
                   context.irBuiltIns.intType,
                   IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION
               )
           }
       }            

      //...
   } 

$default$changed类似,也是通过位运算来表示参数状态的,不过$default比较简单,只有两种状态,使用还是不使用默认值,因此一个$changed参数可表示31个参数是否使用默认值,如果超出再添加一个$changed
编译器注入$default后的效果如下所示:

 @Composable
    fun A(x: Int, $default: Int) {
        val x = if ($default and 0b1 != 0) 0 else x
        f(x)
    } 

总结

本文主要简单介绍了什么是KCPKCP是如何处理@Compose注解的,从中可以看到KCP的强大与复杂,如果你只需要解析注解生成代码的话,可以使用KSP取代KAPT,如果有更多需求,可以尝试使用KCP
同时也可以看到Compose设计的巧妙,将框架背后的复杂度完全隐藏,背后做了这么多工作,使用都却只需添加一个@Compose注解,就能将一个普通的函数变成Compose函数,的确是挺简洁优雅的,感兴趣的同学也可以直接查看源码~

最后

分享给大家一份面试题合集。

下面的题目都是在Android交流群大家在面试时遇到的,如果大家有好的题目或者好的见解欢迎分享,楼主将长期维护此帖。
参考解析:郭霖、鸿洋、玉刚、极客时间、腾讯课堂…

内容特点:条理清晰,含图像化表示更加易懂。

内容概要:包括 Handler、Activity相关、Fragment、service、布局优化、AsyncTask相关
、Android 事件分发机制、 Binder、Android 高级必备 :AMS,WMS,PMS、Glide、 Android 组件化与插件化等面试题和技术栈!
image

Handler 相关知识,面试必问!

常问的点:
Handler Looper Message 关系是什么?
Messagequeue 的数据结构是什么?为什么要用这个数据结构?
如何在子线程中创建 Handler?
Handler post 方法原理?
Android消息机制的原理及源码解析
Android Handler 消息机制

image

Activity 相关

启动模式以及使用场景?
onNewIntent()和onConfigurationChanged()
onSaveInstanceState()和onRestoreInstanceState()
Activity 到底是如何启动的
启动模式以及使用场景
onSaveInstanceState以及onRestoreInstanceState使用
onConfigurationChanged使用以及问题解决
Activity 启动流程解析

image

Fragment

Fragment 生命周期和 Activity 对比
Fragment 之间如何进行通信
Fragment的startActivityForResult
Fragment重叠问题
Fragment 初探
Fragment 重叠, 如何通信
Fragment生命周期

image

Service 相关

进程保活
Service的运行线程(生命周期方法全部在主线程)
Service启动方式以及如何停止
ServiceConnection里面的回调方法运行在哪个线程?
startService 和 bingService区别
进程保活一般套路
关于进程保活你需要知道的一切

image

Android布局优化之ViewStub、include、merge

什么情况下使用 ViewStub、include、merge?
他们的原理是什么?
ViewStub、include、merge概念解析
Android布局优化之ViewStub、include、merge使用与源码分析

image

BroadcastReceiver 相关

注册方式,优先级
广播类型,区别
广播的使用场景,原理
Android广播动态静态注册
常见使用以及流程解析
广播源码解析

image

AsyncTask相关

AsyncTask是串行还是并行执行?
AsyncTask随着安卓版本的变迁
AsyncTask完全解析
串行还是并行

image

Android 事件分发机制

onTouch和onTouchEvent区别,调用顺序
dispatchTouchEvent, onTouchEvent, onInterceptTouchEvent 方法顺序以及使用场景
滑动冲突,如何解决
事件分发机制
事件分发解析
dispatchTouchEvent, onTouchEvent, onInterceptTouchEvent方法的使用场景解析

image

Android View 绘制流程

简述 View 绘制流程
onMeasure, onlayout, ondraw方法中需要注意的点
如何进行自定义 View
view 重绘机制

  • Android LayoutInflater原理分析,带你一步步深入了解View(一)

  • Android视图状态及重绘流程分析,带你一步步深入了解View(二)

  • Android视图状态及重绘流程分析,带你一步步深入了解View(三)

  • Android自定义View的实现方法,带你一步步深入了解View(四)

    image

Android Window、Activity、DecorView以及ViewRoot

Window、Activity、DecorView以及ViewRoot之间的关系

image

Android 的核心 Binder 多进程 AIDL

常见的 IPC 机制以及使用场景
为什么安卓要用 binder 进行跨进程传输
多进程带来的问题

  • AIDL 使用浅析

  • binder 原理解析

  • binder 最底层解析

  • 多进程通信方式以及带来的问题

  • 多进程通信方式对比

    image

Android 高级必备 :AMS,WMS,PMS

AMS,WMS,PMS 创建过程

  • AMS,WMS,PMS全解析

  • AMS启动流程

  • WindowManagerService启动过程解析

  • PMS 启动流程解析

    image

Android ANR

为什么会发生 ANR?
如何定位 ANR?
如何避免 ANR?
什么是 ANR
如何避免以及分析方法
Android 性能优化之 ANR 详解

image

Android 内存相关

注意:内存泄漏和内存溢出是 2 个概念

什么情况下会内存泄漏?
如何防止内存泄漏?

  • 内存泄漏和溢出的区别

  • OOM 概念以及安卓内存管理机制

  • 内存泄漏的可能性

  • 防止内存泄漏的方法

    image

Android 屏幕适配

屏幕适配相关名词解析
现在流行的屏幕适配方式

  • 屏幕适配名词以及概念解析

  • 今日头条技术适配方案

    image

Android 缓存机制

LruCache使用极其原理

  • Android缓存机制

  • LruCache使用极其原理述

    image

Android 性能优化

如何进行 内存 cpu 耗电 的定位以及优化
性能优化经常使用的方法
如何避免 UI 卡顿

  • 性能优化全解析,工具使用

  • 性能优化最佳实践

  • 知乎高赞文章

    image

Android MVC、MVP、MVVM

好几种我该选择哪个?优劣点

任玉刚的文章:设计模式选择

image

Android Gradle 知识

这俩篇官方文章基础的够用了
必须贴一下官方文档:配置构建
Gradle 提示与诀窍

Gradle插件 了解就好
Gradle 自定义插件方式
全面理解Gradle - 执行时序

  • Gradle系列一

  • Gradle系列二

  • Gradle系列三

    image

RxJava

使用过程,特点,原理解析
RxJava 名词以及如何使用
Rxjava 观察者模式原理解析
Rxjava订阅流程,线程切换,源码分析 系列

image

OKHTTP 和 Retrofit

OKHTTP完整解析
Retrofit使用流程,机制详解
从 HTTP 到 Retrofit
Retrofit是如何工作的

image

最流行图片加载库: Glide

郭神系列 Glide 分析
Android图片加载框架最全解析(一),Glide的基本用法
Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程
Android图片加载框架最全解析(三),深入探究Glide的缓存机制
Android图片加载框架最全解析(四),玩转Glide的回调与监听
Android图片加载框架最全解析(五),Glide强大的图片变换功能
Android图片加载框架最全解析(六),探究Glide的自定义模块功能
Android图片加载框架最全解析(七),实现带进度的Glide图片加载功能
Android图片加载框架最全解析(八),带你全面了解Glide 4的用法

image

Android 组件化与插件化

为什么要用组件化?
组件之间如何通信?
组件之间如何跳转?
Android 插件化和热修复知识梳理
为什么要用组件化

  • Android彻底组件化方案实践
  • Android彻底组件化demo发布
  • Android彻底组件化-代码和资源隔离
  • Android彻底组件化—UI跳转升级改造
  • Android彻底组件化—如何使用Arouter

插件化框架历史
深入理解Android插件化技术
Android 插件化和热修复知识梳理

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

好啦,这份资料就给大家介绍到这了,有需要详细文档的小伙伴,可以微信扫下方二维码免费领取哈~

<think>嗯,用户让我介绍一下Android Compose,我需要先回忆一下自己对这个框架的了解Android Jetpack Compose是Google推出的现代UI工具包,用于简化Android应用的界面开发。首先,我应该解释它是什么,然后对比传统的XML布局方式,说明它的优势,比如声明式UI、更简洁的代码等。 用户可能是Android开发者,或者刚入门想了解新技术的人。他们可能已经接触过传统的方法,遇到了一些痛点,比如繁琐的findViewById,或者维护复杂的界面逻辑。所以需要强调Compose如何解决这些问题。 接下来,我应该介绍核心特性,比如声明式编程、Composable函数、实时预览、与现有代码的兼容性,以及性能优化。这部分需要简明扼要,但每个点都要讲清楚好处,比如声明式UI让代码更直观,实时预览提高开发效率。 然后可能需要提到Compose的组成,包括布局、绘图、动画等模块,说明它是一个全面的解决方案。另外,Kotlin的支持也很重要,因为Compose完全基于Kotlin,这也符合现代Android开发的趋势。 用户可能还想知道学习Compose的资源,比如官方文档、教程,或者社区支持。不过用户没有明确问这个,可能不需要展开,但可以稍微提一下,比如推荐官方资源。 还需要注意避免使用过于专业的术语,保持解释的易懂性。比如解释声明式UI时,可以对比命令式,说明开发者只需描述UI应该是什么样子,而不必一步步指挥如何构建。 另外,可能存在的误区是Compose只适用于新项目,但实际它也可以逐步集成到现有项目中,这点值得强调,让用户知道迁移的可行性。 最后,总结一下Compose的优势,比如提升开发效率、代码简洁、维护方便等,适合现代应用开发。确保回答结构清晰,逻辑顺畅,覆盖用户可能关心的各个方面。</think>好的!我来为你介绍一下 **Android Jetpack Compose**(以下简称 Compose)。 --- ### **什么是 Compose?** Compose 是 Google 在 2019 年推出的**现代 Android UI 开发工具包**,旨在通过**声明式编程**简化界面开发。它完全替代了传统的基于 XML 的布局和 `View` 系统,开发者可以用更少的代码、更直观的方式构建动态、高性能的界面。 --- ### **核心特性** 1. **声明式 UI** - 传统 Android 开发使用**命令式**方式(手动更新 `View` 状态),而 Compose 采用**声明式**:你只需描述 UI 的最终形态,Compose 自动处理状态变化后的界面更新。 - 例:一个按钮的显示/隐藏,只需修改状态变量,无需手动调用 `setVisibility()`。 2. **基于 Kotlin** - 完全使用 Kotlin 语言编写,支持 Kotlin 协程、扩展函数等特性,与现有 Kotlin 代码无缝集成。 3. **可组合函数(Composable)** - 通过 `@Composable` 注解定义 UI 组件,类似 React 的函数式组件。 - 例:一个简单的按钮组件: ```kotlin @Composable fun MyButton(text: String, onClick: () -> Unit) { Button(onClick = onClick) { Text(text = text) } } ``` 4. **实时预览 & 热重载** - Android Studio 提供**交互式预览**功能,修改代码后无需重新编译即可看到界面变化(部分场景支持热重载)。 5. **高性能** - 通过智能重组(Recomposition)优化渲染,仅更新需要变化的 UI 部分。 6. **兼容性** - 支持最低 API 21(Android 5.0),可与传统 `View` 系统混合使用,逐步迁移旧项目。 --- ### **Compose 的组成** - **Foundation**:基础组件(如 `Text`, `Button`, `Column`, `Row`)。 - **Material Design**:Material 风格组件(如 `TopAppBar`, `Card`, `Scaffold`)。 - **Animation**:声明式动画 API。 - **Layout**:自定义布局与测量逻辑。 - **Graphics**:直接绘制 Canvas 和自定义图形。 --- ### **优势** 1. **代码量大幅减少** - 无需 XML 布局,避免了 `findViewById` 和繁琐的 `View` 更新逻辑。 2. **强类型安全** - Kotlin 的静态类型检查减少运行时错误。 3. **高度可复用** - 组件化设计,UI 模块易于复用和测试。 4. **现代化工具链** - Android Studio 对 Compose 提供深度支持(如布局检查器、动画调试)。 --- ### **学习资源** - **官方文档**:[Compose 官方指南](https://developer.android.com/jetpack/compose) - **示例项目**:[Compose Samples GitHub](https://github.com/android/compose-samples) - **教程**:Google 提供的 [Compose Pathway](https://developer.android.com/courses/pathways/compose) --- ### **适用场景** - 新项目:推荐直接使用 Compose。 - 旧项目:可逐步迁移,在部分界面中替代传统 `View`。 --- 如果你有具体的开发问题(如状态管理、动画实现等),可以进一步讨论! 😊
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值