Kotlin-KCP的应用-第一篇

Kotlin 云端差分缓存技术 本文由字节跳动 Buildinfra 团队出品。在我们的工程上线 Monorepo 全源码后,Kotlin 编译成了整个编译中最耗时的步骤,全源码过程中大量的 BuildCache Miss 导致我们的编译数据落后原来多仓二进制时代很多,且业界没有相关的解决方案。本篇文章我们来具体阐述下 BuildInfra 团队自研的解决方案 - Kotlin 云端差分方案的原理和技术实现。一、Monorepo... 阅读详情

前言

KCP的应用计划分两篇,本文是第一篇

本文主要记录从发现问题到使用KCP解决问题的折腾过程,下一篇记录KCP的应用

背景

Kotlin 号称百分百兼容 Java ,所以在 Kotlin 中一些修饰符,比如 internal ,在编译后放在纯 Java 的项目中使用(没有Kotlin环境),Java 仍然可以访问被 internal 修饰的类、方法、字段等

在使用 Kotlin 开发过程中需要对外提供 SDK 包,在 SDK 中有一些 API 不想被外部调用,并且已经添加了 internal 修饰,但是受限于上诉问题且第三方使用 SDK 的环境不可控(不能要求第三方必须使用Kotlin)

带着问题Google一番,查到以下几个解决方案:

  1. 使用 JvmName 注解设置一个不符合 Java 命名规则的标识符1
  2. 使用 ˋˋ 在 Kotlin 中把一个不合法的标识符强行合法化1
  3. 使用 JvmSynthetic 注解2

以上方案可以满足大部分需求,但是以上方案都不满足隐藏构造方法,可能会想什么情景下需要隐藏构造方法,例如:

class Builder(internal val a: Int, internal val b: Int) {
    
    /**
     * non-public constructor for java
     */
    internal constructor() : this(-1, -1)
}

为此我还提了个Issue3,期望官方把 JvmSynthetic 的作用域扩展到构造方法,不过官方好像没有打算实现😂

为解决隐藏构造方法,可以把构造方法私有化,对外暴露静态工厂方法:

class Builder private constructor (internal val a: Int, internal val b: Int) {
    
    /**
     * non-public constructor for java
     */
    private constructor() : this(-1, -1)
    
    companion object {

        @JvmStatic
        fun newBuilder(a: Int, b: Int) = Builder(a, b)
    }
}

解决方案说完了,大家散了吧,散了吧~

开玩笑,开玩笑😛,必然要折腾一番

折腾

探索JvmSynthetic实现原理

先看下 JvmSynthetic 注解的注释文档

/**
 * Sets `ACC_SYNTHETIC` flag on the annotated target in the Java bytecode.
 *
 * Synthetic targets become inaccessible for Java sources at compile time while still being accessible for Kotlin sources.
 * Marking target as synthetic is a binary compatible change, already compiled Java code will be able to access such target.
 *
 * This annotation is intended for *rare cases* when API designer needs to hide Kotlin-specific target from Java API
 * while keeping it a part of Kotlin API so the resulting API is idiomatic for both languages.
 */

好家伙,实现原理都说了:在 Java 字节码中的注解目标上设置 ACC_SYNTHETIC 标识

此处涉及 Java 字节码知识点,ACC_SYNTHETIC 标识可以简单理解是 Java 隐藏的,非公开的一种修饰符,可以修饰类、方法、字段等4

得看看 Kotlin 是如何设置 ACC_SYNTHETIC 标识的,打开 Github Kotlin 仓库,在仓库内搜索 JvmSynthetic 关键字 Search · JvmSynthetic (github.com)

image-20220508120615028

在搜索结果中分析发现 JVM_SYNTHETIC_ANNOTATION_FQ_NAME 关联性较大,继续在仓库内搜索 JVM_SYNTHETIC_ANNOTATION_FQ_NAME 关键字 Search · JVM_SYNTHETIC_ANNOTATION_FQ_NAME (github.com)

image-20220508121250275

image-20220508121115651

image-20220508121137580

在搜索结果中发现几个类名与代码生成相关,这里以 ClassCodegen.kt 为例,附上相关代码

