KMM初探与编译过程详解

7560bca8989190cb44040f825f4c64a4.gif

本文字数:22817

预计阅读时间:58分钟

简介

KMM, 即Kotlin Multiplatform Mobile,是由Kotlin发布的移动端跨平台框架。相比于其他跨平台框架,KMM是原生UI+逻辑共享的理念,共享重复逻辑性的工作来提升开发效率的同时,保持原生执行效率与UI特性。所以KMM并不会替代AndroidiOS的原生开发, 而是提倡将共有的逻辑部分抽出,由KMM封装成Android(Kotlin/JVM)aariOS(Kotlin/Native)framework,再提供给View层进行调用,从而节约一部分的工作量。 我们先来比较几种当前流行的跨平台框架:

3c80cfdb9228cfa38140b6ba4637c734.jpeg

主要说两点:

  • 由于KMM的本质就是原生App,跨平台共享的内容都是在编译期进行的处理,所以在性能方面可以说是不受影响。其他的跨平台方案,其性能多多少少都会受到影响且会增加包体积。

  • Flutter为代表的自带渲染引擎实现UI框架在开发效率上是更高的。而KMM主要实现的是共享逻辑,UI层的实现还是建议平台各自去处理,所以开发效率上来说,KMM优于原生开发,但不如Flutter。不过由于Android的官方语言就是Kotlin,对于Android开发来说,KMM的加持更像是一种赠送能力,几乎可以无成本的进行KMM开发。

另外,去年十月初Android 官方宣布 Jetpack开始要支持KMM了,意味着KMM已经得到了官方的支持。目前Collections和 DataStore 已经可以通过依赖 -dev01 版本在多平台上使用,加上KMM已经到了Beta的阶段,到了我们可以进行大胆尝试的时候了。因此本文将从准备工作,结构介绍,Demo示例和编译过程详解来对KMM框架进行说明。

准备工作

安装必要的工具和插件

  • 1.安装或更新AndroidStudio和Xcode到最新版

  • 2.JDK

  • 3.Kotlin Multiplatform Mobile plugin:AndroidStudio下载KMM插件,如图所示:

85c11d6ac9bb0906fedc1050ff06af15.jpeg

image.png
  • 4.Kotlin plugin:更新Kolin Plugin到最新版本,如图所示:

0f2d80e7c6a631401dac2cc9bd2a9533.jpeg

检查环境配置

  • 1.打开Terminal使用HomeBrew安装以下工具:

brew install kdoctor

  • 2.执行kdoctor:

kdoctor

如果你的环境配置有问题,那么将会输出相应的信息,并以X作为标记,大家可根据提示进行修改。Tips:以上步骤可能需要科学上网。

KMM结构说明

在尝试KMM之前,我们先来了解一下KMM的基本结构。我们建个默认的KMM工程,看一下它的结构:99f958f61d5fece214a181e5ab800ced.jpeg默认会生成androidAppsharediosApp这三个子工程。其中androidAppiosApp为Android和iOS这两个平台的工程模块,shared为共享逻辑模块,供androidAppiosApp调用。我们打开根目录的settings.gradle.kts

pluginManagement {
    repositories {
        google()
        gradlePluginPortal()
        mavenCentral()
    }
}

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.name = "My_Application"
include(":androidApp")
include(":shared")

会发现主项目只includeandroidAppshared这两个子项目,因为这两个项目是Gradle项目,那么iOS的项目如何引用呢?我们摘抄一段官网的原文:

The iOS application is produced from an Xcode project. It's stored in a separate directory within the root project. Xcode uses its own build system; thus, the iOS application project isn't connected with other parts of the Multiplatform Mobile project via Gradle. Instead, it uses the shared module as an external artifact – framework.

意思是说iOS作为Xcode项目,储存在根项目的另一个文件夹。Xcode有自己的编译系统,因此iOS项目并不依靠Gradle去和共享工程建立联系,而是依靠将共享工程打包成frameworkiOS项目使用。我们可以看一下shared模块编译后的产物,如下图所示:

2735e102526f7c80191d3d6bd3e41397.jpeg

可以很清晰的看到生成的frameworkaar文件。我们再来看看shared模块都包含了什么:

a4b822480215f34355ec93d8a4c191e5.jpeg

  • 其中commonMain为公共模块,该模块的代码与平台无关,是通过 expected关键字对一些api的声明(声明的实现在platform module中)。

  • androidMainiosMain分别为AndroidiOS这两个平台,通过actual关键字在平台模块进行具体的实现。

我们继续看看shared模块的gradle文件都做了什么:

plugins {
    kotlin("multiplatform")
    id("com.android.library")
}

kotlin {
    android()
    
    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach {
        it.binaries.framework {
            baseName = "shared"
        }
    }
}

这是将这个KMM模块编译成Android aariOS framework的声明。使用了Gradle编译系统和KMM插件来进行实现。其中:

kotlin("multiplatform")

意味着引入KMM插件。

id("com.android.library")

意味着生成一个Android aar,其配置用android {}进行了包裹:

android {
    namespace = "com.example.myapplication"
    compileSdk = 32
    defaultConfig {
        minSdk = 28
        targetSdk = 32
    }
}

iOS framework是使用Kotlin/Native进行编译的,相应的配置是用iosXXX{}进行了包裹:

listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach {
        it.binaries.framework {
            baseName = "shared"
        }
    }

定义了输出格式为framework,输出名称为shared。我们接着看kotlin还包含了什么:

kotlin {
//...
   sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
                implementation("io.ktor:ktor-client-core:$ktorVersion")
                implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
                implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")

            }
        }
        val androidMain by getting{
            dependencies {
                implementation("io.ktor:ktor-client-android:$ktorVersion")
            }
        }
//...
        val iosMain by creating {
//...
            dependencies {
                implementation("io.ktor:ktor-client-darwin:$ktorVersion")
            }
        }
//...
    }
}

在上一节我们提到的commonMainandroidMainiosMain就是sourceSets,其中commonMain是共享的逻辑,而androidMainiosMain是对AndroidiOS共享逻辑的相应实现。每一个sourceSet都可以有自己单独的dependencies,如上面的代码。支持分别引入implementation来实现各自的逻辑。另外Kotlin标准库会被自动加到相应的sourceSet中,无需重复引入。比方说上面的kotlinx-coroutines-core。之后,AndroidiOS分别使用各自常规方式引入/调用aarframework即可。经过以上的说明,我们大概了解了KMM,项目的架构,抄一张官网的图进行总结:

4ed3bf3f81ae6a2450d68c4275c4613a.jpeg

Demo示例 

环境配置好了,我们就可以写一个简单的demo了。打开Android StudioFile->New->New Project,选择Kotlin Multiplatform App

a36a3b85e02db02549b511c1625e3abf.jpeg

点击Next

c9acb5f0ab9a4dcd42bb876b0f806483.jpeg

继续点击Next

ee97bec1daf2d5b44f2cfde2dbe0270c.jpeg

配置iOS项目时,将iOS framework distribution中选择Regular framework,然后Finish。至于为什么选择Regular framework,官方文档的描述如下:

We recommend using the regular framework for your first project, as this option doesn't require third-party tools and has less installation issues. For more complex projects, you might need the CocoaPods dependency manager that helps handle library dependencies. To learn more about CocoaPods and how to set up an environment for them, see CocoaPods overview and setup.

大概意思是说Regular framework不需要三方工具,且有较少的安装事项。对于更复杂的工程来说,可能需要使用CocoaPods dependency manage去管理libs。对于我们的Demo工程来说,先使用Regular framework来进行说明。在上个章节我们对于KMM工程的结构进行了说明,我们进入shared模块,看commonMain文件夹下Greeting的实现:

class Greeting {
    private val platform: Platform = getPlatform()

    fun greeting(): String {
        return "Hello, ${platform.name}!"
    }
}

greeting()方法调用platform.namePlatform的实现如下:

interface Platform {
    val name: String
}

expect fun getPlatform(): Platform

Platform是个接口,使用expect关键字来声明getPlatform(),再由AndroidiOS通过使用actual关键字分别实现:

Android

class AndroidPlatform : Platform {
    override val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}

