惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

Kevin Blog

产品随想 2 产品力与人才密度 产品随想 1 做营销还是做产品 Play PyTorch Stable Diffusion and ONNX, Ollama on Intel Core Ultra 5 225H Ubuntu 25.04 Play with ROCm, PyTorch, Ollama on Ubuntu 24.04 and 780m 写在新的旅程开始前 想念自然与青春 懒猫微服体验——自由协作的神器 没有光纤的日子怎么上网?自制 Home WI-FI! Swift on Server Tour 6 关联 User 和 Post Swift on Server Tour 5 创建 Users Swift on Server Tour 4 构建 Post Controller Swift on Server Tour 3 构建 Post 的 API 将不懂的日语一拍扫尽,介绍捧读全新的「OCR 工作台」功能 Swift on Server Tour 2 连通你的数据库与服务器 Swift on Server Tour 1 你的第一个 Server App 以及它背后的故事 Swift on Server Tour 0: 为什么这可能是你的好选择 纪念左耳朵耗子 How to learn Japanese by reading Novels and News with the help of Oyomi. Write WebAssembly in Swift and use it in Swift App BenQ WiT ScreenBar Halo 体验报告 记录 2021 年考驾照的体验 捧读的 EPUB 日语轻小说阅读器来了 使用 Go Mobile 开发跨平台 Library 小番茄 - 一个只有陪伴的自习室
使用 Kotlin Native 开发跨平台 Library
2021-06-21 · via Kevin Blog

拓展阅读

使用 Go Mobile 开发跨平台 Library

为什么想使用 Kotlin Native?

我的两款日语学习产品「50音起源」「捧读」都是多平台产品,在开发「50音起源」的时候,我选择了平台 Native 技术,虽然有一定的维护成本,但初期觉得工作量还好,不过后来慢慢懒了起来,就逐渐有了放羊的心。

因此当「捧读」在开发的时候,我尝试用 Flutter 解决这个问题,但是上线后又很不幸的发现, Flutter 对 Apple 的技术栈支持并不积极,比如 Catalyst 不能使用,还有一些小性能问题,所以最后「捧读」的 iOS 版本又用 UIKit 重写了。

最近给 Android 版本的「50音起源」做更新的时候,尝试用 Jectpack Compose 写了一个「设置」界面,感觉还不错,声明式,状态化,实时预览,开发效率很高,它即不基于旧的 Android UI Toolkit,没有历史遗留问题,很好的解决了 Android 上以往痛苦无比的 UI 开发过程,也不像 SwiftUI 那样绑定到了系统中,开发者可以自行更新 App 使用的 Jetpack Compose 版本。

所以这段时间,Jectpack Compose 极速拉升了我对 Android 开发的好感。关于它的诸多理念,可以观看官方 19 年的 Session

我在做跨平台开发时,通常有下面几个痛点

  1. 期望无缝结合平台原本已有的特性,比如特有 UI 控件,SDK 的 API。
  2. 期望性能不打折扣,没有用户体验的妥协
  3. 期望不受限于跨平台开发技术的限制,能第一时间跟进最新的设备,系统

如果把这三点考虑进去,最好的方式就是使用原生 UI Toolkit,在数据层上做跨平台。但以往 Android UI 开发工作别繁琐,导致我后来宁愿 Flutter 也不要用 Android 自己的 UI Toolkit.

但现在有了 Jetpack Compose 这种好用的 Toolkit,我可以下个决心只做数据层跨平台了。

那选什么语言呢?

Go Mobile 好像可以,Rust 也有这方面的愿景,但我还是最喜欢写得舒服的 Swift。

可 Swift 显而易见是不适合跑 Android 上的,那 Kotlin 呢?

Kotlin 不仅和 Swift 语法相近,还从官方立场就做了跨平台的完善支持,最近 Swift Package Manager 支持了 XCFramework Bundle 这意味着,只要 Kotlin Native 能编译出不同 Apple 架构的 Framework,就可以轻松的打包成一个 Swift Package 进行跨平台使用以及分发了。

嗅到了「有戏」的味道后,决定试试搞了有些年头的 Kotlin Native。

Kotlin Native 的跨平台原理