// 获取Class的SynthAccessFlag
private fun IrClass.getSynthAccessFlag(languageVersionSettings: LanguageVersionSettings): Int {
    // 如果有`JvmSynthetic`注解,返回`ACC_SYNTHETIC`标识
    if (hasAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME))
        return Opcodes.ACC_SYNTHETIC
    if (origin == IrDeclarationOrigin.GENERATED_SAM_IMPLEMENTATION &&
        languageVersionSettings.supportsFeature(LanguageFeature.SamWrapperClassesAreSynthetic)
    )
        return Opcodes.ACC_SYNTHETIC
    return 0
}

// 计算字段的AccessFlag
private fun IrField.computeFieldFlags(context: JvmBackendContext, languageVersionSettings: LanguageVersionSettings): Int =
    origin.flags or visibility.flags or
            (if (isDeprecatedCallable(context) ||
                correspondingPropertySymbol?.owner?.isDeprecatedCallable(context) == true
            ) Opcodes.ACC_DEPRECATED else 0) or
            (if (isFinal) Opcodes.ACC_FINAL else 0) or
            (if (isStatic) Opcodes.ACC_STATIC else 0) or
            (if (hasAnnotation(VOLATILE_ANNOTATION_FQ_NAME)) Opcodes.ACC_VOLATILE else 0) or
            (if (hasAnnotation(TRANSIENT_ANNOTATION_FQ_NAME)) Opcodes.ACC_TRANSIENT else 0) or
			// 如果有`JvmSynthetic`注解,返回`ACC_SYNTHETIC`标识
            (if (hasAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME) ||
                isPrivateCompanionFieldInInterface(languageVersionSettings)
            ) Opcodes.ACC_SYNTHETIC else 0)

上述源码中 Opcodes 是字节码操作库 ASM 中的类

猜想 Kotlin 编译器也是使用 ASM 编译生成/修改Class文件

🆗,知道了 JvmSynthetic 注解的实现原理,是不是可以仿照 JvmSynthetic 给构造方法也添加 ACC_SYNTHETIC 标识呢❓

首先想到的就是利用 AGP Transform 进行字节码修改

AGP Transform

AGP Transform 的搭建、使用,网上有很多相关文章,此处不再描述,下图是本仓库的组织架构

image-20220508131245185

这里简单说明下:

api-xxx

api-xxx模块中只有一个注解类 Hide

@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface Hide {
}
@Target(
    AnnotationTarget.FIELD,
    AnnotationTarget.CONSTRUCTOR,
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER,
)
@Retention(AnnotationRetention.BINARY)
annotation class Hide
kcp

kcp相关,下篇再讲

lib-xxx

lib-xxx模块中包含对注解api-xxx的测试,打包成SDK,供app模块使用

plugin

plugin模块包含AGP Transform

实现plugin模块

创建MaskPlugin

创建 MaskPlugin 类,实现 org.gradle.api.Plugin 接口

class MaskPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        // 输出日志,查看Plugin是否生效
        project.logger.error("Welcome to guodongAndroid mask plugin.")

        // 目前增加了限制仅能用于`AndroidLibrary`
        LibraryExtension extension = project.extensions.findByType(LibraryExtension)
        if (extension == null) {
            project.logger.error("Only support [AndroidLibrary].")
            return
        }

        extension.registerTransform(new MaskTransform(project))
    }
}
创建MaskTransform

创建 MaskTransform,继承 com.android.build.api.transform.Transform 抽象类,主要实现 transform 方法,以下为核心代码

class MaskTransform extends Transform {
    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        long start = System.currentTimeMillis()
        logE("$TAG - start")

        TransformOutputProvider outputProvider = transformInvocation.outputProvider
        
        // 没有适配增量编译

