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

81 篇文章 1 订阅
79 篇文章 0 订阅

简介

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

  1. Sword - 为 Kotlin 函数增加代理功能(一)
  2. Sword - 为 Kotlin 函数增加代理功能(二)
  3. Sword - 为 Kotlin 函数增加代理功能(三)

前面三篇文章笔者记录了 Sword 的实现过程,如何使用 Sword 以及如何通过 KSPInvocationHandler 生成 FqName 索引类 HandlerFqName

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

Kotlin IR 是什么以及可以做什么,本文不再赘述,网上有不少资料,读者可以自行参考。

预期效果

假设有以下类(GetTextNoArgInvocationHandler)和函数(getTextNoArg()):

 @ProxyHandler("GET_TEXT_NO_ARG")
 class GetTextNoArgInvocationHandler : InvocationHandler {
 ​
     private val TAG = GetTextNoArgInvocationHandler::class.java.simpleName
 ​
     override fun invoke(className: String, methodName: String, args: Array<Any?>): Any? {
         Log.e(TAG, "invoke: className = $className, methodName = $methodName, args(${args.size}) = ${args.joinToString()}")
         return "guodongAndroid-Debug"
     }
 }
 ​
 // -----------------------------------------------------------------------------------------------------------------------
 ​
 @Proxy(
     enable = true,
     handler = HandlerFqName.GET_TEXT_NO_ARG
 )
 fun getTextNoArg() = "guodongAndroid"

通过 Kotlin IR 编译和 Sword 代理后,笔者期望 getTextNoArg 函数转换成类似下面的伪代码:

 @Proxy(
     enable = true,
     handler = HandlerFqName.GET_TEXT_NO_ARG
 )
 fun getTextNoArg(): String {
     return GetTextNoArgInvocationHandler().invoke("Test", "getTextNoArg", emptyArray()) as String
 }

SwordComponentRegistrar

要使用 IR 首先需要注册 IrGenerationExtension 扩展,修改之前 SwordComponentRegistrar 中的代码:

 class SwordComponentRegistrar : ComponentRegistrar {
     override fun registerProjectComponents(
         project: MockProject,
         configuration: CompilerConfiguration
     ) {
         val messageCollector =
             configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
 ​
         /*ClassBuilderInterceptorExtension.registerExtension(
             project,
             SwordClassGenerationInterceptor(messageCollector)
         )*/
 ​
         IrGenerationExtension.registerExtension(
             project,
             SwordIrGenerationExtension(messageCollector)
         )
     }
 }

在上面的代码中:

  1. 注释掉之前通过 ASM 修改字节码的 ClassBuilderInterceptorExtension 扩展,
  2. 新增 IrGenerationExtension 扩展。

Dump

IR 语法树中,所有的节点都实现了 IrElement 接口,这些节点可以是模块,包,文件,类,属性,函数,参数,表达式,函数调用、函数体等等。

那么这些节点是什么样子的呢?实现我们的 SwordIrGenerationExtension

 class SwordIrGenerationExtension(
     private val messageCollector: MessageCollector,
 ) : IrGenerationExtension {
     override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
         messageCollector.report(
             CompilerMessageSeverity.WARNING,
             moduleFragment.dump()
         )
     }
 }

使用 IrElement 的扩展函数 dump 可以输出这些节点的语法树信息。

在上面的代码中,我们输出了整个模块节点的语法树信息,如果模块中有许多文件,类,那么这些信息是相当庞大的,Sword 的目标是 Kotlin 函数,所以此处笔者不再贴出模块节点的语法树信息,等到我们转换函数时,再看看函数节点的语法树信息。

假设我们不知道如何编写 IR 编译器插件的代码,我们可以先写出要实现效果的 Kotlin 代码,再借助 dump 函数输出 IR 语法树信息,参考且对比语法树信息进行开发 IR 编译器插件,所以 dump 在开发 IR 编译器插件时非常有用。

IrElement

前面说了很多 IrElement 接口,目前我们还不知道它的真面目,接下来让我们看看它吧:

 interface IrElement {
     ...
 ​
     fun <R, D> accept(visitor: IrElementVisitor<R, D>, data: D): R
     fun <D> acceptChildren(visitor: IrElementVisitor<Unit, D>, data: D)
 ​
     fun <D> transform(transformer: IrElementTransformer<D>, data: D): IrElement
     fun <D> transformChildren(transformer: IrElementTransformer<D>, data: D)
 }

