本文字数:22817字
预计阅读时间:58分钟
KMM
, 即Kotlin Multiplatform Mobile
,是由Kotlin
发布的移动端跨平台框架。相比于其他跨平台框架,KMM
是原生UI+
逻辑共享的理念,共享重复逻辑性的工作来提升开发效率的同时,保持原生执行效率与UI
特性。所以KMM
并不会替代Android
和iOS
的原生开发, 而是提倡将共有的逻辑部分抽出,由KMM
封装成Android(Kotlin/JVM)
的aar
和iOS(Kotlin/Native)
的framework
,再提供给View
层进行调用,从而节约一部分的工作量。 我们先来比较几种当前流行的跨平台框架:KMM
的本质就是原生App
,跨平台共享的内容都是在编译期进行的处理,所以在性能方面可以说是不受影响。其他的跨平台方案,其性能多多少少都会受到影响且会增加包体积。Flutter
为代表的自带渲染引擎实现UI
框架在开发效率上是更高的。而KMM
主要实现的是共享逻辑,UI
层的实现还是建议平台各自去处理,所以开发效率上来说,KMM
优于原生开发,但不如Flutter
。不过由于Android
的官方语言就是Kotlin
,对于Android
开发来说,KMM
的加持更像是一种赠送能力,几乎可以无成本的进行KMM
开发。Jetpack
开始要支持KMM
了,意味着KMM
已经得到了官方的支持。目前Collections
和 DataStore
已经可以通过依赖 -dev01
版本在多平台上使用,加上KMM
已经到了Beta
的阶段,到了我们可以进行大胆尝试的时候了。因此本文将从准备工作,结构介绍,Demo
示例和编译过程详解来对KMM
框架进行说明。AndroidStudio和Xcode
到最新版JDK
Kotlin Multiplatform Mobile plugin:
从AndroidStudio
下载KMM
插件,如图所示:Kotlin plugin:
更新Kolin Plugin
到最新版本,如图所示:Terminal
使用HomeBrew
安装以下工具:brew install kdoctor
kdoctor:
kdoctor
X
作为标记,大家可根据提示进行修改。Tips:
以上步骤可能需要科学上网。KMM
之前,我们先来了解一下KMM
的基本结构。我们建个默认的KMM
工程,看一下它的结构:默认会生成androidApp
,shared
和iosApp
这三个子工程。其中androidApp
和iosApp
为Android和iOS这两个平台的工程模块,shared
为共享逻辑模块,供androidApp
和iosApp
调用。我们打开根目录的settings.gradle.kts
:pluginManagement {
repositories {
google()
gradlePluginPortal()
mavenCentral()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
rootProject.name = "My_Application"
include(":androidApp")
include(":shared")
include
了androidApp
和shared
这两个子项目,因为这两个项目是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
去和共享工程建立联系,而是依靠将共享工程打包成framework
供iOS
项目使用。我们可以看一下shared
模块编译后的产物,如下图所示:framework
和aar
文件。我们再来看看shared
模块都包含了什么:commonMain
为公共模块,该模块的代码与平台无关,是通过 expected
关键字对一些api
的声明(声明的实现在platform module
中)。androidMain
和iosMain
分别为Android
和iOS
这两个平台,通过actual
关键字在平台模块进行具体的实现。shared
模块的gradle
文件都做了什么:plugins {
kotlin("multiplatform")
id("com.android.library")
}
kotlin {
android()
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "shared"
}
}
}
KMM
模块编译成Android aar
和iOS 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")
}
}
//...
}
}
commonMain
,androidMain
和iosMain
就是sourceSets
,其中commonMain
是共享的逻辑,而androidMain
和iosMain
是对Android
和iOS
共享逻辑的相应实现。每一个sourceSet
都可以有自己单独的dependencies
,如上面的代码。支持分别引入implementation
来实现各自的逻辑。另外Kotlin
标准库会被自动加到相应的sourceSet
中,无需重复引入。比方说上面的kotlinx-coroutines-core
。之后,Android
和iOS
分别使用各自常规方式引入/调用aar
或framework
即可。经过以上的说明,我们大概了解了KMM
,项目的架构,抄一张官网的图进行总结:demo
了。打开Android Studio
,File->New->New Project
,选择Kotlin Multiplatform App
Next
:Next
: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.name
,Platform
的实现如下:interface Platform {
val name: String
}
expect fun getPlatform(): Platform
Platform
是个接口,使用expect
关键字来声明getPlatform()
,再由Android
和iOS
通过使用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 Studio
,configurations list
中选择iosApp
,直接运行即可。shared
模块是如何工作的。先关注一下shared
模块的gradle
文件:plugins {
kotlin("multiplatform")
id("com.android.library")
}
multiplatform
插件,并把此module
作为lib
输出。kotlin {
android()
iosX64()
iosArm64()
iosSimulatorArm64()
}
lib
的关键。我们先看看android()
是怎么编译Android
的aar
的: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)
}
javaTask
和kotlinTask
,然后看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()
做了什么,跟进到K2JVMCompiler
的doExecute()
方法:在实现了一系列的配置之后,我们找到了关键代码: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
的编译过程只做个大概的说明。KotlinMultiplatformPlugin
的setupDefaultPresets()
找到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
整体架构的思考也起到积极的作用。