actual fun getPlatform(): Platform = AndroidPlatform()

iOS

class IOSPlatform: Platform {
    override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}

actual fun getPlatform(): Platform = IOSPlatform()

最后我们看一下是如何调用的:

Android

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    Greeting(Greeting().greeting())
                }
            }
        }
    }
}

@Composable
fun Greeting(text: String) {
    Text(text = text)
}

iOS

struct ContentView: View {
 let greet = Greeting().greeting()

 var body: some View {
  Text(greet)
 }
}

struct ContentView_Previews: PreviewProvider {
 static var previews: some View {
  ContentView()
 }
}

我们运行一下。

  • Android的话,在Android Studio上的configurations list中选择androidApp,直接连接真机或开启模拟器,点击Run->Run 'androidApp'即可。

  • iOS的话,如果是第一次运行,需要打开Xcode,同意一些协议,然后回到Android Studioconfigurations list中选择iosApp,直接运行即可。

效果如下:

5652d8d748a315e30137e92f80211f9d.jpeg

编译过程详解 

接下来我们来看一下shared模块是如何工作的。先关注一下shared模块的gradle文件:

plugins {
    kotlin("multiplatform")
    id("com.android.library")
}

加载了multiplatform插件,并把此module作为lib输出。

kotlin {
    android()
    iosX64()
    iosArm64()
    iosSimulatorArm64()
}

重点来了,这个是编译双端lib的关键。我们先看看android()是怎么编译Androidaar的:

Android编译过程

fun android() = android("android") { }

fun android(
        name: String = "android",
        configure: KotlinAndroidTarget.() -> Unit = { }
    ): KotlinAndroidTarget =
        configureOrCreate(
            name,
            presets.getByName("android") as KotlinAndroidTargetPreset,
            configure
        )

我们看到android()是创建了个KotlinAndroidTarget对象。我们找到插件multiplatform的入口类:AbstractKotlinMultiplatformPluginWrapper:

abstract class AbstractKotlinMultiplatformPluginWrapper : KotlinBasePluginWrapper() {
    override fun getPlugin(project: Project): Plugin<Project> =
        KotlinMultiplatformPlugin()

    override val projectExtensionClass: KClass<out KotlinMultiplatformExtension>
        get() = KotlinMultiplatformExtension::class

    override fun whenBuildEvaluated(project: Project) {
        project.runMissingAndroidTargetProjectConfigurationHealthCheck()
        project.runMissingKotlinTargetsProjectConfigurationHealthCheck()
        project.runDisabledCInteropCommonizationOnHmppProjectConfigurationHealthCheck()
    }
}

返回一个KotlinMultiplatformPlugin对象。KotlinMultiplatformPlugin继承Plugin,我们直接看apply()方法的实现:

override fun apply(project: Project) {
//...

        setupDefaultPresets(project)
        customizeKotlinDependencies(project)
        configureSourceSets(project)
//...

截取重要的部分,看setupDefaultPresets(project)的实现:

fun setupDefaultPresets(project: Project) {
        with(project.multiplatformExtension.presets) {
            add(KotlinJvmTargetPreset(project))
            add(KotlinJsTargetPreset(project).apply { irPreset = null })
            add(KotlinJsIrTargetPreset(project, isWasm = false).apply { mixedMode = false })
            add(
                KotlinJsTargetPreset(project).apply {
                    irPreset = KotlinJsIrTargetPreset(project, isWasm = false)
                        .apply { mixedMode = true }
                }
            )
            add(KotlinJsIrTargetPreset(project, isWasm = true).apply { mixedMode = false })
            add(KotlinAndroidTargetPreset(project))
            add(KotlinJvmWithJavaTargetPreset(project))

            // Note: modifying these sets should also be reflected in the DSL code generator, see 'presetEntries.kt'
            val nativeTargetsWithHostTests = setOf(LINUX_X64, MACOS_X64, MACOS_ARM64, MINGW_X64)
            val nativeTargetsWithSimulatorTests =
                setOf(IOS_X64, IOS_SIMULATOR_ARM64, WATCHOS_X86, WATCHOS_X64, WATCHOS_SIMULATOR_ARM64, TVOS_X64, TVOS_SIMULATOR_ARM64)

            HostManager().targets
                .forEach { (_, konanTarget) ->
                    val targetToAdd = when (konanTarget) {
                        in nativeTargetsWithHostTests ->
                            KotlinNativeTargetWithHostTestsPreset(konanTarget.presetName, project, konanTarget)
                        in nativeTargetsWithSimulatorTests ->
                            KotlinNativeTargetWithSimulatorTestsPreset(konanTarget.presetName, project, konanTarget)
                        else -> KotlinNativeTargetPreset(konanTarget.presetName, project, konanTarget)
                    }

                    add(targetToAdd)
                }
        }
    }

这个方法创建并添加了各个平台的TargetPreset,看上去是用来配置各个平台的编译详情的。我们先看看Android平台KotlinAndroidTargetPreset的是如何创建的:

override fun createTarget(name: String): KotlinAndroidTarget {
        val result = KotlinAndroidTarget(name, project).apply {
            disambiguationClassifier = name
            preset = this@KotlinAndroidTargetPreset
            targetUnderConstruction = this
        }

        project.dynamicallyApplyWhenAndroidPluginIsApplied({ result })

        if (project.hasKpmModel) {
            mapTargetCompilationsToKpmVariants(result, PublicationRegistrationMode.AFTER_EVALUATE)
        }

        targetUnderConstruction = null

        return result
    }

创建了一个KotlinAndroidTarget对象,并执行dynamicallyApplyWhenAndroidPluginIsApplied()方法:

internal fun Project.dynamicallyApplyWhenAndroidPluginIsApplied(
            kotlinAndroidTargetProvider: () -> KotlinAndroidTarget,
            additionalConfiguration: (KotlinAndroidTarget) -> Unit = {}
        ) {
            var wasConfigured = false

            androidPluginIds.forEach { pluginId ->
                plugins.withId(pluginId) {
                    wasConfigured = true
                    val target = kotlinAndroidTargetProvider()
                    androidTargetHandler().configureTarget(target)
                    additionalConfiguration(target)
                }
            }
//...
        }

看重点:

androidTargetHandler().configureTarget(target)

fun configureTarget(kotlinAndroidTarget: KotlinAndroidTarget) {
//...
        project.forEachVariant { variant ->
            val variantName = getVariantName(variant)
//...
            kotlinAndroidTarget.compilationFactory.create(variantName).let { compilation ->
                compilation.androidVariant = variant

                setUpDependencyResolution(variant, compilation)

                preprocessVariant(variant, compilation, project, kotlinOptions, kotlinConfigurationTools.kotlinTasksProvider)

                @Suppress("UNCHECKED_CAST")
                (kotlinAndroidTarget.compilations as NamedDomainObjectCollection<in KotlinJvmAndroidCompilation>).add(compilation)
            }

        }

        project.whenEvaluated {
            forEachVariant { variant ->
                val compilation = kotlinAndroidTarget.compilations.getByName(getVariantName(variant))
                postprocessVariant(variant, compilation, project, ext, plugin)

                val subpluginEnvironment = SubpluginEnvironment.loadSubplugins(project)
                subpluginEnvironment.addSubpluginOptions(project, compilation)
            }
//...
        }
//...
    }

先看preprocessVariant()的实现:

private fun preprocessVariant(
        variantData: BaseVariant,
        compilation: KotlinJvmAndroidCompilation,
        project: Project,
        rootKotlinOptions: KotlinJvmOptionsImpl,
        tasksProvider: KotlinTasksProvider
    ) {
//...
        tasksProvider.registerKotlinJVMTask(project, compilation.compileKotlinTaskName, compilation.kotlinOptions, configAction)
//...
    }

做了一些初始配置,并为tasksProvider注册了KotlinJVMTask。看postprocessVariant()的实现:

private fun postprocessVariant(
        variantData: BaseVariant,
        compilation: KotlinJvmAndroidCompilation,
        project: Project,
        androidExt: BaseExtension,
        androidPlugin: BasePlugin
    ) {
//...
        val javaTask = variantData.getJavaTaskProvider()
        val kotlinTask = compilation.compileKotlinTaskProvider
//...
        wireKotlinTasks(project, compilation, androidPlugin, androidExt, variantData, javaTask, kotlinTask)
    }

创建了javaTaskkotlinTask,然后看wireKotlinTasks()的实现:

override fun wireKotlinTasks(
        project: Project,
        compilation: KotlinJvmAndroidCompilation,
        androidPlugin: BasePlugin,
        androidExt: BaseExtension,
        variantData: BaseVariant,
        javaTask: TaskProvider<out AbstractCompile>,
        kotlinTask: TaskProvider<out KotlinCompile>
    ) {
        val preJavaKotlinOutput = project.files(project.provider {
            mutableListOf<File>().apply {
                add(kotlinTask.get().destinationDirectory.get().asFile)
                if (Kapt3GradleSubplugin.isEnabled(project)) {
                    // Add Kapt3 output as well, since there's no SyncOutputTask with the new API
                    val kaptClasssesDir = Kapt3GradleSubplugin.getKaptGeneratedClassesDir(project, getVariantName(variantData))
                    add(kaptClasssesDir)
                }
            }
        }).builtBy(kotlinTask)

        val preJavaClasspathKey = variantData.registerPreJavacGeneratedBytecode(preJavaKotlinOutput)
        kotlinTask.configure { kotlinTaskInstance ->
            kotlinTaskInstance.libraries
                .from(variantData.getCompileClasspath(preJavaClasspathKey))
                .from(Callable { AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt) })

            kotlinTaskInstance.javaOutputDir.set(javaTask.flatMap { it.destinationDirectory })
        }
//...
    }

从方法命名我们可得知,它的目的是执行KotlinTasks。我们看见kotlinTask是个TaskProvider对象,实际的操作在KotlinCompile里。KotlinCompile是个Task,其execute实现在父类AbstractKotlinCompile中:

@TaskAction
    fun execute(inputChanges: InputChanges) {
        val buildMetrics = metrics.get()
        buildMetrics.measure(BuildTime.GRADLE_TASK_ACTION) {
//...
            executeImpl(inputChanges, outputsBackup)
        }

        buildMetricsReporterService.orNull?.also { it.addTask(path, this.javaClass, buildMetrics) }
    }

    private fun executeImpl(
        inputChanges: InputChanges,
        taskOutputsBackup: TaskOutputsBackup?
    ) {
//...
        callCompilerAsync(
            args,
            allKotlinSources,
            inputChanges,
            taskOutputsBackup
        )
    }

主要看执行在executeImpl()中的callCompilerAsync()方法:

override fun callCompilerAsync(
        args: K2JVMCompilerArguments,
        kotlinSources: Set<File>,
        inputChanges: InputChanges,
        taskOutputsBackup: TaskOutputsBackup?
    ) {
        validateKotlinAndJavaHasSameTargetCompatibility(args, kotlinSources)
//...
        val compilerRunner = compilerRunner.get()
//...
        compilerRunner.runJvmCompilerAsync(
            (kotlinSources + scriptSources).toList(),
            commonSourceSet.toList(),
            javaSources.files, // we need here only directories where Java sources are located
            javaPackagePrefix,
            args,
            environment,
            defaultKotlinJavaToolchain.get().providedJvm.get().javaHome,
            taskOutputsBackup
        )
    }

先获取一个compilerRunner,它的实现如下:

@get:Internal
    override val compilerRunner: Provider<GradleCompilerRunner> = objectFactory.propertyWithConvention(
        // From Gradle 6.6 better to replace flatMap with provider.zip()
        defaultKotlinJavaToolchain.flatMap { toolchain ->
            objectFactory.property(gradleCompileTaskProvider.map {
                GradleCompilerRunnerWithWorkers(
                    it,
                    toolchain.currentJvmJdkToolsJar.orNull,
                    normalizedKotlinDaemonJvmArguments.orNull,
                    metrics.get(),
                    compilerExecutionStrategy.get(),
                    workerExecutor
                )
            })
        }
    )

创建了一个GradleCompilerRunnerWithWorkers对象,再执行compilerRunner.runJvmCompilerAsync()方法。继续追踪若干个调用,执行了如下代码:

protected open fun runCompilerAsync(
        workArgs: GradleKotlinCompilerWorkArguments,
        taskOutputsBackup: TaskOutputsBackup?
    ): WorkQueue? {
        try {
            val kotlinCompilerRunnable = GradleKotlinCompilerWork(workArgs)
            kotlinCompilerRunnable.run()
        } catch (e: GradleException) {
//...
        }

        return null
    }

其中GradleKotlinCompilerWork是个Runnable,执行了它的run()方法:

override fun run() {
        try {
            val messageCollector = GradlePrintingMessageCollector(log, allWarningsAsErrors)
            val (exitCode, executionStrategy) = compileWithDaemonOrFallbackImpl(messageCollector)
//...
        } finally {
//...
        }
    }

继续看的compileWithDaemonOrFallbackImpl()实现:

private fun compileWithDaemonOrFallbackImpl(messageCollector: MessageCollector): Pair<ExitCode, KotlinCompilerExecutionStrategy> {
//...
        if (compilerExecutionStrategy == KotlinCompilerExecutionStrategy.DAEMON) {
            val daemonExitCode = compileWithDaemon(messageCollector)
//...
        }

        val isGradleDaemonUsed = System.getProperty("org.gradle.daemon")?.let(String::toBoolean)
        return if (compilerExecutionStrategy == KotlinCompilerExecutionStrategy.IN_PROCESS || isGradleDaemonUsed == false) {
            compileInProcess(messageCollector) to KotlinCompilerExecutionStrategy.IN_PROCESS
        } else {
            compileOutOfProcess() to KotlinCompilerExecutionStrategy.OUT_OF_PROCESS
        }
    }

可以看出,kotlin编译有三种策略,分别是

  • 守护进程编译:Kotlin编译的默认模式,只有这种模式才支持增量编译,可以在多个Gradle daemon进程间共享

  • 进程内编译:Gradle daemon进程内编译

  • 进程外编译:每次编译都是在不同的进程 我们来看Kotlin编译的默认模式的实现,其compileWithDaemon()方法最终执行了编译逻辑:

private fun compileWithDaemon(messageCollector: MessageCollector): ExitCode? {
//...
        val bufferingMessageCollector = GradleBufferingMessageCollector()
        val exitCode = try {
            val res = if (isIncremental) {
                incrementalCompilationWithDaemon(daemon, sessionId, targetPlatform, bufferingMessageCollector)
            } else {
                nonIncrementalCompilationWithDaemon(daemon, sessionId, targetPlatform, bufferingMessageCollector)
            }
//...
        } catch (e: Throwable) {
//...
        }
//...
        return exitCode
    }

到这里会执行实现类org.jetbrains.kotlin.daemon.CompileServiceImpl 的 compile 方法,这样就终于调到了Kotlin编译器内部。经过代码追踪,会先执行compileImpl()方法,然后执行到doCompile()方法:

val compiler = when (targetPlatform) {
            CompileService.TargetPlatform.JVM -> K2JVMCompiler()
            CompileService.TargetPlatform.JS -> K2JSCompiler()
            CompileService.TargetPlatform.METADATA -> K2MetadataCompiler()
        } as CLICompiler<CommonCompilerArguments>

doCompile(sessionId, daemonReporter, tracer = null) { eventManger, profiler ->
                        val services = createServices(servicesFacade, eventManger, profiler)
                        compiler.exec(messageCollector, services, k2PlatformArgs)
                    }

我们继续看 compiler.exec()做了什么,跟进到K2JVMCompilerdoExecute()方法:在实现了一系列的配置之后,我们找到了关键代码:

KotlinToJVMBytecodeCompiler.compileModules(environment, buildFile, chunk)

继续跟踪compileModules()的关键代码:

val codegenInputs = ArrayList<CodegenFactory.CodegenInput>(chunk.size)

        for (module in chunk) {
//...
            codegenInputs += runLowerings(
                environment, moduleConfiguration, result, ktFiles, module, codegenFactory, backendInput, diagnosticsReporter
            )
        }

        val outputs = ArrayList<GenerationState>(chunk.size)

        for (input in codegenInputs) {
            outputs += runCodegen(input, input.state, codegenFactory, result.bindingContext, diagnosticsReporter, environment.configuration)
        }

        return writeOutputs(environment.project, projectConfiguration, chunk, outputs, mainClassFqName)