IrElement 中有四个接口函数,其中 accept 函数基于访问者模式访问各个节点,transform 函数又是基于 accept 函数提供修改节点语法树的能力。

accept 函数中的参数 IrElementVisitor 接口提供了访问各个节点的函数:

 interface IrElementVisitor<out R, in D> {

     fun visitElement(element: IrElement, data: D): R
     fun visitModuleFragment(declaration: IrModuleFragment, data: D) = visitElement(declaration, data)
     fun visitPackageFragment(declaration: IrPackageFragment, data: D) = visitElement(declaration, data)
     fun visitFile(declaration: IrFile, data: D) = visitPackageFragment(declaration, data)

     fun visitDeclaration(declaration: IrDeclarationBase, data: D) = visitElement(declaration, data)
     fun visitClass(declaration: IrClass, data: D) = visitDeclaration(declaration, data)
     fun visitFunction(declaration: IrFunction, data: D) = visitDeclaration(declaration, data)
     ...
 }

IrElementVisitor 中有非常多接口函数,上面代码片段列举了一些接口函数,这些接口函数大多数都有默认实现,仔细观察这些函数的默认实现,最后都直接或间接的调用到 visitElement 函数。

transform 函数中的参数 IrElementTransformer 接口继承自 IrElementVisitor 接口,IrElementTransformer 接口主要是实现了 IrElementVisitor 的接口函数并调用 IrElement.transformChildren 函数遍历节点修改节点的语法树:

 interface IrElementTransformer<in D> : IrElementVisitor<IrElement, D> {
     override fun visitElement(element: IrElement, data: D): IrElement {
         element.transformChildren(this, data)
         return element
     }
 ​
     override fun visitModuleFragment(declaration: IrModuleFragment, data: D): IrModuleFragment {
         declaration.transformChildren(this, data)
         return declaration
     }
 ​
     override fun visitFile(declaration: IrFile, data: D): IrFile {
         declaration.transformChildren(this, data)
         return declaration
     }
 }

Sword 中,我们主要利用 transform 函数修改节点语法树的能力来实现为 Kotlin 函数增加代理功能。

IrType & IrSymbol

IR 中不仅有 IrElement 还有 IrTypeIrSymbol 。那么这两个有什么作用呢?

IrType

IrType 可以说是 KotlinTypeIR 中的另一种表现形式,表示 Kotlin 中的各种类型,比如 AnyBooleanIntString 等等。IrType 常用在比较函数参数类型,调用函数时传入参数类型等,举个 Sword 中的栗子:

 private val anyNType = pluginContext.irBuiltIns.anyNType
 private val stringType = pluginContext.irBuiltIns.stringType
 private val arrayAnyNType = pluginContext.irBuiltIns.arrayClass.typeWith(anyNType)
 ​
 // `InvocationHandler.invoke(className: String, methodName: String, args: Array<Any?>): Any?`
 val invokeSymbol =
     pluginContext.referenceFunctions(FqName("${param.handler}.$INVOKE_METHOD_NAME"))
         .single {
             val valueParameters = it.owner.valueParameters
             valueParameters.size == 3 &&
             valueParameters[0].type == stringType &&
             valueParameters[1].type == stringType &&
             valueParameters[2].type == arrayAnyNType
         }

在上面的代码片段中 pluginContextIrGenerationExtension.generate 函数中的第二个参数,通过它的 irBuiltIns 字段我们获取一些 Kotlin 内置的 IrType

  • anyNType 表示 Kotlin 中的 Any? 类型,
  • stringType 表示 String 类型,如果想获取 String? 类型,则需要调用 stringType.makeNullable()
  • arrayAnyNType 表示 Array<Any?> 类型,Array 没有对应的 IrType 表示,我们需要先获取 Array 对应的 IrSymbol,再调用 typeWith 扩展函数传入所需泛型的 IrType 即可获取 Array<Any?>IrType

在上面代码片段的最后,笔者的目标是获取 InvocationHandler.invoke 函数的 IrSymbol,考虑到开发者可能会重载 invoke 函数,所以笔者增加了以下判断逻辑:

  1. 函数中有且仅有三个参数,
  2. 第一个参数的类型必须是 String 类型,
  3. 第二个参数的类型必须是 String 类型,
  4. 第三个参数的类型必须是 Array<Any?> 类型。

满足以上几个条件的函数笔者才认为是 InvocationHandler.invoke 函数。

IrSymbol

