关于Kotlin在项目中的实践经验,及在Android平台的编译原理
String name = "Amit Shekhar"; final String name = "Amit Shekhar";
var name = "Amit Shekhar" val name = "Amit Shekhar"
if (text != null) { int length = text.length(); }
val length = text?.length()
String firstName = "Amit"; String lastName = "Shekhar"; String message = "My name is: " + firstName + " " + lastName;
val firstName = "Amit" val lastName = "Shekhar" val message = "My name is: $firstName $lastName"
String text = x > 5 ? "x > 5" : "x <= 5";
val text = if (x > 5) "x > 5" else "x <= 5"
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"; }
var score = // some score var grade = when (score) { 9, 10 -> "Excellent" in 6..8 -> "Good" 4, 5 -> "OK" in 1..3 -> "Fail" else -> "Fail" }
int getScore() { // logic here return score; }
fun getScore(): Int { // logic here return score } // as a single-expression function fun getScore(): Int = score
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 + '}'; } }
data class Developer(val name: String, val age: Int)
public class Child extends Parent implements IHome { }
class Child : Parent(), IHome { }
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中的静态成员,需要创建伴生对象,伴生对象通过 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};
后两种产生了装箱处理,产生开销
kotlin 提供了downTo step until reversed函数简单使用循环,但这些函数组合使用也有可能产生较多的临时对象。
Tools ->Kotlin ->Show Kotlin Bytecode -> Decompile
我们对kotlin比较大的疑问可能是kotlin是怎么和java混编的?或者说kotlin是怎么生成字节码的
kotlin整个都是开源的,可以从github clone下来,地址: https://github.com/JetBrains/kotlin
整个工程很庞大,源代码大概有四百多万行,可以使用 IntelliJ IDEA查看整个工程,具体操作可以看github 项目主页的建议。
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 在语法上是支持跨平台的,是编译期跨平台,而不是容器类跨平台,目前支持JS、iOS、Server。