        // 只关心本项目生成的Class文件
        transformInvocation.inputs.each { transformInput ->
            transformInput.directoryInputs.each { dirInput ->
                if (dirInput.file.isDirectory()) {
                    dirInput.file.eachFileRecurse { file ->
                        if (file.name.endsWith(".class")) {
                            // 使用ASM修改Class文件
                            ClassReader cr = new ClassReader(file.bytes)
                            ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
                            ClassVisitor cv = new CheckClassAdapter(cw)
                            cv = new MaskClassNode(Opcodes.ASM9, cv, mProject)
                            int parsingOptions = 0
                            cr.accept(cv, parsingOptions)
                            byte[] bytes = cw.toByteArray()

                            FileOutputStream fos = new FileOutputStream(file)
                            fos.write(bytes)
                            fos.flush()
                            fos.close()
                        }
                    }
                }

                File dest = outputProvider.getContentLocation(dirInput.name, dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY)
                FileUtils.copyDirectory(dirInput.file, dest)
            }

            // 不关心第三方Jar中的Class文件
            transformInput.jarInputs.each { jarInput ->
                String jarName = jarInput.name
                String md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
                if (jarName.endsWith(".jar")) {
                    jarName = jarName.substring(0, jarName.length() - 4)
                }
                File dest = outputProvider.getContentLocation(jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
                FileUtils.copyFile(jarInput.file, dest)
            }
        }

        long cost = System.currentTimeMillis() - start
        logE(String.format(Locale.CHINA, "$TAG - end, cost: %dms", cost))
    }

    private void logE(String msg) {
        mProject.logger.error(msg)
    }
}
创建MaskClassNode

创建 MaskClassNode,继承 org.objectweb.asm.tree.ClassNode,主要实现 visitEnd 方法

class MaskClassNode extends ClassNode {

    private static final String TAG = MaskClassNode.class.simpleName

    // api-java中`Hide`注解的描述符
    private static final String HIDE_JAVA_DESCRIPTOR = "Lcom/guodong/android/mask/api/Hide;"
    
    // api-kt中`Hide`注解的描述符
    private static final String HIDE_KOTLIN_DESCRIPTOR = "Lcom/guodong/android/mask/api/kt/Hide;"

    private static final Set<String> HIDE_DESCRIPTOR_SET = new HashSet<>()

    static {
        HIDE_DESCRIPTOR_SET.add(HIDE_JAVA_DESCRIPTOR)
        HIDE_DESCRIPTOR_SET.add(HIDE_KOTLIN_DESCRIPTOR)
    }

    private final Project project

    MaskClassNode(int api, ClassVisitor cv, Project project) {
        super(api)
        this.project = project
        this.cv = cv
    }

    @Override
    void visitEnd() {

        // 处理Field
        for (fn in fields) {
            boolean has = hasHideAnnotation(fn.invisibleAnnotations)
            if (has) {
                project.logger.error("$TAG, before --> typeName = $name, fieldName = ${fn.name}, access = ${fn.access}")
                // 修改字段的访问标识
                fn.access += Opcodes.ACC_SYNTHETIC
                project.logger.error("$TAG, after --> typeName = $name, fieldName = ${fn.name}, access = ${fn.access}")
            }
        }

        // 处理Method
        for (mn in methods) {
            boolean has = hasHideAnnotation(mn.invisibleAnnotations)
            if (has) {
                project.logger.error("$TAG, before --> typeName = $name, methodName = ${mn.name}, access = ${mn.access}")
                // 修改方法的访问标识
                mn.access += Opcodes.ACC_SYNTHETIC
                project.logger.error("$TAG, after --> typeName = $name, methodName = ${mn.name}, access = ${mn.access}")
            }
        }

        super.visitEnd()

        if (cv != null) {
            accept(cv)
        }
    }

    /**
     * 是否有`Hide`注解
     */
    private static boolean hasHideAnnotation(List<AnnotationNode> annotationNodes) {
        if (annotationNodes == null) return false
        for (node in annotationNodes) {
            if (HIDE_DESCRIPTOR_SET.contains(node.desc)) {
                return true
            }
        }
        return false
    }
}

使用Transform

build.gradle - project level
buildscript {
    ext.plugin_version = 'x.x.x'
    dependencies {
        classpath "com.guodong.android:mask-gradle-plugin:${plugin_version}"
    }
}
build.gradle - module level
# lib-kotlin
plugins {
    id 'com.android.library'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'maven-publish'
    id 'com.guodong.android.mask'
}
lib-kotlin
interface InterfaceTest {

    // 使用api-kt中的注解
    @Hide
    fun testInterface()
}
class KotlinTest(a: Int) : InterfaceTest {

    // 使用api-kt中的注解
    @Hide
    constructor() : this(-2)

    companion object {

        @JvmStatic
        fun newKotlinTest() = KotlinTest()
    }