IrSymbolKotlin IR 中可以算是比较重要的一个接口了。它以「符号」的形式描述了 Kotlin 的包、文件、类、函数、属性、字段等,笔者把它理解为 Java 字节码中的描述符,所以 IrSymbol 常用在创建类、函数、属性,函数调用等,举个 Sword 中的栗子:

 private val anyNType = pluginContext.irBuiltIns.anyNType
 private val emptyArraySymbol = pluginContext.referenceFunctions(FqName("kotlin.emptyArray")).first()
 ​
 irCall(emptyArraySymbol).also {
     it.putTypeArgument(0, anyNType)
 }
  • anyNType 在上节中出现过,它表示 Any? 类型,
  • emptyArraySymbolemptyArray() 函数在 IR 中的符号,我们同样可以通过 irBuiltIns 获取一些 Kotlin 内置的 IrSymbol其他的符号可以通过 pluginContext.referenceXXX() 的一系列函数查找
  • 接下来调用 irCall 函数并传入 emptyArraySymbol,最后调用 putTypeArgument() 函数设置 emptyArray() 函数的泛型。

所以上面的代码片段其实是调用 Kotlin 中的 emptyArray<Any?>() 函数。

Sword

SwordIrGenerationExtension

 class SwordIrGenerationExtension(
     private val messageCollector: MessageCollector,
 ) : IrGenerationExtension {
 ​
     private val proxyAnnotationFqName = FqName("com.guodong.android.sword.api.kt.Proxy")
 ​
     override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
         // 输出日志
         messageCollector.report(
             CompilerMessageSeverity.WARNING,
             "Welcome to guodongAndroid sword kcp kotlin ir plugin"
         )
 ​
         // 判断当前模块是否存在 `Proxy` 注解
         val proxyAnnotation = pluginContext.referenceClass(proxyAnnotationFqName)
         if (proxyAnnotation == null) {
             messageCollector.report(
                 CompilerMessageSeverity.ERROR,
                 "Not found `Proxy` annotation, make sure to add the "sword-api-kt" library to your dependencies"
             )
             return
         }
 ​
         // 开始转换
         moduleFragment.transform(
             SwordTransformer(pluginContext, proxyAnnotation, messageCollector),
             null
         )
     }
 }

经过前面的知识铺垫, SwordIrGenerationExtension 中的代码逻辑相信读者应该能理解了,笔者这里就不再赘述了,没有理解的读者可以再回顾下前面的内容。

接下来我们主要看看 SwordTransformer 中的逻辑。

SwordTransformer

因为 Sword 的功能是为 Kotlin 函数增加代理功能,所以在 SwordTransformer 中我们仅关注与函数相关的转换函数,即:visitFunctionNew(declaration: IrFunction) 函数。

首先声明一些变量和常量,其中一些变量前面笔者已经介绍过:

 companion object {
     // `InvocationHandler` 中的 `invoke` 函数名称
     private const val INVOKE_METHOD_NAME = "invoke"
 }
 ​
 private val anyNType = pluginContext.irBuiltIns.anyNType
 private val stringType = pluginContext.irBuiltIns.stringType
 private val arrayAnyNType = pluginContext.irBuiltIns.arrayClass.typeWith(anyNType)
 private val emptyArraySymbol = pluginContext.referenceFunctions(FqName("kotlin.emptyArray")).first()
 private val arrayOfSymbol = pluginContext.irBuiltIns.arrayOf
 ​
 // @JvmName 注解完全限定名
 private val jvmNameAnnotationFqName = FqName("kotlin.jvm.JvmName")
 ​
 // @Proxy 注解完全限定名
 private val proxyAnnotationFqName = FqName("com.guodong.android.sword.api.kt.Proxy")

下面我们就覆写 visitFunctionNew() 函数,在覆写的 visitFunctionNew() 函数中有较多的代码逻辑,还是老习惯,笔者先描述下自己的实现思路,然后再根据实现思路依次进行代码实现:

  1. 过滤一些笔者认为不需要处理的函数,读者可以自行斟酌;过滤不包含 Proxy 注解的函数,
  2. 获取 Proxy 注解里的数据存储于第二篇文章中的 SwordParam 中,
  3. 获取当前类名和函数名,判断当前函数是否启用代理,如果启用了代理但是 handler 为空则抛出异常,
  4. 启用代理后,我们直接抛弃原函数体,生成新的代理函数体。
