转载

Kotlin 实践及原理

关于Kotlin在项目中的实践经验,及在Android平台的编译原理

Kotlin 实践及原理

语法回顾

常量与变量

  • Java
String name = "Amit Shekhar";
final String name = "Amit Shekhar";
  • Kotlin
var name = "Amit Shekhar"
val name = "Amit Shekhar"

空判断

  • Java
if (text != null) {
    int length = text.length();
}
  • Kotlin
val length = text?.length()

字符串拼接

  • Java
String firstName = "Amit";
String lastName = "Shekhar";
String message = "My name is: " + firstName + " " + lastName;
  • Kotlin
val firstName = "Amit"
val lastName = "Shekhar"
val message = "My name is: $firstName $lastName"

三元表达式

  • Java
String text = x > 5 ? "x > 5" : "x <= 5";
  • Kotlin
val text = if (x > 5) "x > 5" else "x <= 5"

更灵活的case语句

  • Java
int score = // some score;
String grade;
switch (score) {
    case 10:
    case 9:
        grade = "Excellent";
        break;
    case 8:
    case 7:
    case 6:
        grade = "Good";
        break;
    case 5:
    case 4:
        grade = "OK";
        break;
    case 3:
    case 2:
    case 1:
        grade = "Fail";
        break;
    default:
        grade = "Fail";
}
  • Kotlin
var score = // some score
var grade = when (score) {
    9, 10 -> "Excellent"
    in 6..8 -> "Good"
    4, 5 -> "OK"
    in 1..3 -> "Fail"
    else -> "Fail"
}

方法定义

  • Java
int getScore() {
   // logic here
   return score;
}
  • Kotlin
fun getScore(): Int {
   // logic here
   return score
}

// as a single-expression function

fun getScore(): Int = score

Get Set 构造器

  • Java
public class Developer {

    private String name;
    private int age;

    public Developer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Developer developer = (Developer) o;

        if (age != developer.age) return false;
        return name != null ? name.equals(developer.name) : developer.name == null;

    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

    @Override
    public String toString() {
        return "Developer{" +
                "name='" + name + '/'' +
                ", age=" + age +
                '}';
    }
}
  • Kotlin
data class Developer(val name: String, val age: Int)

类继承、实现接口

  • Java
public class Child extends Parent implements IHome {
    
}
  • kotlin
class Child : Parent(), IHome {
    
}

与Java互操作

相互调用

import java.util.*

fun demo(source: List<Int>) {
    val list = ArrayList<Int>()
    // “for”-循环用于 Java 集合:
    for (item in source) {
        list.add(item)
    }
    // 操作符约定同样有效:
    for (i in 0..source.size - 1) {
        list[i] = source[i] // 调用 get 和 set
    }
}

空安全与平台类型

val list = ArrayList<String>() // 非空(构造函数结果)
list.add("Item")
val size = list.size // 非空(原生 int)
val item = list[0] // 推断为平台类型(普通 Java 对象)
item.substring(1) // 允许,如果 item == null 可能会抛出异常

静态字段

// 文件 example.kt

object Obj {
    const val CONST = 1
}

class C {
    companion object {
        const val VERSION = 9
    }
}

const val MAX = 239

int c = Obj.CONST;
int d = ExampleKt.MAX;
int v = C.VERSION;

kotlin 实践经验

优点

  • 语法简洁,能比java减少40%的代码,也能节约大量的时间
  • 语法级别的安全
  • 目前版本已较为稳定

缺点

  • 可能会有额外的开销
  • 少量的特性支持的还不健全,尤其在与Java互操作上,比如lateinit特性

坑、隐藏开销

kotlin代码是很简洁,但是简洁下面有时候会隐藏较大的开销。

伴生对象

如果我们需要创建类似Java中的静态成员,需要创建伴生对象,伴生对象通过 companion object 创建,如下:

class Test {
    companion object {
        val version = 0
    }
}

转换为同等的Java代码,如下:

public final class Test {
   private static final int version = 0;
   public static final Test.Companion Companion = new Test.Companion((DefaultConstructorMarker)null);

   public static final class Companion {
      public final int getVersion() {
         return Test.version;
      }