Kotlin Native 的跨平台可以说是巨全无比了

  • JVM
  • JS
  • Android / Android NDK
  • Apple
  • Linux
  • Windows
  • WebAssembly

简而言之,Kotlin 虽然可以跑在 JVM 上,也能调用 Java 代码,但 Kotlin 并不是 Java,借助于 LLVM,Pure Kotlin Code 可以编译成平台代码,实现无 VM 跨平台。

它编译出的可以是 executable,也可以是 library,当然还可以是 Apple framework.

写一个 API SDK: HappyNasa

我完全不担心 Kotlin Native 在 Android 上的使用问题,因此主要想得出的结论是和 Swift 一起用怎么样。

一个典型的使用场景就是用 Kotlin Native 写一个 API SDK,由 SDK 负责请求 API 并解析 JSON,并返回一个反序列化后的对象给 Swift.

首先我们需要一个 API.

在网上找了会,我发现 NASA 有一个 Astronomy Picture of the Day 的 API 很有意思,你可以访问这个 Astronomy Picture of the Day 页面查看.

NASA 提供的 API URL 是这样的

https://api.nasa.gov/planetary/apod?api_key={API_KEY}

你可以到 https://api.nasa.gov/ 申请自己的 API_KEY.

接下来就是创建一个 Kotlin Native 项目,我使用的是 IntelliJ IDEA

File -> New -> Project

像下图这样选择 Mobile Library

Screen-Shot-2021-06-22-at-00.32.11

默认会创建三个 target

  • common
  • android
  • ios
flat-structure

继续简而言之,common 里的代码是通用代码,理论上 SDK 最核心的逻辑就应该放在这里面,而 android 和 ios 可以使用 shortcuts 的特性,继承 common,并可以使用自属平台接口.

用法从 Greeting 类的 Platfrom 变量的传递上就可以看出来。

commonMain/kotlin/me.zhoukaiwen.library/Greeting.kt

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

iosMain/kotlin/me.zhoukaiwen.library/Platform.kt

import platform.UIKit.UIDevice

actual class Platform actual constructor() {
    actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}

如果你现在直接按下 Build,完成后,就可以在项目目录的 build/bin/ios 文件夹里找到编译好的 Framework,开箱即用.

进行网络请求

现在你可以到我的 repo kotlin-native-library-demo 上去下载已经完工的项目

首先是需要配置 build.gradle.kts序列化网络请求库 Ktor 的依赖给加上,重点步骤是下面这几个

// 配置 serialization plugin
// https://ktor.io/docs/json.html#kotlinx_dependency
// https://github.com/Kotlin/kotlinx.serialization#setup
// https://kotlinlang.org/docs/mpp-discover-project.html#multiplatform-plugin
plugins {
    kotlin("plugin.serialization") version "1.5.10"
}

kotlin {
  // 配置 target
  // https://kotlinlang.org/docs/mpp-discover-project.html#targets
  val macos = macosX64("macos")
  val ios = iosX64("ios")
  val iosArm64 = iosArm64("iosArm64")
  
  // https://kotlinlang.org/docs/mpp-discover-project.html#source-sets
  sourceSets {
    val commonMain by getting {
                dependencies {
                // 添加 ktor core 到 common
                // https://ktor.io/docs/http-client-multiplatform.html#add-dependencies
                // https://kotlinlang.org/docs/mpp-add-dependencies.html
                    implementation("io.ktor:ktor-client-core:$ktorVersion")
                    implementation("io.ktor:ktor-client-serialization:$ktorVersion")
                }
            }
            
    val iosMain by getting {
        dependsOn(commonMain)
        dependencies {
            // 添加 ktor for ios
            implementation("io.ktor:ktor-client-ios:$ktorVersion")
        }
    }

    val macosMain by getting {
        dependsOn(commonMain)
        dependencies {
            // 添加 ktor for macOS
            implementation("io.ktor:ktor-client-curl:$ktorVersion")
        }
    }

    // 配置每个 target 编译出来的 Framework 名称
    // https://kotlinlang.org/docs/mpp-build-native-binaries.html#declare-binaries
    configure(listOf(ios, iosArm64, macos)) {
        // listOf(RELEASE) 是指 Build 时只编译 Release 版本
        binaries.framework(listOf(RELEASE)) {
            baseName = "HappyNasa"
        }
    }
  }
}