1.过滤
 override fun visitFunctionNew(declaration: IrFunction): IrStatement {
     // 过滤挂起函数,内联函数,多平台声明函数,外部函数(JNI)
     if (declaration.isSuspend || declaration.isInline || declaration.isExpect || declaration.isExternal) {
         return super.visitFunctionNew(declaration)
     }
 ​
     if (declaration is IrSimpleFunction) {
         // 过滤中缀函数,尾递归函数,操作符函数
         if (declaration.isInfix || declaration.isTailrec || declaration.isOperator) {
             return super.visitFunctionNew(declaration)
         }
     }
 ​
     // 过滤函数体为空的函数,不包含 `Proxy` 注解的函数
     if (declaration.body == null || !declaration.hasAnnotation(annotationClass)) {
         return super.visitFunctionNew(declaration)
     }

     ......
 }
  1. 过滤挂起函数,内联函数,多平台声明函数,外部函数(JNI),
  2. 过滤中缀函数,尾递归函数,操作符函数,
  3. 过滤函数体为空的函数,不包含 Proxy 注解的函数。
2.获取
 override fun visitFunctionNew(declaration: IrFunction): IrStatement {
     ......
 ​
     val param = SwordParam()
     param.hasProxyAnnotation = true
 ​
     // 获取函数上 `Proxy` 注解的 `IrElement`, 返回 `IrConstructorCall`
     val irProxyConstructorCall = declaration.annotations.filter {
         it.isAnnotation(proxyAnnotationFqName)
     }.toList().single()
 ​
     // 获取 `Proxy.enable` 属性的值并存储
     val enableParam = irProxyConstructorCall.getValueArgument(0)
     enableParam?.let {
         if (it is IrConst<*>) {
             param.enable = it.value as Boolean
         }
     }
 ​
     // 获取 `Proxy.handler` 属性的值并存储
     val handlerParam = irProxyConstructorCall.getValueArgument(1)
     handlerParam?.let {
         if (it is IrConst<*>) {
             param.handler = it.value as String
         }
     }
 ​
     ......
 }

获取 Proxy 注解中的数据,比较麻烦一些,一开始笔者认为获取到的注解可能类似于 IrAnnotation,然而发现却是 IrConstructorCall,后面仔细想来注解不就是通过构造函数构建一个注解实例么?我们在注解中传入的参数都是赋值给了其构造函数的属性。

通过 getValueArgument() 函数根据注解中声明属性的顺序获取其对应的属性,因为属性可能有默认值,我们在使用时可以不传入某个属性,比如:

 @Proxy(
     // enable = true,
     handler = HandlerFqName.GET_TEXT_NO_ARG
 )
 fun getTextNoArg() = "guodongAndroid"

Proxy 注解中 enable 默认为 True,所以我们在使用时可以不传入 enable 的值,使其使用默认值,但是对于 kotlin IR 来说,没有明确使用的属性,通过 getValueArgument() 函数获取到的为 null,因为在 Kotlin IR 语法树中找不到这个属性。

由于注解中的属性值必须是编译期常量,所以我们可以把 handlerParam 转换为 IrConst 并获取它的值。

3.校验
 override fun visitFunctionNew(declaration: IrFunction): IrStatement {
     ......
 ​
     // 获取 ClassName
     val className: String = getClassName(declaration)

     // 获取 MethodName
     val methodName = declaration.name.asString()
 ​
     // 校验 开启代理后是否注入了 `handler`
     if (param.enable && (param.handler.isEmpty() || param.handler.isBlank())) {
         messageCollector.report(
             CompilerMessageSeverity.ERROR,
             "[$className.$methodName]启用代理后请注入`handler`",
         )
     }
 ​
     ......
 }
 ​
 private fun getClassName(
     declaration: IrFunction,
 ): String {
     val parentClassOrNull = declaration.parentClassOrNull
     val fileOrNull = declaration.fileOrNull
 ​
     return when {
         declaration.isLocal -> {
             // 本地方法: 类名.函数名.<anonymous>
             // 源码中有此逻辑, 逻辑较为繁琐,且不是 `Sword` 的核心逻辑,本文就不记录了
         }
         parentClassOrNull != null -> {
             // 获取类名
             parentClassOrNull.name.asString()
         }
         fileOrNull != null -> {
             // 如果是顶级函数,获取文件名或`JvmName`注解指定的名字
             val annotations = fileOrNull.annotations
             if (annotations.hasAnnotation(jvmNameAnnotationFqName)) {
                 val annotation = annotations.findAnnotation(jvmNameAnnotationFqName)!!
                 val expression = annotation.getValueArgument(0)
                 if (expression != null && expression is IrConst<*>) {
                     expression.value as String
                 } else {
                     fileOrNull.name
                 }
             } else {
                 fileOrNull.name
             }
         }
         else -> "Unknown"
     }
 }