runLowerings()runCodeGen()看起来是我们想要的关键,其会执行到继续追踪关键代码:

return codegenFactory.invokeLowerings(state, backendInput)
            .also { performanceManager?.notifyIRLoweringFinished()
codegenFactory.invokeCodegen(codegenInput)

追踪到CodegenFactory的相应实现。又经过若干跳转后,执行到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);
            }
        }
//...
    }

根据declaration类型来决定用哪个Codegen去编译,我们看方法的生成: functionCodegen.gen()的关键实现:

generateMethod(JvmDeclarationOriginKt.OtherOrigin(function, functionDescriptor), functionDescriptor, strategy);

public void generateMethod(
            @NotNull JvmDeclarationOrigin origin,
            @NotNull FunctionDescriptor functionDescriptor,
            @NotNull MethodContext methodContext,
            @NotNull FunctionGenerationStrategy strategy
    ) {
//...
        Method asmMethod = jvmSignature.getAsmMethod();

//...
        MethodVisitor mv =
                strategy.wrapMethodVisitor(
                        newMethod(
                                origin,
                                flags,
                                asmMethod.getName(),
                                asmMethod.getDescriptor(),
                                strategy.skipGenericSignature() ? null : jvmSignature.getGenericsSignature(),
                                getThrownExceptions(functionDescriptor, typeMapper)
                        ),
                        flags, asmMethod.getName(),
                        asmMethod.getDescriptor()
                );

//..
            generateMethodBody(
                    origin, functionDescriptor, methodContext, strategy, mv, jvmSignature, staticInCompanionObject
            );
 //...
    }

这段代码就是具体的编译过程了,从命名来看,大概就是使用ASM框架去生成Java字节码,其中mv是个MethodVisitor对象,在org.jetbrains.org.objectweb.asm包内。Android的编译过程我们就追踪到这里。

  • 总结一下,Android aar的编译过程是由KotlinMultiplatformPlugin发起kotlinTask,再由kotlinTask开启KotlinCompile编译任务,并交给GradleCompilerRunnerWithWorkers执行。在执行过程中调到了Kotlin编译器内部org.jetbrains.kotlin.daemon.CompileServiceImpl的compile()方法,并交由CodegenFactory实现,最终使用ASM框架去生成Java字节码。

Android的编译过程就看到这里,我们再来看看iOS的,由于本人是Android开发,对于iOS的编译过程只做个大概的说明。

iOS编译过程 

我们重新回到KotlinMultiplatformPluginsetupDefaultPresets()找到iOS相应的配置方法:

val nativeTargetsWithSimulatorTests =
                setOf(IOS_X64, IOS_SIMULATOR_ARM64, WATCHOS_X86, WATCHOS_X64, WATCHOS_SIMULATOR_ARM64, TVOS_X64, TVOS_SIMULATOR_ARM64)

            HostManager().targets
                .forEach { (_, konanTarget) ->
                    val targetToAdd = when (konanTarget) {
                        in nativeTargetsWithHostTests ->
                            KotlinNativeTargetWithHostTestsPreset(konanTarget.presetName, project, konanTarget)
                        in nativeTargetsWithSimulatorTests ->
                            KotlinNativeTargetWithSimulatorTestsPreset(konanTarget.presetName, project, konanTarget)
                        else -> KotlinNativeTargetPreset(konanTarget.presetName, project, konanTarget)
                    }

                    add(targetToAdd)
                }

我们选取一个Preset看它的实现,比如KotlinNativeTargetPreset,我们先看它父类AbstractKotlinNativeTargetPreset的初始化:

override fun createTarget(name: String): T {
        setupNativeCompiler()

        val result = instantiateTarget(name).apply {
            targetName = name
            disambiguationClassifier = name
            preset = this@AbstractKotlinNativeTargetPreset

            val compilationFactory =
                if (project.hasKpmModel) {
                    val kpmVariantClass = kpmNativeVariantClass(konanTarget) ?: error("Can't find the KPM variant class for $konanTarget")
                    KotlinMappedNativeCompilationFactory(this, kpmVariantClass)
                } else {
                    KotlinNativeCompilationFactory(this)
                }
            compilations = project.container(compilationFactory.itemClass, compilationFactory)
        }

        createTargetConfigurator().configureTarget(result)

//...

        return result
    }

