The Android SDK is implemented in Java, and so to meaningfully interact with the platform from Swift, some mechanism for communicating with the Java world is required. The standard for this communication is the Java Native Interface (JNI), which has two halves:
- Invocation API: how C/C++/Swift code loads and invokes Java code
- Native Method Interface: how Java loads and invokes native C/C++/Swift code
Over the years, dozens of Swift-Java bridging frameworks have sprouted up, such as:
- GitHub - SwiftJava/SwiftJava: Swift to Java Bridge (@johnno1962)
- GitHub - SwiftAndroid/swift-jni: Wrap JNI functions (WIP) (@Geordie_J)
- GitHub - jectivex/Kanji: Swift/Java interface and bindings for macOS and Linux (@marcprux)
- GitHub - readdle/swift-java: Java swift module (@andriydruk)
- GitHub - skiptools/swift-jni: Swift Java Native Interface Support (@marcprux, @aabewhite)
- GitHub - swiftlang/swift-java (@Douglas_Gregor, @ktoso)
This last project is part of the Java Interoperability effort and has the potential to become the standard way for Swift to interoperate with Java in desktop and server environments, but because it requires JDK 22+ for its Foreign Function & Memory API, it isn't suitable for Android, which is stuck on an ancient version of Java 8 and will likely never adopt the newer FFI standard (see below).
In addition, different bridging frameworks will satisfy different needs: swiftlang/swift-java
requires running an external tool to create bridging code between Swift and Java, whereas Skip's bridging automatically generates a bridge between Swift and Kotlin with its build plugin, and handles bridging Kotlin-exclusive concepts like tuples, lambdas, and coroutines (async), as well integration with the Jetpack Compose runtime.
That all mostly affects the bridging generation and the Native Method Interface half of the equation, but the Invocation API is another story: the standard jni.h is ubiquitous and largely unchanged since Java 2, and everyone has their own bespoke Swift wrapper around the various C functions (FindClass
, CallObjectMethod
, NewString
, NewGlobalRef
/DeleteGlobalRef
, etc.), all of which do mostly the same stuff. For example, contrast skiptools/swift-jni/Sources/SwiftJNI/SwiftJNI.swift and readdle/java_swift/Sources/JNICore.swift and swift-java/Sources/JavaKit/JavaObject+MethodCalls.swift and it's mostly the same kind of jockeying of wrappers, references, and adding Swift niceties atop the invocation API.
This parochial approach to JNI plumbing reduces the amount of sharing that can be done between libraries that are built on top of them. For example, if someone builds some neat library on top of the Skip JNI implementation (like, say, a mechanism to globally access an Android Context instance like in swift-android-native), it cannot be used by swiftlang/swift-java
or readdle/swift-java
because of different underlying conventions and types.
So I would propose the addition of a new swiftlang/swift-jni
project that satisfies the needs of all these projects. It would be a simple and un-opinionated wrapper around the standard JNI C interface that could work equally well in all environments (server, desktop, mobile, embedded, etc.) and enable the sharing of projects that are built on top of them. It would ideally be something that all the above mentioned projects could migrate to using without too much refactoring. In addition, it could be included by default with the Android SDK so developers could just import(SwiftJNI)
without needing to add any additional dependencies.
I can sketch up a more detailed proposed if there is sufficient interest and agreement that this would be a useful project.