获取 ClassName 时有以下几点考虑:

  1. 首先判断是否是本地方法,如果是本地方法则获取 类名.方法名.[<anonymous>]
  2. 其次获取当前函数所在的父级 IrClass,如果不为 null,则使用类名,
  3. 最后获取函数所在的 IrFile,如果不为 null,再判断文件上是否有 JvmName 注解,有的话使用 JvmName 注解指定的名字,否则使用文件名。

获取 MethodName 时直接使用了函数名称,此处没有判断函数上是否有 JvmName 注解逻辑,相信读者可以自行扩展。

下面就是一个开启代理后必须注入 handler 的校验逻辑。

4.转换

在前面介绍 dump 函数时我们并没有实际上看看 dump 函数的输出内容,接下来让我们看看它输出的语法树信息。

以下面的函数为例:

 @Proxy(
     enable = true,
     handler = HandlerFqName.GetTextArgInvocationHandler
 )
 fun testHandler(): User {
     return GetTextArgInvocationHandler().invoke("Test", "testHandler", emptyArray()) as User
 }

dump 函数的输出结果如下:

 FUN name:testHandler visibility:public modality:FINAL <> ($this:com.guodong.android.sword.app.Test) returnType:com.guodong.android.sword.app.User
   annotations:
     Proxy(enable = 'true', handler = 'com.guodong.android.sword.app.GetTextArgInvocationHandler')
   $this: VALUE_PARAMETER name:<this> type:com.guodong.android.sword.app.Test
   BLOCK_BODY
     RETURN type=kotlin.Nothing from='public final fun testHandler (): com.guodong.android.sword.app.User declared in com.guodong.android.sword.app.Test'
       TYPE_OP type=com.guodong.android.sword.app.User origin=CAST typeOperand=com.guodong.android.sword.app.User
         CALL 'public open fun invoke (className: kotlin.String, methodName: kotlin.String, args: kotlin.Array<kotlin.Any?>): kotlin.Any? declared in com.guodong.android.sword.app.GetTextArgInvocationHandler' type=kotlin.Any? origin=null
           $this: CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in com.guodong.android.sword.app.GetTextArgInvocationHandler' type=com.guodong.android.sword.app.GetTextArgInvocationHandler origin=null
           className: CONST String type=kotlin.String value="Test"
           methodName: CONST String type=kotlin.String value="testHandler"
           args: CALL 'public final fun emptyArray <T> (): kotlin.Array<T of kotlin.ArrayIntrinsicsKt.emptyArray> [inline] declared in kotlin.ArrayIntrinsicsKt' type=kotlin.Array<kotlin.Any?> origin=null
             <T>: kotlin.Any?

fun testHandler(): User

 FUN name:testHandler visibility:public modality:FINAL <> ($this:com.guodong.android.sword.app.Test) returnType:com.guodong.android.sword.app.User

这是 testHandler 函数的定义。它定义了函数的名称,可见性,模态以及类型签名。我们可以清楚的看到它是一个 publicfinal 名为 testHandler 的函数,并且它有一个隐含的参数 this()),但是没有类型参数(<>),最后返回值为 com.guodong.android.sword.app.User

类中的非静态函数(构造函数除外)都有一个隐含的 this 参数:

 $this: VALUE_PARAMETER name:<this> type:com.guodong.android.sword.app.Test

GetTextArgInvocationHandler()

 $this: CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in com.guodong.android.sword.app.GetTextArgInvocationHandler' type=com.guodong.android.sword.app.GetTextArgInvocationHandler origin=null

调用 GetTextArgInvocationHandler 的无参构造方法。

Test & testHandler

 className: CONST String type=kotlin.String value="Test"
 methodName: CONST String type=kotlin.String value="testHandler"

TesttestHandler 作为一个常量字符串。

emptyArray()

 args: CALL 'public final fun emptyArray <T> (): kotlin.Array<T of kotlin.ArrayIntrinsicsKt.emptyArray> [inline] declared in kotlin.ArrayIntrinsicsKt' type=kotlin.Array<kotlin.Any?> origin=null
             <T>: kotlin.Any?

emptyArray() 函数是有类型参数的,因编译器推导我们在编写代码时可以省略,但是在 Kotlin IR 中明确显示了它的类型参数(<T>: kotlin.Any?),所以我们在转换代码时需要注意此处细节,否则转换代码在编译期不会报错,在运行时会抛出异常

