此函式庫簡化了 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 中心列出了可用版本。