We recently announced support for crash reporting for bugs in native code built with the Android NDK. Creating this new product came with a set of challenges, one of which was determining how best to integrate this product into your amazing mobile apps.
The goal was to design a library for native code that was as painless to integrate as our SDK library; a single line of code. In order to build this clean user interface to our library, we set out to package all of the code into a single .JAR file. Unfortunately, the Android system makes this a little more difficult than it probably should be.
Including a .JAR library in your Android project is really as simple as pasting the file into the libs directory in your project.* On build time, the contents of the .JAR are combined with that of your project into the APK primarily by mapping the contents of the directories together. For example, the files in the /assets directory of the .JAR will appear to the build system as being in the /assets directory of the application.
When you build native code for an Android application with the NDK build tools, it places the compiled binary object files in the /libs/<eabi>/ directory. At build time, these files are mapped into the /lib directory in the APK. Upon installation, the proper .so for the hardware is extracted into /data/data/<app dir>/lib/ directory on the device. To load this library in order to call native methods from Java, one simply calls:
This searches the libs directory for the library <lib-name>.** This system is straightforward, and for the normal use case, quite seamless. The native libs get put into the lib directory in the APK and are automatically extracted.
In order to have the native library in the .JAR library included and extracted the same way, the obvious choice would be to place the native libraries into the /lib directory in the .JAR so that they will be built into the lib directory in the APK and end up in the application’s lib directory on the device. All this works fine until the actual install when you are prompted by the following error in eclipse:
This error occurs because the device detected native code in the .JAR that was to be placed into the application’s lib folder. This error is extremely frustrating because the fact is that my .JAR contains native libraries that will actually run on the device. According to a post in the Android NDK Google Group by the Android SDK Tech Lead,
“We added that because we’ve seen people include jar that contains native code compiled for windows/mac/linux. We should at least check that it looks like a proper Android native library and just output a warning. I’m afraid there’s no work around, you’ll need to manually add the library to the project that includes the jar file.”
He proposes that instead of packaging the native library in our .JAR, we include it separately and have developers, in addition to copying the .JAR file into their project, also copy the native code into the libs directory of their project. While that would be a reasonable solution, we felt that it unnecessarily complicated the inclusion process and added room for error.
Instead, we devised a strategy to avoid this check by packaging the native library as an asset of the .JAR file. Then we detect whether the library exists on disk. If it doesn’t, we grab the library from assets (Context.getAssets().open()), and extract it to the filesystem. That way, we can load the library easily using the more generic System.load() function that takes a complete path rather than just a library name. This way we are able to seemlessly package our native code into the .JAR and avoid the fatal error shown above.
I’m not sure that this is the cleanest solution from our perspective, but it has proven to be a satisfactory solution for our needs. It uses no extra disk space by including the native library directly. While there is an extraction at runtime rather than at install time, it takes place just once, the file being extracted is small (<100kb), and all of this happens in an auxiliary thread. Most importantly, packaging our library this way creates a simple and seamless experience for the developer.
*<property name=”jar.libs.dir” value=”libs” /> is set in /tools/ant/main_rules.xml by default but you can change the jar.libs.dir in your .properties files for ant.
**When you build it with ndk-build, lib- will be appended to the name and when you load the library you don’t need the lib-
Author: Andrew Werner, Crittercism Intern Extraordinaire