Using JNI in Swift to put an app into the Android Play Store

Geordie J
8 min readJul 14, 2016

This is part three of what I’d planned to be a single-part series about putting what may have been the first Android app into the Google Play Store with a sizable Swift component. This is the most technical part so far and assumes some knowledge about Swift and Java programming.

Discover the reason why we went down this path in part one. For the steps we took to prepare our codebase and to get Swift code working in an Android .apk, see part two.

So our Swift code worked in an .apk app bundle on Android devices. But the communication between the JavaScript code of our web app (running in a Crosswalk web view) and our Swift code was limited at best. No one on our four-person dev team had had much experience with Java, let alone the JNI (Java Native Interface), so there was another learning curve ahead of us…

Ignoring anything fancy like cross-language array or string manipulations, the first step was to get Java to call Swift at all and vice-versa.

Calling from Java into Swift

Since Swift compiles down to platform-native machine code that exists on the real Android ‘machine’ and not Java’s virtual machine, calling from Java into Swift is the easier case here. The following example takes some ints from Java, adds them together and returns the result back to Java directly.

Java Code:

package com.flowkey.plugins; // also sets the namespace for JNI// We use Cordova, but this works from any class (or activity)
public class OurSwiftPlugin extends CordovaPlugin {
// Explicitly load all the required frameworks in order.
// Android 5.0+ finds most dependencies by itself and
// doesn't need as much hand-holding. We support 4.4+:
static {
System.loadLibrary("c++_shared");
System.loadLibrary("scudata"); // icu libs...
System.loadLibrary("scuuc"); // see part 2 of this series
System.loadLibrary("scui18n"); // -------------------------
System.loadLibrary("swiftCore");
System.loadLibrary("swiftGlibc");
System.loadLibrary("swiftJNI");
// Contains the Swift code we want to call:
System.loadLibrary("ourSwiftFramework");
}
private native int addInts(int x, int y); @Override
public void pluginInitialize() {
System.out.println(addInts(12, 22)); // prints 34
}
}

Swift code (compiled into libourSwiftFramework.so):

@_silgen_name("Java_com_flowkey_plugins_OurSwiftPlugin_addInts")
public func callYourSwiftFunctionWhateverYouWantInternally(env: UnsafeMutablePointer<JNIEnv>, jobj: jobject, x: jint, y: jint) -> jint {
return x + y // jint is a typealias of Int32, so this just works
}

This contrived and overly basic example should give you an idea of what is involved in calling Swift from Java. Some things to note:

  1. The Swift framework is actually called libourSwiftFramework.so, but it gets loaded via System.loadLibrary(“ourSwiftFramework”). This isn’t specific to Swift (this is the standard way to load a dynamic library from Java) but it’s important to know.
  2. We’re using an all but undocumented Swift feature ‘@_silgen_name’ to work around Swift’s name mangling and produce a symbol with a specific name that can be called directly by C or, in this case, Java.
  3. The exported ‘@_silgen_name’ we’re providing from Swift must follow the JNI’s strict rules, namely ‘Java_[package_name]_[class_name]_[exported_function_name]’. I’ve highlighted the connections between these parts in the Java code.
  4. The Swift function to be called must be top-level (not nested within a class or other structure) and public, otherwise it won’t appear in the global symbol table and JNI won’t find it!

I should mention here that a function this trivial is absolutely a terrible use case for JNI. In the same way that accessing the DOM from JavaScript has a very high overhead, calling from Java into native code and vice-versa is not something you want to be doing over and over (like in a loop).

In general, stick to one side of the Java-Native bridge for as long as you can and put meaningful boundaries between where Java ends and native begins: it’s better to send a bunch of data at once, do some significant amount of processing on it and then send it back across the bridge, rather than constantly switching back and forth.

In our case, we access the microphone buffers in real-time using OpenSL ES from C, send these buffers to Swift about 50 times per second (remembering that native-native is basically free), then if (and only if) we find something interesting in the microphone data (i.e. the user has played the right notes), we send a callback back across the bridge into Java. Which leads us to the next challenge.

Calling from Swift into Java

Now we’re into the fun stuff. Not that I’ve ever done it before myself, but I gather that writing JNI code in C (being a supported language for Android Native Development Kit or NDK) is painful enough to begin with. But at least C has some form of code completion and half-decent debugging support on Android. Swift doesn’t really have either of those things, although the code completion on Linux platforms is generally improving, and some of the NDK debugging tools also work with Swift out of the box to some degree.

To write JNI in Swift though, stabbing in the dark and imitating JNI examples written in C(++), hoping the code would compile in Swift, is clearly not enough. So I moved the development of the parts of our code interacting with the JNI from our Linux development server back into Xcode, which is pretty easy to do:

  1. Start a new Xcode project, as blank as you can get it. I used the macOS command-line tool template. Choose Swift as your project’s language.
  2. Add a (dummy) Objective C file to the project and say yes to adding a Bridging Header.
  3. Drag the jni.h header from your Android NDK installation at ndk_root/platforms/android-24/arch-arm/usr/include/jni.h into your Xcode project, choosing to copy the file if needed. Download the NDK here.
  4. Remove the numerous references to __NDK_FPABI__ within the Xcode copy of jni.h (Xcode won’t understand the Android-specific macro and will fail to load the header) and import it into the Bridging Header created for you in step 2 — just add the line #import “jni.h”