    private val binding: LayoutKotlinTestBinding? = null

    // 使用api-kt中的注解
    var a = a
        @Hide get
        @Hide set

    fun getA1(): Int {
        return a
    }

    fun test() {
        a = 1000
    }

    override fun testInterface() {
        println("Interface function test")
    }
}
app
# MainActivity.java

private void testKotlinLib() {
    // 创建对象时不能访问无参构造方法,可以访问有参构造方法或访问静态工厂方法
    KotlinTest test = KotlinTest.newKotlinTest();
    // 调用时不能访问`test.getA()`方法,仅能访问`getA1()方法
    Log.e(TAG, "testKotlinLib: before --> " + test.getA1());
    test.test();
    Log.e(TAG, "testKotlinLib: after --> " + test.getA1());
    
    // Pass
    test.testInterface();
    
    InterfaceTest interfaceTest = test;
    // Error - cannot resolve method 'testInterface' in 'InterfaceTest'
    interfaceTest.testInterface();
}

happy:happy:

参考文档


  1. 正确地使用 Kotlin 的 internal ↩︎ ↩︎

  2. Support more targets for @JvmSynthetic : KT-24981 (jetbrains.com) ↩︎

  3. Support ‘constructor’ target for JvmSynthetic annotation : KT-50609 (jetbrains.com) ↩︎

  4. Chapter 4. The class File Format (oracle.com) ↩︎

Sublime Text4代码配色自定义方案 关于Sublime Text对于我的使用体验,只能说内置的代码主题真的都太low了,一点都不好看。所以接下来我分享一下我自定义代码配色。当然,大家也可以通过我给的中文翻译注释来自定义自己喜欢的颜色。废话不多说,直接上代码直接将代码复制粘贴到:Preferences > Customize Color Scheme 即可。},"globals":"foreground": "var(white)", // 前景色。 阅读详情

相关推荐

果蔬及菜品识别系统

果蔬及菜品识别系统

我的博客 2116

Kotlin 元编程之 KSP 全面突破

没想到吧,这世上除了元宇宙,还有元编程,如果没有接触过,可能会有点懵,不过没关系,简单的说就是用代码来生成代码。实现元编程的传统常见手段主要是使用 APT注解处理器 +JavaPoet组合拳,如果你是作为一名Android 开发者,一定在曾经或者现在使用过很多知名的开源库,比如ARouter等,这些都是基于 注解处理器 +JavaPoet的方式实现的元编程,是的,虽然元编程这个词很高大上,但是可能你已经默默的使用了很多年了。

川峰的博客 5082

基于STM32F103标准库与DMA的实时音频FFT频谱显示系统设计

本文详细介绍了基于STM32F103芯片与DMA技术实现实时音频FFT频谱显示系统的设计方法。通过标准库驱动和FFT算法处理音频信号,结合OLED显示屏实时绘制音乐频谱,涵盖硬件选型、核心原理、开发环境搭建及性能优化等关键步骤,为数字信号处理应用提供实用参考。

yellow的专栏 775

Kotlin-KCP的应用-第二篇

前言 接Kotlin-KCP的应用-第一篇,本文是第二篇,以下是本文的目标: 记录如何简单搭建 KCP 开发环境 使用 KCP 解决第一篇中的问题 何为KCP?为何不使用KSP? KSP KSP 即 Kotlin Symbol Processing(Kotlin符号处理器),KSP 目前只能生成代码,不能修改字节码,第一篇中的问题需要修改字节码,因此 KSP 不能满足需求 KCP KCP 即 Kotlin Compiler Plugin(Kotlin编译器插件),在 kotlinc 过程中提供 hoo

孫小逗 1016

Linux内核4.14版本——drm框架分析(1)——drm简介

drm框架分析

yangguoyu8023的博客 1万+

避坑指南:C#调用CodeSoft打印标签时常见的5个错误及解决方法

本文总结了C#调用CodeSoft打印标签时常见的5个核心错误及解决方案,涵盖环境配置、模板加载、变量赋值、打印机切换和资源管理。重点解析了如何避免COM对象创建失败、模板路径错误、变量名不匹配、打印机切换异常及内存泄漏等问题,帮助开发者实现稳定高效的标签打印集成。

weixin_29215815的博客 223