关于这个文件的结构说明,可以参考官方文档 Discover Project 以及 Build final native binaries

接下来就可以编写请求逻辑了

commonMain/kotlin/me.zhoukaiwen.library/Nasa.kt

@Serializable
@SerialName("APOD")
data class APOD(val date: String,
                val explanation: String,
                val hdurl: String,
                val media_type: String,
                val service_version: String,
                val title: String,
                val url: String)

class NASA(private val apiKey: String) {

    val NASAEntryPoint.fullPath: String
        get() {
            return nasaBaseURL + this.path
        }

    private val client = HttpClient() {
        install(JsonFeature) {
            serializer = KotlinxSerializer()
        }
    }

    private val nasaBaseURL: String = "https://api.nasa.gov"

    enum class NASAEntryPoint(val path: String) {
        APOD( "/planetary/apod")

    }

    suspend fun getAPOD(): APOD? {
        val response: HttpResponse =  client.get(NASAEntryPoint.APOD.fullPath) {
            parameter("api_key", apiKey)
        }

        return try {
            val apod: APOD = response.receive()
            apod
        } catch (e: NoTransformationFoundException) {
            null
        }
    }
}

现在按下 Build,我们就可以在 build/bin 下找到 ios iosArm64 macos 这三个平台的 Framework.

创建 Swift Package

首先是使用 xcodebuild 合并 Framework 成一个 xcframework.

xcodebuild -create-xcframework -framework ./lib/iosArm64/releaseFramework/HappyNasa.framework -framework ./lib/ios/releaseFramework/HappyNasa.framework -framework ./lib/macos/releaseFramework/HappyNasa.framework -output ./happy_lib.xcframework

随后就是新建我们的 Swift Package 项目了,创建的教程就略过吧,你可以直接到 Github 下载已经完工的项目 kotlin_native_swift_package_demo_lib

配置 Package.swift

import PackageDescription

let package = Package(
    name: "kotlin_demo_lib",
    products: [
        .library(
            name: "kotlin_demo_lib",
            targets: ["kotlin_demo_lib", "HappyNasa"]),
    ],
    targets: [
        .target(
            name: "kotlin_demo_lib",
            dependencies: []),
        .binaryTarget(
                    name: "HappyNasa",
                    path: "Sources/happy_lib.xcframework"),
        .testTarget(
            name: "kotlin_demo_libTests",
            dependencies: ["kotlin_demo_lib"]),
    ]
)

主要的操作就是增加 binaryTarget,参考官方文档的 Declare a Binary Target in the Package Manifest 即可。

在 iOS 中使用 HappyNasa

新建一个 Xcode 的 Swift 项目,加入我们刚刚完工的 Swift Package 的引用,你可以在我的 repo kotlin_native_ios_demo 这里下载完工的项目。

Screen-Shot-2021-06-22-at-01.09.26

需要注意的是我引用的是本地地址,如果你直接 clone 了我的项目,那么请重新添加 Swift Pakcage 的依赖。

ViewController.swift

import UIKit
import kotlin_demo_lib
import HappyNasa

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let a = Greeting().greeting()
        print(a)

        let nasaClient = NASA.init(apiKey: "{API_KEY}")
        nasaClient.getAPOD { apod, error in
            if let apod = apod {
                print(apod.title)
            } else {
                print("Get apod failed")
            }
        }
    }
}

使用起来感觉还是蛮优雅的,Kotlin 的 suspend 函数被翻译成了 Swift 的 completion handler. 在 Kotlin 中定义的对象 APOD 也可以正常访问属性。

结语

Kotlin Native 看起来是个很不错的跨平台方案,既有高级语言的特性,又能很完美的针对多平台进行编译。

当然,目前还是有美中不足的部分的,Catalyst 和 Apple Silicon 的架构支持还在进行中,根据官方的issue KT-40442 KT-45302 Kotlin 1.5.30 发布的时候,我们应该就可用上了。

但我还有一个梦想,有一天 Swift 可以像 Kotlin 这样十分方便的跑在各种平台上。

其他资源

Kotlin-Multiplatform-Libraries Interoperability with C Kotlin/Native as an Apple framework – tutorial