Getting Started
doc
やっていること
元がSwiftコードのバイナリをAndroid OS で実行するデモとなっていて 以下をやっている。
- Swift コードをクロスコンパイルして Android向けのELFを吐き出し
- 吐き出したELFと Android NDK のC++ の共有ライブラリをemulatorにpush
- emulator上でshellから ELFを実行
ざっくり準備 と 実行
進め方の詳細は記事をみたほうがよさそう。ここではざっくり書くと
準備
- SwiftSDK For Android の準備(Swiftのホストツールチェーンもここで必要)
- Android NDK
- SwiftSDK For AndroidとAndroid NDK のリンク
実行
// swiftlyで作ったパッケージをAndroid(aarch64)向けにクロスコンパイル
swiftly run swift build --swift-sdk aarch64-unknown-linux-android28 --static-swift-stdlib
// emulatorに ELFと共有ライブラリ送信して実行
adb push .build/aarch64-unknown-linux-android28/debug/hello /data/local/tmp
adb push $ANDROID_NDK_HOME/toolchains/llvm/prebuilt/*/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so /data/local/tmp/
adb shell /data/local/tmp/hello
// hello
要するにSwift をクロスコンパイルして、AndroidOS向けのバイナリ生成する仕組み。
swift-android-examples
doc
大きく二つ。
- swift-java を使った,SwiftをAndroidで動かす仕組み
- JNIを直に使った連携
swift-java による連携
サンプル
出典: https://github.com/swiftlang/swift-android-examples/tree/main/hello-swift-java
自作のシンプルなSwiftライブラリを Androidから呼んでいる。
どういった連携手法なのか
- Swiftで書いた関数や型をAndroid側(Java/Kotlin)に公開するためのビルドツール群とGradleプラグインのセット
- Swiftのシンボルから必要なJNIバインディングとJavaラッパークラスの自動生成してくれる
import Crypto
#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif
public func hash(_ input: String) -> String {
SHA256.hash(data: Data(input.utf8)).description
}
↑ から ↓。JNIのネイティブメソッドがjavaラッパーとして生成されていて,これをKotlinから参照している。
public final class SwiftHashing {
static final String LIB_NAME = "SwiftHashing";
static {
System.loadLibrary(LIB_NAME);
}
// ==== --------------------------------------------------
// hash
/**
* Downcall to Swift:
* {@snippet lang=swift :
* public func hash(_ input: String) -> String
* }
*/
public static java.lang.String hash(java.lang.String input) {
return SwiftHashing.$hash(input);
} // printJavaBindingWrapperMethod(_:_:importedFunc:signaturesOnly:) @ JExtractSwiftLib/JNISwift2JavaGenerator+JavaBindingsPrinting.swift:480
private static native java.lang.String $hash(java.lang.String input);
}
かなりの部分を自作のgradle タスクで賄っているのが面白い。この辺りに実験的さを感じる
(細かなステータスやアナウンスは把握してないので感想)
preBuildにひっかけ。
preBuild.dependsOn(copyJniLibs)
buildSwiftAllというカスタミタスクに繋いで
// copyJniLibs
def copyJniLibs = tasks.register("copyJniLibs", Copy) {
dependsOn(buildSwiftAll)
// ここ以降はビルド済みsoファイル等を必要な場所に移動してたりする。
各Android ABI (arm64-v8a, armeabi-v7a, x86_64) 向けにSwiftコードをビルド。
// buildSwiftAllから path等設定して以下
executable(getSwiftlyPath())
args("run", "swift", "build", "+${swiftVersion}", "--swift-sdk", info.triple)
このswift build の中で JExtractSwiftPlugin というプラグインが動いて JNI連携のjavaが吐き出される様子。
残りは細かいですが、吐き出したsoファイルを特定のフォルダにまとめてJNI連携しやすいようにしているっぽい。
シンプルなJNI連携
doc
swift
ネイティブ連携ということで、Swift側にも見慣れない低レベルなコードが多い。
import Android
@_cdecl("Java_org_example_helloswift_MainActivity_stringFromSwift")
public func MainActivity_stringFromSwift(env: UnsafeMutablePointer<JNIEnv?>, clazz: jclass) -> jstring {
let hello = ["Hello", "from", "Swift", "❤️"].joined(separator: " ")
return hello.withCString { ptr in
env.pointee!.pointee.NewStringUTF(env, ptr)!
}
}
連携するための記法ということでざっくり
- JNI特有の型(jstring, jclass, JNIEnv)
- C言語互換(@_cdecl, withCStrin)
ビルド
swift-javaの例と同じく, gradle scriptで頑張っている
この辺り( swift-android.gradle.kts)
val swiftBuildTask = createSwiftBuildTask(buildTypeName, arch, isDebug)
val copyTask = createCopySwiftLibrariesTask(buildTypeName, arch, isDebug, swiftBuildTask)
...
tasks.findByName("merge${capitalizedVariantName}JniLibFolders")?.let { task ->
task.dependsOn(copyTask)
- Swiftビルドタスクでsoファイル生成
libhello-swift-raw-jni-library.so - コピータスクでJNILibsディレクトリに移動
- AndroidのJNIマージタスクに依存関係を追加(パイプラインに統合)
という感じ。おそらくこのpluginを公開しようと頑張っているのではないかな?
Android側
.soを初期化時にロードして、JNI関数呼び出し
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.sample_text).text = stringFromSwift()
}
/**
* A native method that is implemented by the 'helloswift' native library,
* which is packaged with this application.
*/
external fun stringFromSwift(): String
companion object {
// Used to load the 'helloswift' library on application startup.
init {
System.loadLibrary("helloswift")
}
}
}
終わり
- swift-javaの連携で、swiftコードも馴染みある感じになる。こちらのgradle pluginが充実すると Androidでも使いやすそうではあった(感想)
- SwiftUI連携は今のところ公式ではなさそうだが、やろうとしているところはある様子