其中setupNativeCompiler()方法的主要作用是创建一个NativeCompilerDownloader对象,下载一些必要的工具,然后创建一个KotlinCompilationFactory对象,编译执行的重点在:

createTargetConfigurator().configureTarget(result)

我们看一下createTargetConfigurator()创建了什么:

override fun createTargetConfigurator(): AbstractKotlinTargetConfigurator<KotlinNativeTarget> {
        val configurator = KotlinNativeTargetConfigurator<KotlinNativeTarget>()
        return if (project.hasKpmModel)
            KpmNativeTargetConfigurator(configurator)
        else configurator
    }

创建了一个KotlinNativeTargetConfigurator对象,接下来我们看看configureTarget()的实现:

fun configureTarget(
        target: KotlinTargetType
    ) {
        configureCompilationDefaults(target)
        configureCompilations(target)
        defineConfigurationsForTarget(target)
        configureArchivesAndComponent(target)
        configureSourceSet(target)
        configureBuild(target)
        configurePlatformSpecificModel(target)
    }

做了很多配置,我们看看针对iOS单独做了什么,继续看configurePlatformSpecificModel()的实现:

override fun configurePlatformSpecificModel(target: T) {
        configureBinaries(target)
        configureFrameworkExport(target)
        configureCInterops(target)

        if (target.konanTarget.family.isAppleFamily) {
            registerEmbedAndSignAppleFrameworkTasks(target)
        }

        if (PropertiesProvider(target.project).ignoreIncorrectNativeDependencies != true) {
            warnAboutIncorrectDependencies(target)
        }
    }

我们看一下configureBinaries(target)的主要实现:

target.binaries.all {
            project.createLinkTask(it)
        }

创建了一个linkTask

private fun Project.createLinkTask(binary: NativeBinary) {
//...
        if (binary is Framework) {
            createFrameworkArtifact(binary, result)
        }
    }

继续追踪createFrameworkArtifact()

private fun Project.createFrameworkArtifact(
        binary: Framework,
        linkTask: TaskProvider<KotlinNativeLink>
    ) {
        configurations.create(lowerCamelCaseName(binary.name, binary.target.name)) {
            it.isCanBeConsumed = true
            it.isCanBeResolved = false
            it.configureConfiguration(linkTask)
        }

        if (FatFrameworkTask.isSupportedTarget(binary.target)) {
            configureFatFramework()
        }
    }

先执行了configureConfiguration(),后执行了configureFatFramework(),其中linkTask FatFrameworkTask,编译任务最重都是由FatFrameworkTask来执行的,其编译过程在如下方法里:

@TaskAction
    protected fun createFatFramework() {
        val outFramework = fatFrameworkLayout

        outFramework.mkdirs()
        mergeBinaries(outFramework.binary)
        mergeHeaders(outFramework.header)
        createModuleFile(outFramework.moduleFile, fatFrameworkName)
        mergePlists(outFramework.infoPlist, fatFrameworkName)
        mergeDSYM()
    }

最终生成了可被iOS引用的framework。如果我们解压framework文件,会发现这些merge方法与其framework的产物是一一对应的。

总结

好了,对于KMM的介绍就到这里。KMM的特性意味着我们可以大胆的使用它,不用担心性能问题,上架风险等。虽然它还不够成熟,支持性还不够高,但考虑到它较低的学习成本(特别是对于Andorid开发来说),我们完全可以局部的使用它。对于前端开发同学来说,跨平台是个总也绕不开的话题,学习不同的框架对于我们对App整体架构的思考也起到积极的作用。

参考文献

  • https://kotlinlang.org/docs/multiplatform-mobile-getting-started.html

  • https://www.jianshu.com/p/bd0cf9b2193c

  • https://blog.csdn.net/rain_butterfly/article/details/87941589

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值