invoke("Test", "testHandler", emptyArray())

 CALL 'public open fun invoke (className: kotlin.String, methodName: kotlin.String, args: kotlin.Array<kotlin.Any?>): kotlin.Any? declared in com.guodong.android.sword.app.GetTextArgInvocationHandler' type=kotlin.Any? origin=null

这是 invoke 函数的调用,清楚表示调用 invoke 函数需要三个参数以及参数的类型,同时函数返回值为 kotlin.Any?

as String

 TYPE_OP type=com.guodong.android.sword.app.User origin=CAST typeOperand=com.guodong.android.sword.app.User

这是强转操作,TYPE_OP 表示一种类型操作,origin=CAST 表示类型操作符。

return

 RETURN type=kotlin.Nothing from='public final fun testHandler (): com.guodong.android.sword.app.User declared in com.guodong.android.sword.app.Test'

Kotlinreturn 其实是一个表达式,所以此处 returntypekotlin.Nothing

通过上面 dump 函数输出语法树的分析,我们已经知道转换后函数体的语法信息,接下来让我们根据上面的分析依次来实现吧。

 val handlerConstructorSymbol =
     pluginContext.referenceConstructors(FqName(param.handler)).single {
         it.owner.valueParameters.isEmpty()
     }

首先通过 pluginContext.referenceConstructors() 查找 Handler 类的无参构造函数的符号。

 val invokeSymbol =
     pluginContext.referenceFunctions(FqName("${param.handler}.$INVOKE_METHOD_NAME"))
         .single {
             val valueParameters = it.owner.valueParameters
             valueParameters.size == 3 &&
                     valueParameters[0].type == stringType &&
                     valueParameters[1].type == stringType &&
                     valueParameters[2].type == arrayAnyNType
         }

接下来通过 pluginContext.referenceFunctions() 查找 Handler 类的 invoke 函数,可能存在函数重载,需要通过 single 函数确定我们需要的函数,这个在前面 IrType 举过例子。

 private fun IrBuilderWithScope.irSwordArrayParams(function: IrFunction): IrCall {
     val parameters = function.valueParameters
     return if (parameters.isEmpty()) {
         irCall(emptyArraySymbol).also {
             it.putTypeArgument(0, anyNType)
         }
     } else {
         irCall(arrayOfSymbol).also {
             val expressions = parameters.map { parameter -> irGet(parameter) }
             it.putValueArgument(0, irVararg(anyNType, expressions))
         }
     }
 }

上面代码是组装 invoke 函数的第三个参数 args: Array<Any?>,当前函数没有参数时使用 emptyArray<Any?>(),有参数时使用 arrayOf(vararg elements: T)

 val invokeCall = irCall(invokeSymbol).apply {
     dispatchReceiver = irCallConstructor(handlerConstructorSymbol, emptyList())
     putValueArgument(0, irString(className))
     putValueArgument(1, irString(methodName))
     putValueArgument(2, irSwordArrayParams(function))
 }

构造 Handler 实例并调用它的 invoke 函数。

 val irReturn = irReturn(
     typeOperator(
         resultType = function.returnType,
         argument = invokeCall,
         typeOperator = IrTypeOperator.CAST,
         typeOperand = function.returnType
     )
 )

typeOperator 是语法树中的 TYPE_OP,就是强转操作,irReturn 表示 Kotlin 中的 return 表达式。

 private fun irSword(
     function: IrFunction,
     param: SwordParam,
     className: String,
     methodName: String,
 ): IrBlockBody {
     return DeclarationIrBuilder(pluginContext, function.symbol).irBlockBody {
         ......

         +irReturn
     }
 }

通过 + 操作符(unaryPlus)链接整个返回表达式到函数体,最后用新的函数体替换原函数体达到代理的功能:

 override fun visitFunctionNew(declaration: IrFunction): IrStatement {
     ......
 ​
     if (param.enable) {
         declaration.body = irSword(declaration, param, className, methodName)
     }
 ​
     return super.visitFunctionNew(declaration)
 }

至此,使用 Kotlin IRKotlin 函数增加代理功能完成。

总结

本文简单记录了通过 Kotlin IR 实现 Sword 代理功能的过程,同时简单介绍了一些 Kotlin IR 的 API 以及笔者对这些 API 的个人理解。

希望可以帮您开发自己的 Kotlin 编译器插件,happy~

作者:guodongAndroid
链接:https://juejin.cn/post/7157576701986209828

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值