导语:在本文中,我将使用Kotlin的代码共享特性创建一个iOS和Android应用程序。对于Android,我将使用Kotlin/JVM,而对于iOS,我将使用Kotlin/Native。
在本文中,我将使用Kotlin的代码共享特性创建一个iOS和Android应用程序。对于Android,我将使用Kotlin/JVM,而对于iOS,我将使用Kotlin/Native。
你将在本文中学习到以下内容:
1.用Android Studio创建一个Android应用程序;
2.创建一个共享的Kotlin库:
2.1使用Android应用程序;
2.2启动Android应用程序;
3.用Xcode创建一个iOS应用程序:
3.1使用iOS应用程序中共享的Kotlin库;
3.2使用Swift的Kotlin;
3.3启动iOS应用程序
本文的目标是向你介绍如何在Kotlin中共享代码,及其共享代码提供的好处。虽然你以下看到的是一个简化的应用程序,但这只是为了方便讲解,不过以下所举的样本的内容均可以应用于实际应用程序,与其大小或复杂程度无关。
我将要创建的应用程序会简单的在Android上显示Kotlin Rocks on Android,在iOS上显示Kotlin Rocks on iOS <version>,我的想法是共享生成此消息的代码。
公用代码是“Kotlin Rocks on ${platformName()}”,其中platformName()是一个使用expect关键字声明的函数,在实际的适用过程中将特定于某个平台。
构建运行环境
本文的Android部分将使用Android Studio,也可以使用IntelliJ IDEA(java编程语言开发的集成环境)或Ultimate edition(一个测试软硬件系统信息的工具,它可以详细的显示出PC每一个方面的信息)。
应在IDE中安装Kotlin插件1.3.x及其以上版本,这可以通过IDE的设置(或首选项)中的Language & Frameworks | Kotlin更新部分来验证。
为iOS和macOS设备编译需要配有macOS主机操作系统,为此我需要安装和配置Xcode和工具,更多细节请访问苹果开发者网站。
注意:我将使用IntelliJ IDEA 2018.3 EAP, Android Studio 3.2, Kotlin 1.3.0, Xcode 10.0, macOS 10.14, Gradle 4.10.2。
创建一个Android项目
我将通过Start New Android Project项目创建一个新的Android项目,如果使用IntelliJ IDEA,我需要在New Project向导的左侧面板中选择Android。
确保勾选包含Kotlin支持复选框非常重要,只有这样,我可以在向导的下一步中保留默认设置。然后我继续选择Empty Activity选项并点击Next,最后按Finish。
注意,如果使用Kotlin插件的预发布版本或EAP版本,IDE可能无法打开生成的项目,从而导致Gradle导入错误。这是因为build.gradle文件中没有引用正确的Maven存储库,可以通过将以下内容两次添加到每个repositories { .. } 块中来解析它:
maven {url 'https://dl.bintray.com/kotlin/kotlin-eap'}
Kotlin/Native插件需要更新版本的Gradle(一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具),这可以让我修补gradle / wrapper / gradle-wrapper.properties,并使用以下distrubutionUrl:
distributionUrl = https \:/ /services.gradle.org/distributions/gradle-4.10.2-all.zip
我需要刷新Gradle项目设置来应用这些更改,点击Sync Now链接或使用Gradle工具窗口,从root Gradle项目的上下文菜单中点击refresh操作。
此时,我应该就能够编译并运行Android应用程序。
创建共享模块
本文的目标是展示如何让Kotlin代码在Android和iOS之间进行共享。让我从使用平台间共享的代码创建SharedCode项目开始,我将在该项目中创建几个新文件。
添加Kotlin来源
我的想法是根据平台的不同,让每个平台都显示以下类似的文本信息: Kotlin Rocks on Android以及Kotlin Rocks on iOS。这样,我就可以重用生成消息的方式。以下就是我在SharedCode/src/commonMain/kotlin/common.kt下创建的主文件:
package org.kotlin.mpp.mobile expect fun platformName(): String fun createApplicationScreenMessage() : String { return "Kotlin Rocks on ${platformName()}" }
这是很通常的代码部分,这些生成最终消息的代码,期望平台从expect fun platformName(): String函数提供平台名称。而我将使用的是来自Android和iOS应用程序的createApplicationScreenMessage。
现在,我需要在SharedCode/src/androidMain/kotlin/ actu.kt中为Android创建应用文件:
package org.kotlin.mpp.mobile actual fun platformName(): String { return "Android" }
我在SharedCode/src/iosMain/kotlin/ actu.kt中为iOS目标创建了一个类似的文件:
package org.kotlin.mpp.mobile import platform.UIKit.UIDevice actual fun platformName(): String { return UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion }
在这里,我可以使用来自Apple UIKit框架的UIDevice类,它在Java中不可用,它只能在Swift和Objective-C中可用。Kotlin/Native编译器附带了一组预先导入的框架,所以我可以使用UIKit框架,而不需要额外的步骤。Objective-C和Swift互操作在这里有详细介绍。
更新Gradle脚本
SharedCode项目应该生成几个工具包括:
1.Android项目的JAR文件,来自androidMain源代码集;
2.苹果公司的框架:
2.1 iOS设备和App Store (arm64 目标);
2.2 iOS模拟器(x86_64目标)
看看我是如何更新Gradle脚本的?
首先,我将新项目添加到settings.gradle文件中,只需将以下代码行添加到文件末尾即可:
include ':SharedCode'
接下来,我需要使用以下内容创建SharedCode/build.gradle文件:
apply plugin: 'kotlin-multiplatform' kotlin { targets { final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \ ? presets.iosArm64 : presets.iosX64 fromPreset(iOSTarget, 'iOS') { compilations.main.outputKinds('FRAMEWORK') } fromPreset(presets.jvm, 'android') } sourceSets { commonMain.dependencies { api 'org.jetbrains.kotlin:kotlin-stdlib-common' } androidMain.dependencies { api 'org.jetbrains.kotlin:kotlin-stdlib' } } } // workaround for https://youtrack.jetbrains.com/issue/KT-27170 configurations { compileClasspath }
构建跨平台Gradle项目
SharedCode/build.gradle文件使用kotlin-multiplatform插件来实现我需要的东西,在SharedCode/build.gradle文件中,我定义了几个常见的目标,android和iOS。每个目标都有自己的平台。common目标包含每个平台编译中的Kotlin通用代码,允许expect声明。其他目标为来自common目标的所有expect操作提供实际的支持。有关多平台项目的更详细说明,请点此处了解。
下表,是我对以上内容的梳理。
现在是时候在Android Studio中再次刷新Gradle项目了。点击黄色条纹上的Sync,或者使用Gradle工具窗口,在root Gradle项目的上下文菜单中点击Refresh操作。SharedCode项目现在应该可以被IDE识别了。现在,我已经准备好使用Android和iOS应用程序的SharedCode库了。
使用Android共享代码
在本文中,由于我希望尽量减少Android项目的更改,因此我在SharedCode项目中添加了一个普通的依赖项。不过你也可以在Android Gradle项目中直接使用kotlin-multiplatform插件,而不是kotlin-android插件。有关更多信息,请点此了解。
要包括从SharedCode项目到Android项目的依赖关系,就需要修补app/build.gradle文件并将以下代码行包含在dependencies { .. }块中:
implementation project(':SharedCode')
我需要将id分配给活动的TextView控件以便从代码中访问它,我会修补app/src/main/res/layout/activity_main.xml文件(如果我在新项目向导中更改了它,名称可能会有所不同),并向<TextView>元素添加更多属性:
android:id="@+id/main_text" android:textSize="42sp" android:layout_margin="5sp" android:textAlignment="center"
接下来,让我将以下代码行包含在/app/src/main/java/<package>/MainActivity.kt文件的MainActivity类中,直到onCreate方法的末尾:
findViewById<TextView>(R.id.main_text).text = createApplicationScreenMessage()
使用IDE的项目时包括以下缺失的导入行:
import org.kotlin.mpp.mobile.createApplicationScreenMessage
将它放到/app/src/main/java/<package>/MainActivity.kt文件中。
现在我就有了TextView,它将显示由共享代码函数createApplicationScreenMessage()创建的文本。它在Android上显示了Kotlin Rocks on Android。让我看看它是如何工作的。
运行Android应用程序
点击App运行配置,让我的项目在真正的Android设备或模拟器上运行。
现在我可以看到在Android模拟器中运行的应用程序:
创建iOS应用程序
我打开Xcode并选择Create a new Xcode project选项。在该对话框中,我会选择iOS目标并选择Single View App。使用默认值填写下一页,并使用KotlinIOS或其他内容作为产品名称。我会选择Swift作为语言(也可以使用Objective-C),此时,我会指示Xcode将以上设置好的项目放入我项目下的运行文件夹中,稍后我将在配置文件中使用相对路径。
创建的iOS应用程序可以在iOS模拟器或iOS设备上运行,设备运行可能需要Apple开发人员帐户并颁发开发人员证书,而Xcode尽最大努力指导我完成整个过程,以确保我可以在iPhone模拟器或设备上运行该应用程序。
在Xcode中设置框架依赖性
SharedCode会构建生成用于Xcode项目的iOS框架。所有框架都在SharedCode/build/bin文件夹中。它为每个框架目标创建调试和发布版本。这些框架的路径如下:
SharedCode/build/bin/iOS/main/debug/framework/SharedCode.framework SharedCode/build/bin/iOS/main/release/framework/SharedCode.framework
我使用Gradle脚本中的条件来为框架选择目标平台,它可以是iOS arm64,也可以是iOS x86_64,这取决于环境变量。
优化Gradle构建脚本
我需要根据Xcode项目中的选定目标提供正确的框架,这取决于在Xcode中选择的目标配置。另外,我想让Xcode在构建之前为我编译框架,我需要在SharedCode / build.gradle Gradle文件的末尾包含附加任务。
task packForXCode(type: Sync) { final File frameworkDir = new File(buildDir, "xcode-frameworks") final String mode = System.getenv('CONFIGURATION')?.toUpperCase() ?: 'DEBUG' inputs.property "mode", mode dependsOn kotlin.targets.iOS.compilations.main.linkTaskName("FRAMEWORK", mode) from { kotlin.targets.iOS.compilations.main.getBinary("FRAMEWORK", mode).parentFile } into frameworkDir doLast { new File(frameworkDir, 'gradlew').with { text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \[email protected]\n" setExecutable(true) } } } tasks.build.dependsOn packForXCode
请注意,如果使用早于4.10版本的Gradle,则任务可能无法正常工作。在本文中,我已将其升级到4.10.2。
现在,我会切换回Android Studio并从Gradle工具窗口执行SharedCode项目的构建目标。该任务查找由Xcode构建设置的环境变量,并将框架的正确变体复制到SharedCode/build/xcode-frameworks文件夹中。然后,我将该文件夹中的框架包含到构建中。
设置Xcode
我会将SharedCode框架添加到Xcode项目中。为此,我要点击project navigator的根节点并选择目标设置。接下来,我点击嵌入式二进制文件部分中的+,点击对话框中的Add Other…按钮,从磁盘中选择框架。我可以选择以下文件夹:
SharedCode/build/xcode-frameworks/SharedCode.framework
然后我会看到类似的东西:
另外,我还需要禁用项目中的Bitcode功能。Kotlin/Native生成的是完全适用于本机的二进制文件,而不是LLVM的bitcode,因此我需要导航到Build Settings选项卡,选择下面的All子选项卡,然后在搜索字段中输入bitcode,选择No作为“启用Bitcode”选项。
现在我需要向Xcode解释,在哪里寻找框架。我需要添加相对路径$(SRCROOT)/../../SharedCode/build/xcode框架到 Search Paths | Framework Search Paths选项。再次打开Build Settings选项卡,选择下面的All子选项卡,然后在Search字段中输入Framework Search Paths,以便轻松找到该选项,然后,Xcode将在用户界面中显示替换路径。
最后一步是让Xcode调用我的Gradle构建,以便在每次运行之前准备好共享代码框架。此时,我需要打开Build Phases选项卡并单击+以添加New Run Script Phase并将以下代码添加到其中。
cd "$SRCROOT/../../SharedCode/build/xcode-frameworks" ./gradlew :SharedCode:packForXCode
注意,这里我使用的是$SRCROOT/../..作为我Gradle项目的根路径。它可以取决于创建Xcode项目的方式,另外,我使用生成的SharedCode / build / xcode-frameworks / gradlew脚本,packForXCode任务生成它。我假设在新设备上打开Xcode项目之前,Gradle构建至少执行一次。
我应该将创建的构建阶段拖到列表的顶部:
现在,我已经准备好开始编写iOS应用程序,并使用刚刚使用过的Kotlin代码。
从Swift调用Kotlin代码
请记住,我的目标是在屏幕上显示文本消息。如你以上所见,我的iOS应用程序在屏幕上什么内容都不会显示。要让它用文本消息显示UILabel(UILabel继承自UIView是iOS中使用非常频繁的一个视图控件一般用于显示文字)。我需要使用以下代码替换ViewController.swift文件的内容:
import UIKit import SharedCode class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let label = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 21)) label.center = CGPoint(x: 160, y: 285) label.textAlignment = .center label.font = label.font.withSize(25) label.text = CommonKt.createApplicationScreenMessage() view.addSubview(label) } }
我会使用import SharedCode函数来将共享代码导入我的框架,并会使用Kotlin代码编译Kotlin/Native。接下来,我将通过它调用称为CommonKt.createApplicationScreenMessage()的Kotlin函数。更多细节,请点此查看。
现在,我已准备好在模拟器或iOS设备上启动应用程序。
运行iOS应用程序
现在,我可以点击Xcode中的Run按钮,看到我的应用程序的运行。
总结
在本文中我讲到了以下知识点:
· 在Android Studio中创建了一个Android应用程序;
· 在Xcode中创建了一个iOS应用程序;
· 新增了Kotlin跨平台子项目;
· 使用共享的Kotlin代码;
· 将其编译为Android Jar;
· 将其编译为iOS 框架;
· 让Android和iOS重新使用共享的Kotlin代码;
你可以在GitHub上找到本文的全部代码。
本文只是iOS和Android以及其他平台与Kotlin,Kotlin/Native和Kotlin多平台项目之间共享Kotlin代码的一个研究样本。你可以在实际中,将此研究应用到更复杂的情况中。
在平台之间共享代码是一项高难度的技术,但如果没有我在Android,JVM或iOS平台中使用的丰富API,可能很难实现。不过,也可以使用多平台库来解决这个问题。它们直接在常见的Kotlin代码中引入了丰富的API。这样的库有: