我报名参与金石计划1期挑战——瓜分10万奖池,这是我的第 2 篇文章,点击检查活动详情

简介

Sword:一个能够给 Kotlin 函数增加署理的第三方库,基于 KCP 完结。

前言

为什么写这个库呢?笔者在学习 Retrofit 时,写过一篇文章 Retrofit学习日记-动态署理,在这时候笔者就萌生了为函数/办法也增加署理功用的主意。

正好笔者之前就有把某个函数/办法标记为开发调试办法的需求:在开发调试阶段能够履行,正式发布之后就不可履行。要完结这种需求,笔者能够罗列下自己想到的计划:

  1. 在函数/办法中经过 BuildConfig#DEBUG 判断是否能够履行:此计划最简略,在编码期依靠开发人员手动完结,可是编码进程比较繁琐且索然寡味,
  2. 为函数/办法增加某个注解,经过修正函数/办法的字节码增加判断是否能够履行逻辑:此计划完结起来较为复杂,需求了解一些字节码信息,可是完结后使用起来比较方便且一了百了。

细心思考一下,其实能够发现:为函数/办法增加署理功用 能够完结 把某个函数/办法标记为开发调试办法的需求

经过一番思想斗争,笔者挑选了第二种计划,然后结合 为函数/办法也增加署理功用的主意 ,所以最终选型为:注解 + KCP + ASM。

又正好笔者之前写过几篇关于 KCP 的文章:

  1. Kotlin-KCP的使用-第一篇
  2. Kotlin-KCP的使用-第二篇
  3. Kotlin-KCP的使用-修正SDK版本号

在上面的 KCP 文章中,笔者记录了建立 KCP 开发环境的进程,经过阅览上面的文章能够快速建立一个 KCP 开发环境。

本文记录下 Sword 的项目结构及前期的开发环境建立,后续文章再分析 Sword 的中心代码部分,下面让我们先看看项目结构吧。

项目结构

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

  • api-kotlin:API 模块,界说相关的注解 API 类,
  • compiler:ksp 模块,辅佐 Sword 生成常量类,后续文章再讲,
  • kcp:kcp 模块,完结 Gradle Plugin 和 Kotlin Compiler Plugin。

API

Proxy

首要创立 API 模块并界说 Proxy 注解:

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class Proxy(
  /**
   * 是否启用, 默许True
   */
  val enable: Boolean = true,
​
  /**
   * [InvocationHandler]完结类的全限定名, 完结类必须有无参结构办法
   *
   * e.g. com.example.ProxyTestInvocationHandler
   */
  val handler: String = "",
)

Proxy 注解中有两个参数:

  1. enable:表明是否为函数启用署理,默许 True
  2. handler:表明署理函数处理类的全限定名此处理类必须完结 InvocationHandler 接口,且必须有无参结构办法

InvocationHandler

先看下 Java 动态署理接口 java.lang.reflect.InvocationHandler

// java
public interface InvocationHandler {
  
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

然后我们模仿 Java 的动态署理接口界说我们的接口:

// kt
interface InvocationHandler {
​
  fun invoke(className: String, methodName: String, args: Array<Any?>): Any?
}

两者是不是比较类似呢?可是后者的接口办法参数有所不同:

  1. className:表明当时署理办法所在的类名,
  2. methodName:表明当时署理办法的称号,
  3. args:表明当时署理办法的参数数组。

在完结方式上也有所不同:

  1. Java 动态署理是在运行时动态创立并加载 Class,然后经过反射调用,有必定的功能开支,
  2. Sword 牵强属于静态署理吧,在编译期修正字节码,没有经过反射调用,基本没有功能开支。

API 相关界说完结,接下来开始编写 KCP 吧。

KCP

对 KCP 不太了解的读者能够先看下上面罗列的几篇文章。这里再贴下 KCP 的架构图吧:

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

Gradle Plugin

class SwordGradlePlugin : KotlinCompilerPluginSupportPlugin {override fun apply(target: Project) = with(target) {
    logger.error("Welcome to guodongAndroid sword kcp gradle plugin.")
   }
​
  override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = trueoverride fun getCompilerPluginId(): String = BuildConfig.KOTLIN_PLUGIN_ID
​
  override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact(
    groupId = BuildConfig.KOTLIN_PLUGIN_GROUP,
    artifactId = BuildConfig.KOTLIN_PLUGIN_NAME,
    version = BuildConfig.KOTLIN_PLUGIN_VERSION,
   )
​
  override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider<List<SubpluginOption>> {
    val project = kotlinCompilation.target.project
    return project.provider { emptyList() }
   }
}

对现在的 Sword 来说,它不需求任何参数和配置项。所以 Gradle Plugin 就一个 SwordGradlePlugin 类,比较简略,在其间配置下 PluginId 和 KCP 的 SubpluginArtifact 即可。

Kotlin Compiler Plugin

CommandLineProcessor

@AutoService(CommandLineProcessor::class)
class SwordCommandLineProcessor : CommandLineProcessor {override val pluginId: String = BuildConfig.KOTLIN_PLUGIN_ID
​
  override val pluginOptions: Collection<AbstractCliOption> = emptyList()
}

CommandLineProcessor 也十分简略,只需配置 PluginId,没有任何参数和配置项。

ComponentRegistrar

@AutoService(ComponentRegistrar::class)
class SwordComponentRegistrar : ComponentRegistrar {override fun registerProjectComponents(
    project: MockProject,
    configuration: CompilerConfiguration
   ) {
    val messageCollector =
      configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
​
    messageCollector.report(
      CompilerMessageSeverity.WARNING,
      "Welcome to guodongAndroid sword kcp kotlin plugin"
     )
​
    ClassBuilderInterceptorExtension.registerExtension(
      project,
      SwordClassGenerationInterceptor(messageCollector)
     )
   }
}

ComponentRegistrar 的逻辑也很简略:首要获取 MessageCollector 用于日志输出,然后注册一个 ClassBuilder 阻拦器用于阻拦类的生成。

ClassBuilderInterceptorExtension

class SwordClassGenerationInterceptor(
  private val messageCollector: MessageCollector,
) : ClassBuilderInterceptorExtension {
​
  override fun interceptClassBuilderFactory(
    interceptedFactory: ClassBuilderFactory,
    bindingContext: BindingContext,
    diagnostics: DiagnosticSink
   ): ClassBuilderFactory = object : ClassBuilderFactory by interceptedFactory {
    override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder {
      return SwordClassBuilder(messageCollector, interceptedFactory.newClassBuilder(origin))
     }
   }
}

完结 ClassBuilderInterceptorExtension 接口办法 interceptClassBuilderFactory,返回一个 ClassBuilderFactory,在 newClassBuilder 中做阻拦,加入自己的处理逻辑,所以接下来的 ClassBuilder 算是 Sword 的中心代码了。

总结

本文只是记录了 Sword 的项目结构及前期的开发环境建立进程,能够看出 KCP 的开发环境建立是有迹可循,有模板可依,此次的建立进程与之前的 Mask 和 修正 SDK 版本号大同小异,笔者后续会供给一个 KCP 开发环境的模板工程供我们参考。

在学习或工作中有好的主意必定要及时记录下来,不要着急去完结你的主意,认真思考几种完结计划,细心衡量计划中的利害,挑选一个你以为好的计划后再开始做完结。

哦,对了,有主意必定要去实践,不要只是记录下来!

下篇再见,happy~