      private Companion() {
      }

      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

也就是会多产生一次的函数调用开销,不过可以把 val version 改为 const val version 避免这个问题

装箱拆箱

class Test {
    val a: IntArray = intArrayOf(1)
    val b: Array<Int> = arrayOf(1)
    val c: Array<Int?> = arrayOf(null)
}

转为Java如下:

@NotNull
private final int[] a = new int[]{1};
@NotNull
private final Integer[] b = new Integer[]{1};
@NotNull
private final Integer[] c = new Integer[]{(Integer)null};

后两种产生了装箱处理,产生开销

For循环

kotlin 提供了downTo step until reversed函数简单使用循环,但这些函数组合使用也有可能产生较多的临时对象。

回滚

Tools ->Kotlin ->Show Kotlin Bytecode -> Decompile

Kotlin 编译原理

我们对kotlin比较大的疑问可能是kotlin是怎么和java混编的?或者说kotlin是怎么生成字节码的

kotlin整个都是开源的,可以从github clone下来,地址: https://github.com/JetBrains/kotlin

整个工程很庞大,源代码大概有四百多万行,可以使用 IntelliJ IDEA查看整个工程,具体操作可以看github 项目主页的建议。

编译流程图:

Kotlin 实践及原理

kotlin的maven id 为kotlin-gradle-plugin,我们做下全局搜索,发现路径为:root/libraries/tools/kotlin-gradle-plugin

每个插件都会有入口类,我们在module配置时都会添加:apply plugin: ‘kotlin-android’,kotlin-android代表的就是配置入口类文件的名字,所以我们看下下kotlin-android.properties文件内容,如下:

implementation-class=org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper

我们看到插件入口类为 KotlinAndroidPluginWrapper,接下来我们就从这个入口类分析下kotlin编译过程。

KotlinAndroidPluginWrapper 源码如下:

open class KotlinAndroidPluginWrapper @Inject constructor(
    fileResolver: FileResolver,
    protected val registry: ToolingModelBuilderRegistry
) : KotlinBasePluginWrapper(fileResolver) {
    override fun getPlugin(project: Project, kotlinGradleBuildServices: KotlinGradleBuildServices): Plugin<Project> =
        KotlinAndroidPlugin(kotlinPluginVersion, registry)
}

真正的实现是在 KotlinAndroidPlugin 中,源码如下:

internal open class KotlinAndroidPlugin(
    private val kotlinPluginVersion: String,
    private val registry: ToolingModelBuilderRegistry
) : Plugin<Project> {

    override fun apply(project: Project) {
        val androidTarget = KotlinAndroidTarget("", project)
        val tasksProvider = AndroidTasksProvider(androidTarget.targetName)

        applyToTarget(
            project, androidTarget, tasksProvider,
            kotlinPluginVersion
        )
        registry.register(KotlinModelBuilder(kotlinPluginVersion, androidTarget))
    }

    companion object {
        fun applyToTarget(
            project: Project,
            kotlinTarget: KotlinAndroidTarget,
            tasksProvider: KotlinTasksProvider,
            kotlinPluginVersion: String
        ) {
			// 省略无关代码
            
            val variantProcessor = if (compareVersionNumbers(version, legacyVersionThreshold) < 0) {
                LegacyAndroidAndroidProjectHandler(kotlinTools)
            } else {
                val android25ProjectHandlerClass = Class.forName("org.jetbrains.kotlin.gradle.plugin.Android25ProjectHandler")
                val ctor = android25ProjectHandlerClass.constructors.single {
                    it.parameterTypes.contentEquals(arrayOf(kotlinTools.javaClass))
                }
                ctor.newInstance(kotlinTools) as AbstractAndroidProjectHandler<*>
            }

            variantProcessor.handleProject(project, kotlinTarget)
        }
    }
}

插件加载首先执行的是apply函数,跟进applyToTarget函数,省略掉无关代码,重点在最后一句handleProject

fun handleProject(project: Project, kotlinAndroidTarget: KotlinAndroidTarget) {
    
  // ignore ..
  forEachVariant(project) {
         processVariant(
            it, kotlinAndroidTarget, project, ext, plugin, kotlinOptions, kotlinConfigurationTools.kotlinTasksProvider
            )
        }
  // ignore ..
}

省略掉无关代码,可以代码在processVariant,跟进去看下

private fun processVariant(
    variantData: V,
    target: KotlinAndroidTarget,
    project: Project,
    androidExt: BaseExtension,
    androidPlugin: BasePlugin,
    rootKotlinOptions: KotlinJvmOptionsImpl,
    tasksProvider: KotlinTasksProvider
) {
    // ignore ..
    // 创建 kotlin 任务
    val kotlinTask = tasksProvider.createKotlinJVMTask(project, kotlinTaskName, compilation)
    // ignore ..
    wireKotlinTasks(project, compilation, androidPlugin, androidExt, variantData, javaTask, kotlinTask)
}

其中会创建 kotlin 任务,创建任务入口先留意一下,先看下 wireKotlinTasks 实现:

override fun wireKotlinTasks(
    project: Project,
    compilation: KotlinJvmAndroidCompilation,
    androidPlugin: BasePlugin,
    androidExt: BaseExtension,
    variantData: BaseVariantData<out BaseVariantOutputData>,
    javaTask: AbstractCompile,
    kotlinTask: KotlinCompile
) {
    kotlinTask.dependsOn(*javaTask.dependsOn.toTypedArray())
    
    configureJavaTask(kotlinTask, javaTask, logger)
}

configureJavaTask 比较可疑,跟进去看下:

internal fun configureJavaTask(kotlinTask: KotlinCompile, javaTask: AbstractCompile, logger: Logger) {
    // ignore ..
    javaTask.dependsOn(kotlinTask)
	// ignore ..
}

我们看到函数核心是定义了kotlin task在java task之前执行,ok,那我们接下来跟进kotlin task的实现,我们返回上面的创建kotlin task的地方: tasksProvider.createKotlinJVMTask(project, kotlinTaskName, compilation) ,跟进去:

open fun createKotlinJVMTask(
    project: Project,
    name: String,
    compilation: KotlinCompilation
): KotlinCompile {
    val properties = PropertiesProvider(project)
    val taskClass = taskOrWorkersTask<KotlinCompile, KotlinCompileWithWorkers>(properties)
    return project.tasks.create(name, taskClass).apply {
        configure(this, project, properties, compilation)
    }
}

大致意思就是根据任务名称创建任务,任务名称就来自泛型中定义的两个,那我们选择KotlinCompileWithWorkers,看下是如何定义的。

internal open class KotlinCompileWithWorkers @Inject constructor(
    @Suppress("UnstableApiUsage") private val workerExecutor: WorkerExecutor
) : KotlinCompile() {
    override fun compilerRunner() = GradleCompilerRunnerWithWorkers(this, workerExecutor)
}

看来是覆写了父类的compilerRunner,我们跟进去看看GradleCompilerRunnerWithWorkers的实现:

internal class GradleCompilerRunnerWithWorkers(
    task: Task,
    private val workersExecutor: WorkerExecutor
) : GradleCompilerRunner(task) {
    override fun runCompilerAsync(workArgs: GradleKotlinCompilerWorkArguments) {
        project.logger.kotlinDebug { "Starting Kotlin compiler work from task '${task.path}'" }
        // todo: write tests with Workers enabled;
        workersExecutor.submit(GradleKotlinCompilerWork::class.java) { config ->
            config.isolationMode = IsolationMode.NONE
            config.forkMode = ForkMode.NEVER
            config.params(workArgs)
        }
    }

}

核心是提交了一个 runnable,这就比较明确了,我们看下GradleKotlinCompilerWork的实现,重点看run的实现:

override fun run() {
    // ignore ..
    val exitCode = try {
        compileWithDaemonOrFallbackImpl()
    } catch (e: Throwable) {
        clearLocalStateDirectories(log, localStateDirectories, "exception when running compiler")
        throw e
    } finally {
        if (buildFile != null && System.getProperty(DELETE_MODULE_FILE_PROPERTY) != "false") {
            buildFile.delete()
        }
    }
	// ignore ..
}

run 里面的核心就是compileWithDaemonOrFallbackImpl函数,跟进去:

private fun compileWithDaemonOrFallbackImpl(): ExitCode {
		// ignore
        if (executionStrategy == DAEMON_EXECUTION_STRATEGY) {
            val daemonExitCode = compileWithDaemon()
        }
        // ignore
    }

核心代码为:compileWithDaemon(),跟进去:

private fun compileWithDaemon(): ExitCode? {
	// ignore
    val targetPlatform = when (compilerClassName) {
        KotlinCompilerClass.JVM -> CompileService.TargetPlatform.JVM
        KotlinCompilerClass.JS -> CompileService.TargetPlatform.JS
        KotlinCompilerClass.METADATA -> CompileService.TargetPlatform.METADATA
        else -> throw IllegalArgumentException("Unknown compiler type $compilerClassName")
    }
    val exitCode = try {
        val res = if (isIncremental) {
            incrementalCompilationWithDaemon(daemon, sessionId, targetPlatform)
        } else {
            nonIncrementalCompilationWithDaemon(daemon, sessionId, targetPlatform)
        }
        exitCodeFromProcessExitCode(log, res.get())
    } catch (e: Throwable) {
        log.warn("Compilation with Kotlin compile daemon was not successful")
        e.printStackTrace()
        null
    }
    // ignore
    return exitCode
}

选择编译平台,根据编译方式执行不同函数,我们选择nonIncrementalCompilationWithDaemon跟进去看下:

private fun nonIncrementalCompilationWithDaemon(
    daemon: CompileService,
    sessionId: Int,
    targetPlatform: CompileService.TargetPlatform
): CompileService.CallResult<Int> {
    // ignore
    return daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults = null)
}

继续,目前跟进到CompileServiceImpl#compile,忽略无关重点如下:

doCompile(sessionId, daemonReporter, tracer = null) { _, _ ->
            val compiler = when (targetPlatform) {
                CompileService.TargetPlatform.JVM -> K2JVMCompiler()
                CompileService.TargetPlatform.JS -> K2JSCompiler()
                CompileService.TargetPlatform.METADATA -> K2MetadataCompiler()
            } as CLICompiler<CommonCompilerArguments>
                                                     
            compiler.exec(messageCollector, Services.EMPTY, k2PlatformArgs)
}

继续,忽略意义不大的跳转到K2JVMCompiler#doExecute,如下:

override fun doExecute(
    arguments: K2JVMCompilerArguments,
    configuration: CompilerConfiguration,
    rootDisposable: Disposable,
    paths: KotlinPaths?
): ExitCode {
  // ignore
   try {

        if (arguments.buildFile != null) {

            KotlinToJVMBytecodeCompiler.configureSourceRoots(configuration, moduleChunk.modules, buildFile)

            KotlinToJVMBytecodeCompiler.compileModules(environment, buildFile, moduleChunk.modules)
        } else if (arguments.script) {
            return KotlinToJVMBytecodeCompiler.compileAndExecuteScript(environment, scriptArgs)
        } else {
            KotlinToJVMBytecodeCompiler.compileBunchOfSources(environment)
        }
        return OK
    } catch (e: CompilationException) {
        messageCollector.report(
            EXCEPTION,
            OutputMessageUtil.renderException(e),
            MessageUtil.psiElementToMessageLocation(e.element)
        )
        return INTERNAL_ERROR
    }
}

其中的KotlinToJVMBytecodeCompiler看起来是比较重要,跟进去其中一个分支看下:

fun compileBunchOfSources(environment: KotlinCoreEnvironment): Boolean {
	// 词法 语法 分析
    val generationState = analyzeAndGenerate(environment) ?: return false
	// 查找主类
    val mainClass = findMainClass(generationState, environment.getSourceFiles())
	// 写入文件
    try {
        writeOutput(environment.configuration, generationState.factory, mainClass)
        return true
    } finally {
        generationState.destroy()
    }
}

看来已经找到关键函数入口了,跟进去analyzeAndGenerate,转到KotlinCodegenFacade#doGenerateFiles,如下:

public static void doGenerateFiles(
        @NotNull Collection<KtFile> files,
        @NotNull GenerationState state,
        @NotNull CompilationErrorHandler errorHandler
) {
    state.getCodegenFactory().generateModule(state, files, errorHandler);

    CodegenFactory.Companion.doCheckCancelled(state);
    state.getFactory().done();
}

跟进去CodegenFactory,关注generate开头的函数,又经过无数跳转到,MemberCodegen#genSimpleMember:

public void genSimpleMember(@NotNull KtDeclaration declaration) {
    if (declaration instanceof KtNamedFunction) {
        try {
            functionCodegen.gen((KtNamedFunction) declaration);
        }
        catch (ProcessCanceledException | CompilationException e) {
            throw e;
        }
        catch (Exception e) {
            throw new CompilationException("Failed to generate function " + declaration.getName(), e, declaration);
        }
    }
    else if (declaration instanceof KtProperty) {
        try {
            propertyCodegen.gen((KtProperty) declaration);
        }
        catch (ProcessCanceledException | CompilationException e) {
            throw e;
        }
        catch (Exception e) {
            throw new CompilationException("Failed to generate property " + declaration.getName(), e, declaration);
        }
    }
}

具体的生成细节,如果是function就由functionCodegen生成,如果属性就由propertyCodegen生成,跟进去functionCodegen:

public void gen(@NotNull KtNamedFunction function) {

    if (owner.getContextKind() != OwnerKind.DEFAULT_IMPLS || function.hasBody()) {
	// ignore
        generateMethod(JvmDeclarationOriginKt.OtherOrigin(function, functionDescriptor), functionDescriptor, strategy);
    }
	// ignore
}

忽略无关的跳转,转到:

private void generateMethodBody(
        @NotNull JvmDeclarationOrigin origin,
        @NotNull FunctionDescriptor functionDescriptor,
        @NotNull MethodContext methodContext,
        @NotNull FunctionGenerationStrategy strategy,
        @NotNull MethodVisitor mv,
        @NotNull JvmMethodSignature jvmSignature,
        boolean staticInCompanionObject
) {
    OwnerKind contextKind = methodContext.getContextKind();
    if (!state.getClassBuilderMode().generateBodies || isAbstractMethod(functionDescriptor, contextKind)) {
        generateLocalVariableTable(
                mv,
                jvmSignature,
                functionDescriptor,
                getThisTypeForFunction(functionDescriptor, methodContext, typeMapper),
                new Label(),
                new Label(),
                contextKind,
                typeMapper,
                Collections.emptyList(),
                0);

        mv.visitEnd();
        return;
    }

    if (!functionDescriptor.isExternal()) {
        generateMethodBody(mv, functionDescriptor, methodContext, jvmSignature, strategy, memberCodegen, state.getJvmDefaultMode(),
                           state.getLanguageVersionSettings().supportsFeature(LanguageFeature.ReleaseCoroutines));
    }
    else if (staticInCompanionObject) {
        // native @JvmStatic foo() in companion object should delegate to the static native function moved to the outer class
        mv.visitCode();
        FunctionDescriptor staticFunctionDescriptor = JvmStaticInCompanionObjectGenerator
                .createStaticFunctionDescriptor(functionDescriptor);
        Method accessorMethod = typeMapper.mapAsmMethod(memberCodegen.getContext().accessibleDescriptor(staticFunctionDescriptor, null));
        Type owningType = typeMapper.mapClass((ClassifierDescriptor) staticFunctionDescriptor.getContainingDeclaration());
        generateDelegateToStaticMethodBody(false, mv, accessorMethod, owningType.getInternalName(), false);
    }

    endVisit(mv, null, origin.getElement());
}

代码中有几个地方 visitor 还有 visitEnd,我们看下具体的引用:

import org.jetbrains.org.objectweb.asm.*;
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
import org.jetbrains.org.objectweb.asm.commons.Method;
import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;

看来是利用ASM框架去生成字节码,例子:

// 如果生成一个类使用ClassWriter
ClassWriter cw = new ClassWriter(0);
// 定义类的方法
cw.visitMethod(Opcodes.ACC_PUBLIC+Opcodes.ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I",null, null).visitEnd();
// 完成
cw.visitEnd(); 
// 将cw转换成字节数组
byte[] data = cw.toByteArray();
// 写入文件
File file = new File("/Users/test/Comparable.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();

ok,kotlin的编译过程基本就是,插件 - kotlin任务 - 编译器 - 生成方法、属性 - 利用ASM生成字节码

Kotlin 跨平台

kotlin 在语法上是支持跨平台的,是编译期跨平台,而不是容器类跨平台,目前支持JS、iOS、Server。

原文  http://www.jackywang.tech/2019/01/30/Kotlin-实践及原理/
正文到此结束
Loading...