该库简化了 Kotlin/JVM 和 Kotlin/Native 程序中 Kotlin/JS 库的使用。它使得获取代码就像获取数据一样简单:
Zipline 的工作原理是将 QuickJS JavaScript 引擎嵌入到您的 Kotlin/JVM 或 Kotlin/Native 程序中。它是一个小型且快速的 JavaScript 引擎,非常适合嵌入到应用程序中。
(正在寻找 Android 版 Duktape?)
让我们制作一个问答游戏,即使我们的用户不更新他们的应用程序,每天都会有新问题。我们在commonMain
中定义接口,以便可以从 Kotlin/JVM 调用它并在 Kotlin/JS 中实现它。
interface TriviaService : ZiplineService {
fun games (): List < TriviaGame >
fun answer ( questionId : String , answer : String ): AnswerResult
}
接下来我们在jsMain
中实现它:
class RealTriviaService : TriviaService {
// ...
}
让我们将 Kotlin/JS 中运行的实现连接到 Kotlin/JVM 中运行的接口。在jsMain
中,我们定义一个导出函数来绑定实现:
@JsExport
fun launchZipline () {
val zipline = Zipline .get()
zipline.bind< TriviaService >( " triviaService " , RealTriviaService ())
}
现在我们可以启动一个开发服务器来为任何请求它的正在运行的应用程序提供 JavaScript 服务。
$ ./gradlew -p samples trivia:trivia-js:serveDevelopmentZipline --info --continuous
请注意,此 Gradle 永远不会达到 100%。这是预料之中的;我们希望开发服务器保持运行。另请注意,只要代码更改, --continuous
标志就会触发重新编译。
您可以在 localhost:8080/manifest.zipline.json 中查看所提供的应用程序清单。它引用应用程序的所有代码模块。
在jvmMain
中,我们需要编写一个程序来下载 Kotlin/JS 代码并调用它。我们使用ZiplineLoader
来处理代码下载、缓存和加载。我们创建一个Dispatcher
来运行 Kotlin/JS。这必须是单线程调度程序,因为每个 Zipline 实例必须仅限于单个线程。
suspend fun launchZipline ( dispatcher : CoroutineDispatcher ): Zipline {
val manifestUrl = " http://localhost:8080/manifest.zipline.json "
val loader = ZiplineLoader (
dispatcher,
ManifestVerifier . NO_SIGNATURE_CHECKS ,
OkHttpClient (),
)
return loader.loadOnce( " trivia " , manifestUrl)
}
现在我们构建并运行 JVM 程序以将它们组合在一起。在与开发服务器不同的终端中执行此操作!
$ ./gradlew -p samples trivia:trivia-host:shadowJar
java -jar samples/trivia/trivia-host/build/libs/trivia-host-all.jar
Zipline 可以轻松地与 Kotlin/JS 共享接口。在commonMain
中定义一个接口,在 Kotlin/JS 中实现它,并从主机平台调用它。或者做相反的事情:在主机平台上实现它并从 Kotlin/JS 调用它。
桥接接口必须扩展ZiplineService
,它定义一个close()
方法来释放所持有的资源。
默认情况下,参数和返回值是按值传递的。 Zipline 使用 kotlinx.serialization 对跨越边界传递的值进行编码和解码。
从ZiplineService
扩展的接口类型是按引用传递的:接收者可以调用实时实例上的方法。
接口功能可能会暂停。 Zipline 在内部实现了setTimeout()
以使异步代码按照 Kotlin/JS 中的预期工作。
Zipline 还支持Flow<T>
作为参数或返回类型。这使得构建反应式系统变得容易。
嵌入 JavaScript 的一个潜在瓶颈是等待引擎编译输入源代码。 Zipline 将 JavaScript 预编译为高效的 QuickJS 字节码,以消除这种性能损失。
另一个瓶颈是等待代码下载。 Zipline 通过支持模块化应用程序解决了这个问题。每个输入模块(如 Kotlin 的标准库、序列化库和协程库)都是同时下载的。每个下载的模块都会被缓存。模块还可以嵌入主机应用程序中,以避免在网络无法访问时进行任何下载。如果您的应用程序模块的更改比您的库更频繁,则用户仅下载更改的内容。
如果您在 QuickJS 运行时遇到性能问题,Zipline 包含一个采样分析器。您可以使用它来详细了解应用程序如何花费其 CPU 时间。
Zipline 通过将消息转发到主机平台来实现console.log
。它在 Android 上使用android.util.Log
,在 JVM 上使用java.util.logging
,在 Kotlin/Native 上使用stdout
。
Zipline 将 Kotlin 源映射集成到 QuickJS 字节码中。如果您的进程崩溃,堆栈跟踪将打印.kt
文件和行号。即使底层有 JavaScript,开发人员也不需要与.js
文件交互。
使用桥接接口后,必须将其关闭,以便对等对象可以被垃圾收集。这很难做到正确,因此 Zipline 借鉴了 LeakCanary 的想法,并积极检测何时错过了close()
调用。
Zipline 支持 EdDSA Ed25519 和 ECDSA P-256 签名来验证下载的库。
设置非常简单。生成 EdDSA 密钥对。此任务是通过 Zipline Gradle 插件安装的。
$ ./gradlew :generateZiplineManifestKeyPairEd25519
...
---------------- ----------------------------------------------------------------
ALGORITHM: Ed25519
PUBLIC KEY: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
PRIVATE KEY: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
---------------- ----------------------------------------------------------------
...
将私钥放在构建服务器上并将其配置为签署构建:
zipline {
signingKeys {
create( " key1 " ) {
privateKeyHex.set( .. .)
algorithmId.set(app.cash.zipline.loader. SignatureAlgorithmId . Ed25519 )
}
}
}
将公钥放入每个主机应用程序中并配置它以验证签名:
val manifestVerifier = ManifestVerifier . Builder ()
.addEd25519( " key1 " , .. .)
.build()
val loader = ZiplineLoader (
manifestVerifier = manifestVerifier,
.. .
)
签名和验证都接受多个密钥以支持密钥轮换。
Zipline 旨在随时随地运行您组织的代码。它不提供沙箱或进程隔离,也不应用于执行不受信任的代码。
重要的是要记住,这种设计将隐式信任置于:
它不能防止上述任何形式的妥协。
此外,它还没有提供一种机制来禁止存在已知问题的旧(签名)版本的可执行代码。
您可以采取一些措施来确保热重载尽可能快地运行:
kotlin.incremental.js.ir=true
以启用 Kotlin/JS 增量编译。org.gradle.unsafe.configuration-cache=true
以启用 Gradle 配置缓存。tasks.withType(DukatTask::class) { enabled = false }
以关闭Dukat任务。Zipline 适用于 Android 4.3+(API 级别 18+)、Java 8+ 和 Kotlin/Native。
Zipline 在其实现中使用不稳定的 API,并且对这些组件的版本更新很敏感。
成分 | 支持版本 | 笔记 |
---|---|---|
Kotlin 编译器 | 2.0.0 | Kotlin 编译器插件还没有稳定的 API。 |
Kotlin 序列化 | 1.6.3 | 对于decodeFromDynamic() 、 encodeToDynamic() 和ContextualSerializer 。 |
Kotlin 协程 | 1.8.1 | 对于transformLatest() 、 Deferred.getCompleted() 和CoroutineStart.ATOMIC 。 |
我们打算在稳定的 API 可用后立即使用它们。
我们打算保持 Zipline 主机和运行时版本的互操作性,以便您可以独立升级每个版本。
主机Zipline版本 | 支持的运行时 Zipline 版本 |
---|---|
0.x | 与主机完全相同的 0.x 版本。 |
1.x | 任何 1.x 版本。 |
Copyright 2015 Square, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
该项目以前称为 Duktape-Android,打包了适用于 Android 的 Duktape JavaScript 引擎。 Duktape 历史记录和发布标签仍然存在于该存储库中。 Maven 中心列出了可用版本。