深入浅出 Compose Compiler(1) Kotlin Compiler & KCP

Compose 的语法简洁、代码效率非常高,这主要得益于 Compose Compiler 的一系列编译期魔法,帮开发者生成了很多样板代码。但编译期插桩也阻碍了我们对于 Compose 运行原理的认知,想要真正读懂 Compose 就必须先了解它的 Compiler。本系列文章将带大家揭开 Compose Compiler 的神秘面纱。Compose 是一个 Kotlin Only 框架,所以 Compose Compiler 的本质是一个 KCP(Kotlin Compiler Plugin)。

移动端开发干货分享 2573

YOLOv11 改进 - 特征融合 | SSFF+TPE+CPAM 协同,ASF-YOLO 三重模块破解密集小目标分割难题

本文介绍了基于注意力尺度序列融合的ASF - YOLO模型在YOLOv11中的结合应用。ASF - YOLO旨在实现准确快速的细胞实例分割,结合空间和尺度特征,引入SSFF模块增强多尺度信息提取能力,TFE模块融合不同尺度特征图,CPAM整合前两者以关注信息丰富通道和小物体。我们将ASF - YOLO相关模块集成进YOLOv11,替代原有的部分特征融合模块,实现更高效的特征提取与融合。实验证明,YOLOv11 - ASFYOLO在细胞实例分割任务中表现出色,展现了该模型在深度学习中的广泛应用前景。

魔改工程师的博客 1256

Kotlin-KCP的应用-修改SDK版本号

背景 在 SDK 开发中,一般会暴露获取 SDK 版本号的接口,获取的版本号一般为 String 类型,比如: // sdk接口 interface Sdk { fun getVersion(): String } // sdk调用方 sdk.getVersion() 上述方式可以通过在 gradle.properties 中配置版本号,然后在 build.gradle 中读取版本号生成至 BuildConfig.java 中,例如: // gradle.properties VERSION=1

孫小逗 1192

智驾车载以太网(18)-switch之报文转发和vlan处理流程

本文详细解析了交换机中报文处理和转发流程:1)通过目标MAC查找转发端口,源MAC创建ARL条目;2)VLAN标签处理包括tagged/untagged帧判断、默认VID标记等5种处理方式;3)完整转发流程包含CRC校验、IQ封装、VLAN和ARL表查询、端口向量计算及优先级队列发送。文中还涉及TCAM修改VLAN、802.1q多种模式处理等细节,省略了流量控制等次要流程。该过程实现了基于MAC和VLAN的高效二层交换。

mitugaoyang1993的博客 41

用 XCO_CP_XLSX 在 ABAP 中写 Excel:从写入访问到样式控制的完整指南

本文介绍了在 ABAP 环境中使用 XCO_CP_XLSX 模块导出 Excel 报表的完整实践方案。文章从获取写入访问权限开始,详细讲解了基于 row_stream 的批量数据写入和基于 cursor 的精细控制两种方法,并提供了创建新文档、添加工作表、设置样式等关键操作示例。通过分层抽象的工作簿-工作表-单元格模型,开发者可以灵活应对企业应用中常见的报表导出需求,特别是在 SAP BTP 和 S/4HANA Cloud 环境下实现高效可靠的 Excel 文件生成与处理。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 162

Kotlin 移动端跨平台是种怎样的体验

前言 近些年,移动端无论是在技术还是在市场方面都已经逐步成熟,效率成为各家争夺存量 、开拓疆土的利器,因而跨平台技术的热度也如日中天。其中Flutter算得上是跨平台技术的网红,易上手、UI像素级同步、性能优异,引起一大波粉丝(各大厂)为它打call,各大厂商都为Flutter社区带来了很大贡献,迅速扩大了它的技术生态。实不相瞒,个人也是Flutter的小粉丝,既然Flutter这么好,那我还在...

a357664853的专栏 1251

go语言实现服务器server端与客户client端即时通信(附完整源码)

