Native Libraries on Android Are Hard

I’ve been working on a personal project written in C. The library is relatively straightforward, depends only on 3 other C libraries. One of these libraries is a DSP library that needs C99’s complex values, which will be relevant to the story later.

I’ve got my library working natively. Great! Then I run it through emscripten to get it working in JS, and I do a cross-compile to get it working in iOS. Aside from some small cross-compile pains, these both go relatively easily. Both provide fairly typical platforms to run a C library on.

The next platform I tried was Android. You might think this isn’t possible, since Android applications are typically written in Java. In fact, you can use Java’s JNI to run native code. But as I found out, this is actually fairly tricky to accomplish, especially if you want to support older Android versions. Since I’m distributing a library and not an app, I wanted compatibility with old versions. Rewriting in Java wasn’t really an option since my library and its dependencies are fairly large and it seems like keeping the code in sync would be more work than cross-compiling and wrapping.

As I found out, the Android does not supply C99’s complex functions in API versions < 21 (Lollipop), which came out about 2 years ago. There’s enough fragmentation that I want to support users running older versions, so I had to take the ones provided by musl and ship them with my library. This wasn’t especially complicated, but the fact that the functions were missing was confusing at first. It was also disappointing to see that parts of C99 were not supported, considering it is now more than 15 years old.

I also encountered some issues with the toolchain itself. In order to build native code you need the Android NDK. From what I can tell, pieces of the NDK have moved around quite a bit in its lifetime. Since my project and its dependencies build with Cmake, I looked around for a Cmake toolchain file that would work here. It turns out that the first one you find when you search around is out of date and doesn’t work because things have moved around in the NDK. After further digging, I was fortunately able to find someone’s fork that works in newer versions, but it’s apparent that this toolchain needs work every time a new version of the NDK comes out. This is a maintenance nightmare. Thankfully, it looks like the NDK has started shipping its own Cmake toolchain file, which will hopefully alleviate this issue.

I ran into no shortage of other minor snags. It seems that some combinations of {CPU Architecture, Android version} have slightly different sets of C standard library headers that they provide. You will get your project working in one combination and test it in another, only to realize things have moved around under you. In some combinations you’ll find that you can use pthread_condattr_setclock with CLOCK_MONOTONIC, in others you need pthread_cond_timedwait_relative_np, Darwin-style. Each one isn’t overly difficult to fix but is another papercut. Doing these fixes makes me wonder if my native library that works today will work in the next version.

After I got my library building, I encountered more problems while debugging it. You can attach gdb to a native library in Android, but it’s fairly tricky to get it working right. In my case, my native library is wrapped by a Java library with some suitable Java code to make it more idiomatic. I have a demo app that I use that consumes this library. This sort of configuration might be typical for library wrappers like this, and the supplied ndk-gdb supports it. But, I always find it tricky to get it to attach. There’s some special set of flags you need to make this situation work, and even then it doesn’t seem to reliably find the app in the emulator for me.

I couldn’t get it to attach, so I figured I’d try printfs, the reliable old standby. However, in native libraries, you can’t immediately read stdout output. Supposedly, doing log.redirect-stdio true from adb shell makes stdout appear in logcat monitor, but it just puts my emulator in a bootloop.

Ultimately I had to modify this particular library to instead include <android/log.h> and link in the Android logging library in order to see the values I wanted. This isn’t a huge deal but it is frustrating when you just want to fix the bug. I’m not sure if I’m just missing something but it would be really handy to have an easier option for native debugging on Android, especially if it were easy to use from Android studio like Java debugging is.

All of these pain points made me feel like putting native libraries on Android is much harder than on other current platforms. I’m especially worried that something will change in the future and my library will stop working. To be fair, any time I have contacted Android’s IRC for help, I’ve been set on the right track. Still, I’m left with the impression that this is an option few choose, and probably for good reason.

Author’s Note: I have re-published this story from a previous article. The prior writing was written out of frustration. I have edited it to be more objective.