And now you have an Xcode project with code completion for the Android JNI!

Code completion is one of those things you don’t miss until its gone.

From here it was relatively easy to put the required pieces together to call from Swift into Java. I say relatively easy, because this is still some pretty heavy stuff — the JVM can be a difficult beast to deal with from the outside, and Swift’s memory-safety guarantees make it explicitly difficult to work with unsafe types like the raw pointers (and pointers to pointers) rife throughout the JNI’s API. But we’d gotten so far already, it only made sense to finish the job. Plus, things were just starting to get fun!

Before we get too carried away though, let’s use our new knowledge and JNI-enabled Xcode project to call a Java method from Swift.

The following simple example will give you an idea of how Swift and Java can interact. The Java code pushes some numbers into an Array in Swift. When the Swift Array has five elements in it, it will run a callback back into Java with the result.

Java Code:

package com.flowkey.plugins; // see first example, abovepublic class OurSwiftPlugin extends CordovaPlugin {
static {
// ... and the other dependencies here (see example above)
System.loadLibrary("ourSwiftFramework");
}
private native void pushIntOntoSwiftArray(int n); @Override
public void pluginInitialize() {
pushIntOntoSwiftArray(2);
pushIntOntoSwiftArray(5);
pushIntOntoSwiftArray(7);
pushIntOntoSwiftArray(12);
pushIntOntoSwiftArray(8);
}
// Swift will call this when its array contains five elements
private void onArrayFull(int arraySum) {
System.out.println(arraySum); // prints 34
}
}

Swift code (compiled into libourSwiftFramework.so):

var intArray: [jint] = []@_silgen_name("Java_com_flowkey_plugins_OurSwiftPlugin_pushIntOntoSwiftArray")
public func pushInt(env: UnsafeMutablePointer<JNIEnv>, jobj: jobject, n: jint) {

intArray.append(n)

if intArray.count >= 5 {
let callbackResult = intArray.reduce(jint(0), combine: +)
callOnArrayFull(env, jobj: jobj, value: callbackResult)
intArray.removeAll()
}
}
private func callOnArrayFull(env: UnsafeMutablePointer<JNIEnv>, jobj: jobject, value: jint) {
let jni = env.memory.memory // env is a pointer to a pointer!

let methodName = "onArrayFull"
let methodSignature = "(I)V" // one Int argument, returns Void
let javaClass = jni.GetObjectClass(env, jobj)
let methodID = jni.GetMethodID(env, javaClass, methodName, methodSignature)

let valueAsJValue = jvalue(i: value)
var methodArgs: [jvalue] = [valueAsJValue]
jni.CallVoidMethodA(env, jobj, methodID, &methodArgs)
}

Are you still with me? Now, my goal isn’t to teach you how to use the JNI; there are much better resources for that on the web. What I’d like to do though is go over the Swift-specific parts, namely when intArray.count >= 5 and we run callOnArrayFull in Swift:

  1. The env argument passed in by Java is a pointer to the JNIEnv type, defined in jni.h. JNIEnv is itself a pointer to the JNI-internal JNINativeInterface type, which encapsulates almost all JNI functionality. To access it, we need to tap into env.memory.memory (env.pointee.pointee in Swift 3+) — this sure gets tiresome and ugly if you’re doing it a lot.
  2. We have to pass env as the first argument into any call to a JNINativeInterface method. This is the legacy of C.
  3. GetObjectClass and GetMethodID work just as they do from C. Note, it’s critical to provide the correct method signature of the Java method you’re trying to call (see Oracle’s JNI Docs for more info).
  4. When calling a Java method from native, the calling arguments must all be wrapped in jvalue structs. Autocomplete is invaluable here to figure out what all the cryptic abbreviations mean when creating a jvalue instance.
  5. The most Swift-compatible way of calling a Java method is to use Call(ReturnType)MethodA. This method allows you to pass an Array of jvalue arguments to Java (the array can be empty or contain just one jvalue argument). Collecting these arguments in a mutable Array to be referenced with the & character avoids dealing with any icky UnsafeMutablePointers as much a possible.

As you can see, even calling a simple Java method from Swift is quite involved and potentially error-prone, especially when you compare it to calling C<->Swift or even Java->Swift.

To deal with that, I started abstracting away much of this complexity in the WIP SwiftJNI module I started on the SwiftAndroid GitHub. SwiftJNI imports and exposes jni.h (internally as CJNI), but it also implements a Swift class called JNI that more closely resembles the slightly nicer C++ API, removing the need for dancing around with env.memory.memory. It also internally checks for null pointers at key points and returns optionals where appropriate for more Swift-like coding.

The module also contains the beginnings of an even Swiftier subclass of JNI called SwiftJNI, which is incomplete and very experimental for now. There are also functions for converting between Swift Arrays and Java arrays in both directions, and, important for the use case above in particular, the JavaCallback struct. These abstractions also let you call JNI methods from outside the context (i.e. function call) they were created in, and go some way to deal with concerns about threading.

I think this article is long enough for now, and I haven’t had time to work on SwiftJNI lately(it meets our needs and then some for now) but if there is enough interest I will consider writing more about the module in the future. It is also worth noting that, due to recent advancements in Swift (namely CF_SWIFT_NAME) the manual wrapping I did may even be unnecessary now.

--

--