go语言实现服务器server端与客户client端即时通信(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 626

Sword - 为 Kotlin 函数增加代理功能(一)

Sword:一个可以给 Kotlin 函数增加代理的第三方库,基于 KCP 实现。

m0_64420071的博客 330

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

社区说 | Kotlin 编译器插件: 我们究竟在期待什么?

活动时间7月8日(本周四)20:00pm-21:00pm活动日程20:00-20:45《Kotlin 编译器插件:我们究竟在期待什么?》对比常见的 Kotlin 元编程的方案,包括反射、注...

Android开发者 394

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

社区说 | Kotlin 编译器插件:我们究竟在期待什么?

活动时间7月8日(本周四)20:00pm-21:00pm活动日程20:00-20:45《Kotlin 编译器插件:我们究竟在期待什么?》对比常见的 Kotlin 元编程的方案,包括反射、注...

谷歌开发者 571

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

拥抱Kotlin Symbol Processing(KSP),手把手带你实现Kotlin的专有注解处理

好玩系列 | 拥抱Kotlin Symbol Processing(KSP),项目实战 写在最前 这一篇,我们抱着拥抱新事物的心态,尝试一些新事物。笔者在这一次历程中,对三项事物进行了尝鲜: 手动迁移一个小规模的Gradle项目,由 Groovy Script 转为 Kotlin Script Kotlin Symbol Processing Kotlin Poet 这次的 重点是KSP ,Kotlin Poet学习成本比较低,迁移 Kotlin Script 仅仅是比较繁琐。 既然要实战,那么就需要一

leobert.lan的博客 2624

Kotlin Symbol Processing(KSP)使用初体验

近日,android官方发布了Kotlin Symbol Processing(KSP)的alpha版本。 Announcing Kotlin Symbol Processing (KSP) Alpha 为什么使用KSP? 很多人在使用Kotlin时的痛点之一就是编译速度过慢。 很多常见的三方库都通过注解简化模板代码,例如Room、Dagger、Retrofit等, Kotlin使用KAPT处理注解,KAPT没有专门的注解处理器,需要借助APT实现的,因为APT只能处理Java,所以KAPT需要生成A.

移动端开发干货分享 7556

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

/ 今日科技快讯 /近日,由IDC、浪潮信息、清华大学全球产业研究院联合编制的《2021-2022全球计算力指数评估报告》在北京发布,量化揭示了全球主要国家GDP、数字经济与计算力...

郭霖 447

手把手带你实现Kotlin的专有注解处理

/ 今日科技快讯 /据TechWeb消息,国外媒体报道称,分析师估计,由于需求增加,电动汽车制造商特斯拉今年第二季度可能交付约20万辆汽车,同比增长100%。今年4月初,特斯拉宣布...

郭霖 1160

Kotlin cacheable - 缓存一切函数

在 Kotlin 中,Lazy 是我们经常用到的操作,当我们需要用到时才创建对象,只需要一个 lazy 即可搞定,如下:

大模型训练 1021

从 Kotlin 到 KSP 再到项目实践 KtorfitX:一段探索之路

本文讲述了作者从初识Kotlin到深入探索KSP技术的历程。2021年接触Kotlin后,作者逐渐从使用语法糖转向研究其生态扩展能力,特别是KSP技术。基于KSP,作者开发了ktorfitx项目,解决了Kotlin Multiplatform中网络请求重复编码的问题。这段经历让作者深刻体会到技术真正的价值在于解放开发者创造力,而非单纯追求语法优雅。文章展现了Kotlin从语言到开发平台的演进,以及如何通过技术创新解决实际问题。

a2965502228的博客 507

Sword - 为 Kotlin 函数增加代理功能(四) - Kotlin IR

在第三篇文章的最后笔者有一个新的想法:通过 `Kotlin IR` 重新实现 `Sword` 的功能。经过最近几天晚上和早晨的努力,笔者初步实现了 `Sword` 的功能,可能还有一些问题,但是效果已经达到了笔者的预期,遂本篇文章记录下笔者的实现过程。

m0_59162559的博客 385

转载:Android 声明性布局Compose预研

一篇在上家公司写的,预研方案,放在公司的技术账号下面了。随着kotlin的流行,Compose越来月成熟,KCP(Kotlin Compiler Plugin)和KSP(Kotlin Symbol Processing API)的未来加持下,会走的很远。 Android 声明性布局Compose预研 ...

HUandroid的博客 245
上一篇: Android-TextView跑马灯探秘
下一篇: Kotlin-KCP的应用-第二篇
guodongAndroid
博客等级 码龄10年 16粉丝 19原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值