大家好,我是 imike! ! !
Swift 6 顛覆性的 Android NDK 支援終於讓我可以發布JNIKit了,這是我自 Swift 5 以來一直在為SwifDroid專案建置的便捷工具!現在最大的障礙已經消失:我們可以直接import Android
,而不必費力地手動匯入頭檔。雖然最後一步,也就是官方的二進位檔案生成,仍然由finagolfin出色的swift-android-sdk (Swift Stream 使用的工具)負責,但 Swift 專案已經計劃將其作為官方 SDK。
今天,我想向你展示如何為 Android 編寫你的第一個真正的原生 Swift 程式碼。這將會是一段有趣的旅程,所以,準備好一杯茶,讓我們開始吧。
VSCode 的Swift Stream IDE擴展
或者,安裝 Android Studio,以便稍後使用真正的 Android 應用程式測試您的程式庫。
只要您的作業系統可以執行 Docker 和 VSCode,它就不重要。
安裝 Docker 後,開啟 VSCode。
首先,請確保您已安裝Dev Containers 擴充功能。
接下來,確保Swift Stream IDE擴充也已安裝。
如果您還沒有這些擴展,只需在市場上搜尋它們並點擊Install
(您的 Captain Obvious 😄)
在 VSCode 的左側邊欄上,按一下 Swift Stream 圖示(小鳥)。
....然後點選Start New Project
😏
現在,輸入您的專案名稱,例如MyFirstAndroidLib
。
您將看到新專案預設會建立在您的主資料夾中。您可以點擊三個點按鈕來選擇其他資料夾。
下一步是選擇專案類型。對我們來說,是Android -> Library
。
按一下Create Project
。
接下來,輸入庫的 Java 命名空間。這通常是網域的反向名稱(例如com.example.mylib
)。
輸入命名空間後,按 Enter 進入下一步,其中選擇Android Min SDK Version
。
我建議根據您的需求選擇24或29 。再次按Enter
選擇Android Compile SDK Version
。
就目前而言, 35是一個不錯的選擇。再次按下Enter
,開始建立專案。
此時,VSCode 將建立一個包含所有專案檔案的資料夾,並開始下載具有可立即使用的 Android 開發環境的 Docker 映像。
下載鏡像後,它將啟動容器並開啟一個新的 VSCode 窗口,其中包含您的專案。然後,容器將下載 Swift 工具鏈、Swift for Android SDK、Gradle、Android NDK 和 Android SDK。這些工具會快取在共享的 Docker 磁碟區中,因此您的下一個專案將在幾秒鐘內建立完成。但是,首次啟動可能需要一些時間,請耐心等待。
一切就緒!可以開始寫程式了!
Java 原生介面 (JNI) 是原生程式碼與 Java 虛擬機器通訊的橋樑。具體來說:如果您編寫的是 Java 程式碼,則可以使用 Android SDK。但如果您使用的語言(例如 Swift 或 C++)無法編譯為 Java 字節碼,則需要 Android NDK 透過 JNI 與 Java 進行通訊。
使用 JNI,您幾乎可以完成任何用 Java 可以完成的工作 — — 真正的挑戰是以一種不那麼令人頭疼的方式來完成它。
這正是 JNIKit 的用武之地!為了舒適高效地執行,我們需要一個便捷的層,將那些低階的 C 風格 JNI 呼叫包裝成更優雅、更 Swift 風格的程式碼。這正是 JNIKit 的用武之地。
從本質上講,它是一個純粹的 Swift 套件管理器專案。關鍵依賴項是JNIKit
以及帶有swift-log
的AndroidLogging
。
您的 Swift 程式碼預設位於Sources/<target_name>/Library.swift
中。
Android 函式庫(一個 Gradle 專案)位於Library
資料夾中。首次 Swift 建置後,此資料夾將自動產生。或者,您也可以從 Swift Stream 側邊欄面板手動建立它。
一切都始於一個initialize
方法。此方法暴露給 Java/Kotlin 端,必須在任何其他原生方法之前呼叫。
以下程式碼顯示如何使用@_cdecl
為 JNI 公開此方法。
@_cdecl
命名約定至關重要,因為它遵循 JNI 模式:
Java_<package>_<class>_<method>
package
是使用底線而不是點的完全限定套件名稱
class
是類別名稱
method
是方法名稱
此方法的參數也遵循 JNI 約定。前兩個參數是必需的,由 JNI 自動傳遞:
envPointer
:這個指標永遠不會改變。它是指向 JNI 環境的指針,是與 JVM 互動的主要介面。
clazzRef
或thizRef
:如果 Java 方法是靜態的(例如本例中,該方法位於 Kotlin object
內部),則傳回clazzRef
。如果是實例方法,則傳回thizRef
。前者是指向類別的指標;後者是指向實例的指標。
這些參數之後的任何參數都代表 Java/Kotlin 方法本身的參數。在我們的例子中,該方法有一個額外的參數:一個caller
物件。我們從應用程式中傳遞它來提供上下文。這個caller
實例對於快取應用程式的類別載入器(稍後會詳細介紹)是必需的。注意:如果我們使用的是thizRef
而不是clazzRef
,我們可能不需要傳遞這個額外的caller
物件。
#if os(Android)
@_cdecl("Java_to_dev_myandroidlib_myfirstandroidproject_SwiftInterface_initialize")
public func initialize(
envPointer: UnsafeMutablePointer<JNIEnv?>,
clazzRef: jobject,
callerRef: jobject
) {
// Activate Android logger
LoggingSystem.bootstrap(AndroidLogHandler.taggedBySource)
// Initialize JVM
let jvm = envPointer.jvm()
JNIKit.shared.initialize(with: jvm)
// ALSO: class loader cache example
// ALSO: `toString` example
// ALSO: `Task` example
}
#endif
方法主體顯示我們首先使用 Android 記錄器引導 Swift 日誌系統(這只需要執行一次)。
之後,我們可以在任何地方使用記錄器,就像這樣:
let logger = Logger(label: "🐦🔥 SWIFT")
logger.info("🚀 Hello World!")
然後,我們初始化與 JVM 的連線。現在,一切就緒!
這裡有一個常見的問題:預設情況下,當您嘗試透過 JNI 尋找 Java 類別時,它會使用系統類別載入器。這個系統載入器(驚喜吧!)無法辨識您應用程式中動態載入的類別——這意味著它會錯過您自己的類別以及任何 Gradle 依賴項。
解決方案?我們需要取得應用程式的類別載入器,它可以透過.getClass().getClassLoader()
從任何 Java 物件取得。最佳實踐是在初始化期間獲取該類別載入器一次,建立對它的全域引用,將其儲存在 JNIKit 的快取中,並在任何地方使用它。它在整個應用程式生命週期內都有效。
以下是在initialize
方法中快取的方法:
// Access current environment
let localEnv = JEnv(envPointer)
// Convert caller's local ref into global ref
let callerBox = callerRef.box(localEnv)
// Defer block to clean up local references
defer {
// Release local ref to caller object
localEnv.deleteLocalRef(callerRef)
}
// Initialize `JObject` from boxed global reference to the caller object
guard let callerObject = callerBox?.object() else { return }
// Cache the class loader from the caller object
// it is important to load non-system classes later
// e.g. your own Java/Kotlin classes
if let classLoader = callerObject.getClassLoader(localEnv) {
JNICache.shared.setClassLoader(classLoader)
logger.info("🚀 class loader cached successfully")
}
注意:如果您的本機方法是實例方法,則應使用thizRef
而不是callerRef
。
toString()
嗎?是的,當然!這是一個至關重要的 Java 方法,JNIKit 讓它的使用變得非常簡單:
logger.info("🚀 caller description: \(someObject.toString())")
JNIEnv
與目前執行緒綁定。這個環境就像一座橋樑,負責完成所有操作,將呼叫傳遞到 JVM 或從 JVM 中傳出。
如果切換線程(例如,在Task
中),則必須將 JNI 環境附加到該新線程。 JNIKit 為此提供了一個簡單的方法: JEnv.current()
。
Task {
// Access current environment in this thread
guard let env = JEnv.current() else { return }
logger.info("🚀 new env: \(env)")
// Print JNI version into LogCat
logger.info("🚀 jni version: \(env.getVersionString())")
}
public final class SwiftInterface {
static {
System.loadLibrary("MyFirstAndroidProject");
}
private SwiftInterface() {}
public static native void initialize(Object caller);
}
object SwiftInterface {
init {
System.loadLibrary("MyFirstAndroidProject")
}
external fun initialize(caller: Any)
}
Swift Stream 會為你產生 Kotlin 文件,所以我們就堅持這樣做。稍後我們會看到更多 JNI 範例 🙂
好了,開始建造!切換到左側邊欄的Swift Stream
選項卡,然後點選Project -> Build
。
系統將提示您選擇Debug
或Release
方案。
現在讓我們進行Debug
。建置過程即將開始。
在 Swift Stream 中,您可以選擇Log Level
來控制看到的詳細資訊量:
普通的
詳細(這是預設設定)
詳細
難以忍受(當你真的需要看到一切時)
使用預設的詳細級別,您將在建置過程中看到類似如下的輸出:
🏗️ Started building debug
💁♂️ it will try to build each phase
🔦 Resolving Swift dependencies for native
🔦 Resolved in 772ms
🔦 Resolving Swift dependencies for droid
🔦 Resolved in 2s918ms
🧱 Building `MyFirstAndroidProject` swift target for arm64-v8a
🧱 Built `MyFirstAndroidProject` swift target for `.droid` in 10s184ms
🧱 Building `MyFirstAndroidProject` swift target for armeabi-v7a
🧱 Built `MyFirstAndroidProject` swift target for `.droid` in 7s202ms
🧱 Building `MyFirstAndroidProject` swift target for x86_64
🧱 Built `MyFirstAndroidProject` swift target for `.droid` in 7s135ms
🧱 Preparing gradle wrapper
🧱 Prepared gradle wrapper in 1m50s
✅ Build Succeeded in 2m20s
如您所見,初始 Swift 編譯本身非常快,三個架構目標(arm64-v8a、armeabi-v7a 和 x86_64)總共耗時約30 秒。大部分時間(1 分 50 秒)花在了初始gradle wrapper
設定上,這是一次性開銷。
好訊息是,後續建置速度會非常快,三個目標只需大約 3 秒!這是因為所有內容都被快取了。
此建置命令也會自動為您產生 Java 庫 Gradle 專案。現在,它已準備好在Library
資料夾中使用。
Swift Stream 為您的程式庫產生初始樣板程式碼,然後您可以維護和擴充它。
以下是產生的 Kotlin 介面的範例:
import android.util.Log
object SwiftInterface {
init {
System.loadLibrary("MyFirstAndroidProject")
}
external fun initialize(caller: Any)
external fun sendInt(number: Int)
external fun sendIntArray(array: IntArray)
external fun sendString(string: String)
external fun sendDate(date: Date)
external fun ping(): String
external fun fetchAsyncData(): String
}
Swift Stream IDE 會自動管理您的 Gradle 專案。它會根據Package.swift
中的 Swift 目標產生 Java 包,並保持所有 Gradle 檔案同步。
在Library/settings.gradle.kts
中,它管理特殊註釋標籤中包含的目標清單:
// managed by swiftstreamide: includes-begin
include(":myfirstandroidproject")
// managed by swiftstreamide: includes-end
在每個Library/<target>/build.gradle.kts
檔案中,它會根據 Swift 程式碼中的匯入和您正在使用的 Swift 版本自動管理依賴項:
implementation("com.github.swifdroid.runtime-libs:core:6.1.3")
// managed by swiftstreamide: so-dependencies-begin
implementation("com.github.swifdroid.runtime-libs:foundation:6.1.3")
implementation("com.github.swifdroid.runtime-libs:foundationessentials:6.1.3")
implementation("com.github.swifdroid.runtime-libs:i18n:6.1.3")
// managed by swiftstreamide: so-dependencies-end
預設情況下,這些依賴項會自動從 SwifDroid runtime-libs
JitPack 倉庫中獲取,該倉庫為每個支援的 Swift 版本維護。這意味著無需手動從 Android SDK 套件複製.so
檔!
但是,如果您需要更多控制權,您可以手動接管,而無需手動複製文件的麻煩。 Swift Stream IDE 使用設定檔 ( .vscode/android-stream.json
),您可以在其中設定soMode
:
"soMode": "Packed"
"Packed"
(預設)表示 Gradle 從 JitPack 匯入所有內容。您可以切換到"PickedManually"
以僅指定您實際需要的.so
檔:
"soMode": "PickedManually",
"schemes": [
{
"title": "MyFirstAndroidProject Debug",
"soFiles": [
"libandroid.so",
"libc.so",
"libm.so"
]
}
]
此設定檔也是您控制其他關鍵專案設定的地方:
"packageName": "to.dev.myandroidlib",
"compileSDK": 35,
"minSDK": 24,
"javaVersion": 11,
您甚至可以將自訂參數直接傳遞給 Swift 編譯器以進行細微控制:
"schemes": [
{
"title": "MyFirstAndroidProject Debug",
"swiftArgs": []
}
]
最後,要建立可分發的 Android 庫檔案( .aar
),只需點擊 Swift Stream 側邊欄中的Java Library Project -> Assemble
。
此指令在背景執行gradlew assembleDebug
或gradlew assembleRelease
,將所有內容打包以供分發。
現在到了最有趣的部分,讓我們在一個真正的 Android 應用程式中使用這個函式庫!開啟你現有的專案,或是在 Android Studio 中建立一個新的專案。
打開專案後,第一步是新增 JitPack 作為倉庫。導航到你的settings.gradle.kts
文件,並確保它包含 JitPack 倉庫:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
maven { url = uri("https://jitpack.io") }
mavenCentral()
}
}
接下來,您需要將依賴項新增至應用程式模組的build.gradle.kts
檔案 ( app/build.gradle.kts
)。您必須同時包含.aar
檔案和所有必要的執行時庫:
dependencies {
implementation(files("libs/myfirstandroidproject-debug.aar"))
implementation("com.github.swifdroid.runtime-libs:core:6.1.3")
implementation("com.github.swifdroid.runtime-libs:foundation:6.1.3")
implementation("com.github.swifdroid.runtime-libs:foundationessentials:6.1.3")
implementation("com.github.swifdroid.runtime-libs:i18n:6.1.3")
// the rest of dependencies
}
重要提示:您必須手動列出這些依賴項,因為 Android 無法自動從
.aar
檔案內部取得它們。
現在,取得你剛剛建立的庫檔案!你可以在 Swift Stream 專案的以下路徑找到.aar
檔:
Library/myfirstandroidproject/build/outputs/aar/myfirstandroidproject-debug.aar
複製此文件。然後,在您的 Android Studio 專案中,導航到應用程式模組的目錄(例如app/
),並在build.gradle.kts
檔案旁邊建立一個名為libs
的資料夾。將.aar
檔案貼到這個新的libs
資料夾中。
一切就緒!現在,在應用程式的某個地方——通常是在Application
類別或主 Activity 的onCreate
中,初始化 Swift 程式碼:
SwiftInterface.initialize(this)
同步您的 Gradle 專案、建置它並在裝置或模擬器上執行它。
關鍵時刻:開啟 LogCat 並篩選「SWIFT」。你應該會看到我們這精彩的訊息:
I [🐦🔥 SWIFT] 🚀 Hello World!
耶!你的 Swift 程式碼現在可以在 Android 上執行了。
當您對 Swift 程式碼進行更改時,這是您的快速更新週期:
在 Swift Stream 中,點擊Project -> Build
然後,點選Java Library Project -> Assemble
將新的.aar
檔案從outputs/aar
資料夾複製到 Android 專案的app/libs
資料夾中,取代舊檔案。
重建並執行您的 Android 應用程式!
就這樣!你現在是跨平台 Swift 開發者了。
現在到了最令人興奮的部分——程式碼!讓我們來談談如何在 Swift 和 Java/Kotlin 之間進行通訊。我們將繼續使用 Kotlin,因為它是當今 Android 開發的標準。
我們將在本文中介紹一些簡單但常見的場景,並在下次深入探討更複雜的場景。
⚠️至關重要:不要忘記在任何其他本機呼叫之前呼叫
SwiftInterface.initialize(this)
!
Int
讓我們從簡單的開始。在SwiftInterface.kt
中宣告一個方法:
external fun sendInt(number: Int)
在 Swift 端實現它:
#if os(Android)
@_cdecl("Java_to_dev_myandroidlib_myfirstandroidproject_SwiftInterface_sendInt")
public func sendInt(
envPointer: UnsafeMutablePointer<JNIEnv?>,
clazzRef: jobject,
number: jint
) {
let logger = Logger(label: "🐦🔥 SWIFT")
logger.info("#️⃣ sendInt: \(number)")
}
#endif
從您的應用程式呼叫它:
SwiftInterface.sendInt(123)
檢查 LogCat:
I [🐦🔥 SWIFT] #️⃣ sendInt: 123
太簡單了,對吧? :)
IntArray
聲明方法:
external fun sendIntArray(array: IntArray)
在 Swift 端,處理陣列:
#if os(Android)
@_cdecl("Java_to_dev_myandroidlib_myfirstandroidproject_SwiftInterface_sendIntArray")
public func sendIntArray(
envPointer: UnsafeMutablePointer<JNIEnv?>,
clazzRef: jobject,
arrayRef: jintArray
) {
// Create lightweight logger object
let logger = Logger(label: "🐦🔥 SWIFT")
// Access current environment
let localEnv = JEnv(envPointer)
// Defer block to clean up local references
defer {
// Release local ref to array object
localEnv.deleteLocalRef(arrayRef)
}
// Get array length
logger.info("🔢 sendIntArray 1")
let length = localEnv.getArrayLength(arrayRef)
logger.info("🔢 sendIntArray 2 length: \(length)")
// Get array elements
var swiftArray = [Int32](repeating: 0, count: Int(length))
localEnv.getIntArrayRegion(arrayRef, start: 0, length: length, buffer: &swiftArray)
// Now you can use `swiftArray` as a regular Swift array
logger.info("🔢 sendIntArray 3 swiftArray: \(swiftArray)")
}
#endif
從您的應用程式呼叫它:
SwiftInterface.sendIntArray(intArrayOf(7, 6, 5))
檢查 LogCat:
I [🐦🔥 SWIFT] 🔢 sendIntArray: 1
I [🐦🔥 SWIFT] 🔢 sendIntArray: 2 length: 3
I [🐦🔥 SWIFT] 🔢 sendIntArray: 3 swiftArray: [7, 6, 5]
String
聲明方法:
external fun sendString(string: String)
在 Swift 方面:
#if os(Android)
@_cdecl("Java_to_dev_myandroidlib_myfirstandroidproject_SwiftInterface_sendString")
public func sendString(envPointer: UnsafeMutablePointer<JNIEnv?>, clazzRef: jobject, strRef: jobject) {
// Create lightweight logger object
let logger = Logger(label: "🐦🔥 SWIFT")
// Access current environment
let localEnv = JEnv(envPointer)
// Defer block to clean up local references
defer {
// Release local ref to string object
localEnv.deleteLocalRef(strRef)
}
// Wrap JNI string reference into `JString` and get Swift string
logger.info("✍️ sendString 1")
guard let string = strRef.wrap().string() else {
logger.info("✍️ sendString 1.1 exit: unable to unwrap jstring")
return
}
// Now you can use `string` as a regular Swift string
logger.info("✍️ sendString 2: \(string)")
}
#endif
從您的應用程式呼叫它:
SwiftInterface.sendString("With love from Java")
檢查 LogCat:
I [🐦🔥 SWIFT] ✍️ sendString 1
I [🐦🔥 SWIFT] ✍️ sendString 2: With love from Java
Date
物件從 Kotlin 傳送到 Swift聲明方法:
external fun sendDate(date: Date)
在 Swift 方面:
#if os(Android)
@_cdecl("Java_to_dev_myandroidlib_myfirstandroidproject_SwiftInterface_sendDate")
public func sendDate(envPointer: UnsafeMutablePointer<JNIEnv?>, clazzRef: jobject, dateRef: jobject) {
// Create lightweight logger object
let logger = Logger(label: "🐦🔥 SWIFT")
// Access current environment
let localEnv = JEnv(envPointer)
// Defer block to clean up local references
defer {
// Release local ref to date object
localEnv.deleteLocalRef(dateRef)
}
// Wrap JNI date reference into `JObjectBox`
logger.info("📅 sendDate 1")
guard let box = dateRef.box(localEnv) else {
logger.info("📅 sendDate 1.1 exit: unable to box Date object")
return
}
// Initialize `JObject` from boxed global reference to the date
logger.info("📅 sendDate 2")
guard let dateObject = box.object() else {
logger.info("📅 sendDate 2.1 exit: unable to unwrap Date object")
return
}
// Call `getTime` method to get milliseconds since epoch
logger.info("📅 sendDate 3")
guard let milliseconds = dateObject.callLongMethod(name: "getTime") else {
logger.info("📅 sendDate 3.1 exit: getTime returned nil, maybe wrong method")
return
}
// Now you can use `milliseconds` as a regular Swift Int64 value
logger.info("📅 sendDate 4: \(milliseconds)")
}
#endif
從您的應用程式呼叫它:
SwiftInterface.sendDate(Date())
檢查 LogCat:
I [🐦🔥 SWIFT] 📅 sendDate 1
I [🐦🔥 SWIFT] 📅 sendDate 2
I [🐦🔥 SWIFT] 📅 sendDate 3
I [🐦🔥 SWIFT] 📅 sendDate 4: 1757533833096
聲明一個傳回值的方法:
external fun ping(): String
在 Swift 端,傳回一個字串:
#if os(Android)
@_cdecl("Java_to_dev_myandroidlib_myfirstandroidproject_SwiftInterface_ping")
public func ping(envPointer: UnsafeMutablePointer<JNIEnv?>, clazzRef: jobject) -> jobject? {
// Wrap Swift string into `JSString` and return its JNI reference
return "🏓 Pong from Swift!".wrap().reference()
}
#endif
從您的應用程式呼叫它:
Log.i("HELLO", "Pinging: ${SwiftInterface.ping()}")
檢查 LogCat:
I Pinging: 🏓 Pong from Swift!
聲明方法:
external fun fetchAsyncData(): String
您需要知道@_cdecl
屬性不適用於非同步運算子。這就是為什麼我們在這裡使用semaphore
來以非同步的方式執行 Swift 程式碼。這種方法完全沒問題,但僅適用於non-UI
程式碼。如果您在主線程上嘗試這樣做,您將面臨徹底的死鎖,所以不要這樣做。我將在下一篇文章中向您展示如何處理 UI 程式碼。
@_cdecl("Java_to_dev_myandroidlib_myfirstandroidproject_SwiftInterface_fetchAsyncData")
public func fetchAsyncData(env: UnsafeMutablePointer<JNIEnv>, obj: jobject) -> jstring? {
// Create semaphore to wait for async task
let semaphore = DispatchSemaphore(value: 0)
// Create result variable
var result: String? = nil
// Start async task
Task {
// Simulate async operation
try? await Task.sleep(nanoseconds: 5_000_000_000) // 5 seconds
// Set result
result = "Async data fetched successfully!"
// Release semaphore
semaphore.signal()
}
// Wait for async task to complete by blocking current thread
semaphore.wait()
// Check if result is available
guard let result = result else { return nil }
// Wrap Swift string into `JSString` and return its JNI reference
return result.wrap().reference()
}
#endif
從您的應用程式呼叫它(脫離主執行緒!):
CoroutineScope(Dispatchers.IO).launch {
Log.i("ASYNC", "Swift async call started")
try {
val result = SwiftInterface.fetchAsyncData()
Log.i("ASYNC", "Swift returned: $result")
} catch (e: Exception) {
// Handle error
}
Log.i("ASYNC", "Swift async call finished")
}
檢查 LogCat:
I Swift async call started
I Swift returned: Async data fetched successfully!
I Swift async call finished
為了在 Swift 中使用 Java 類,我們需要包裝器。讓我們為java/util/Date
建立一個:
public final class JDate: JObjectable, Sendable {
/// The JNI class name
public static let className: JClassName = "java/util/Date"
/// JNI global reference object wrapper, it contains class metadata as well.
public let object: JObject
/// Initializer for when you already have a `JObject` reference.
///
/// This is useful when you receive a `Date` object from Java code.
public init (_ object: JObject) {
self.object = object
}
/// Allocates a `Date` object and initializes it so that it represents the time
/// at which it was allocated, measured to the nearest millisecond.
public init? () {
#if os(Android)
guard
// Access current environment
let env = JEnv.current(),
// It finds the `java.util.Date` class and loads it directly or from the cache
let clazz = JClass.load(Self.className),
// Call to create a new instance of `java.util.Date` and get a global reference to it
let global = clazz.newObject(env)
else { return nil }
// Store the object to access it from methods
self.object = global
#else
// For non-Android platforms, return nil
return nil
#endif
}
/// Allocates a `Date` object and initializes it to represent the specified number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT.
///
/// - Parameter milliseconds: The number of milliseconds since January 1, 1970, 00:00:00 GMT.
public init? (_ milliseconds: Int64) {
#if os(Android)
guard
// Access current environment
let env = JEnv.current(),
// It finds the `java.util.Date` class and loads it directly or from the cache
let clazz = JClass.load(Self.className),
// Call to create a new instance of `java.util.Date`
// with `milliseconds` parameter and get a global reference to it
let global = clazz.newObject(env, args: milliseconds)
else { return nil }
// Store the object to access it from methods
self.object = global
#else
// For non-Android platforms, return nil
return nil
#endif
}
}
以上就是讓這個類正常運作所需的最低要求。它允許你從頭初始化一個java.util.Date
類,或包裝一個已經是正確類別的JObject
物件。
好了,骨架已經搭建好了。現在我們需要為它加入一些肌肉,讓我們寫下類別方法!
/// Returns the day of the week represented by this date.
public func day() -> Int32? {
// Convenience call to `java.util.Date.getDay()`
object.callIntMethod(name: "getDay")
}
你明白了!現在,繼續對getHours
、 getMinutes
、 getSeconds
和getTime
方法執行完全相同的操作。它們只是更多相同的模式!
現在來看看更有趣的東西:一個更複雜的方法,它採用另一個JDate
作為參數。
/// Tests if this date is before the specified date.
public func before(_ date: JDate) -> Bool {
// Convenience call to `java.util.Date.before(Date date)`
// which passes another `Date` object as a parameter
// and returns a boolean result
object.callBoolMethod(name: "before", args: date.object.signed(as: JDate.className)) ?? false
}
你猜對了,對after
方法也做同樣的操作。它實際上和before
完全相同。
最後,為了覆寫絕對最小值並使此類真正有用,讓我們加入一個超級方便的方法,將我們的 Java JDate
轉換為本機 Swift Date
物件。
/// Converts this java `Date` object to a Swift `Date`.
public func date() -> Date? {
// Get milliseconds since epoch using `getTime` method
guard let time = time() else { return nil }
// Convert milliseconds to seconds and create a Swift `Date` object
return Date(timeIntervalSince1970: TimeInterval(time) / 1000.0)
}
現在你已經對 Swift 如何透過 JNI 與 Java/Kotlin 協同工作有了基本的了解!希望你已經在你的 Android 專案中成功編譯並測試了它。
各位,今天就到這裡啦!
如需了解更多深入資訊和進階功能,請查看GitHub 上全面的 JNIKit README文件。它包含大量詳細資訊!
在 Swift Stream Discord 社群中找到我,加入並隨時提問!
點擊訂閱,就不會錯過下一篇文章!我們一定會討論如何透過 JitPack 分發庫,深入探討更複雜的 JNI 用例,以及…UI!
敬請關注!
原文出處:https://dev.to/swiftstream/the-swift-android-setup-i-always-wanted-285d