Android Gradle, add native .so dependencies

Background

A few months ago, I wrote a Key-Value database for Android called SnappyDB based on Google’s LevelDB.

Since it uses native C++ code, the generated bundle contains (.so) binaries libs, along with Jars.

Distribution via Maven repo is not a problem (As soon as you pass the hassle of the publishing process:), maven-android-plugin can help you include the shared libs.
Maven dependencies convention allows you to specify the type of ABI (different CPU architectures) & the format of the library (obviously .so in our case) you want to resolve using classifier:

Ex: resolving arm shared lib for SnappyDB

<dependency>
  <groupId>com.snappydb</groupId>
  <artifactId>snappydb-native</artifactId>
  <version>0.2.0</version>
  <classifier>armeabi</classifier>
  <type>so</type>
</dependency>

This approach works fine if you use Maven & Eclipse ADT as a build system, until you succumbed to Gradle’s siren call!

Android Studio & Gradle

Android Gradle plugin, handle gracefully all Jars dependencies by using maven repos (among others …)

ex: declaring a dependency inside build.gradle

dependencies {
     classpath 'commons-io:commons-io:2.4'
}

but it struggles when it comes to native dependencies, as compared with Maven, you can’t¹ write something like this:

dependencies {
       classpath 'com.snappydb:snappydb-native:2.+:arm-v7a'
}

This is due to the fact that the NDK support is still a work in progress with Android plugin. (as with Android Studio)

¹ actually, technically speaking you can, but Gradle will just ignore these native file since it doesn’t know what to do with them.

jniLibs to the rescue!

In their 0.7.2 release of the Android plugin, Google introduced a new folder ‘jniLibs‘ to the source sets. This means, that you can now add your prebuilt .so files to
this folder, and Android plugin will take care of packaging those native libraries inside your APK.

.
├── AndroidManifest.xml
└── jniLibs
    ├── armeabi
    │   └── libsnappydb-native.so
    ├── armeabi-v7a
    │   └── libsnappydb-native.so
    ├── mips
    │   └── libsnappydb-native.so
    └── x86
        └── libsnappydb-native.so

This feature is great, but the developer still need to download & copy his prebuilt .so files manually, which isn’t great especially if you use a Continuous Integration  server like Jenkins or Travis for instance.

a lot of hacks & workarounds emerged to try to sort this out, but a lot of them are really verbose & require the user to download manually his native dependencies.

So, you get the picture. There has to be a better way.

Meet android-native-dependencies

android-native-dependencies is a Gradle plugin I wrote to automate the process of resolving & downloading & copying the native dependencies into jniLibs folder, so Android plugin can include them automatically in your APK build.

the plugin uses the same repository declared to resolve regular dependencies (jar)
here is an example:

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:0.10.+'
    classpath 'com.nabilhachicha:android-native-dependencies:0.1'
  }
}

apply plugin: 'android'
apply plugin: 'android-native-dependencies'

native_dependencies {
    artifact 'com.snappydb:snappydb-native:0.2+:armeabi'
    artifact 'com.snappydb:snappydb-native:0.2+:x86'
}

dependencies {
    //regular Jar dependencies ...
}

Convention

The artifact DSL follows the naming convention for Maven artifacts. thus, you can use one of the following syntax:

  • abbreviated group:name:version[:classifier]
//adding x86 classifier will resolve only intel's (.so) lib
native_dependencies {
    artifact 'com.snappydb:snappydb-native:0.2+:x86'
}

//omit the classifier will resolve all supported architectures
native_dependencies {
    artifact 'com.snappydb:snappydb-native:0.2+'
}
  • map-style notation
//adding x86 classifier will resolve only intel's (.so) lib
native_dependencies {
    artifact group: 'com.snappydb', name: 'snappydb-native', version: '0.2+', classifier: 'x86'
}

//omit the classifier will resolve all supported architectures
native_dependencies {
    artifact group: 'com.snappydb', name: 'snappydb-native', version: '0.2+'
}

In both notations, classifier is optional. this means that when omitted, the plugin try to resolve the artifacts for all architectures: armeabi, armeabi-v7a, x86 and mips.

Conclusion

Until we get a full support for NDK in Android Gradle plugin, using android-native-dependencies can help you build your CI & automate repetitive task with native dependencies. Please try it and send your feedback to @nabilhachicha.

Another great Gradle plugin I recommend is the android-sdk-manager by (Jake Wharton) who helps downloads and manages your Android SDK.

 

Advertisements

About nhachicha

Android Developer, share experience about Java, System and Android
This entry was posted in Android and tagged , , , , , , , , , , . Bookmark the permalink.

5 Responses to Android Gradle, add native .so dependencies

  1. Daniel says:

    Couldn’t one package everything into an aar (Android Library File) instead?
    http://tools.android.com/tech-docs/new-build-system/aar-format
    There are jni folders for every abi and libs for your optional Java stubs.

    • nhachicha says:

      AAR is indeed the target, but it’s still a recent format, and not all deps support it yet.
      this plugin is more intended for the legacy projects not already available as AAR.

  2. justagenericindividual says:

    I just started using this library and I think I have found a bug.

    I’m pulling in a native library that starts with the name lib – e.g. ‘libnativelibrary_so.so’
    When it is pulled down I think your plugin is prepending the name ‘lib’; so that it becomes ‘liblibnativelibrary_so.so’ and thus I get an UnsatisifiedLinkError due to the incorrect name. If you have the time can you confirm that the bug is correct and offer any workaround?

    • nhachicha says:

      Hi,
      please check this issue

      you can use the flag addLibPrefixToArtifact=false to disable the convention prefix
      artifact ('com.snappydb:snappydb-native:0.2.+:armeabi') {
      addLibPrefixToArtifact=false
      }

      Cheers,

      • justagenericindividual says:

        That’s perfect. It’s working as expected. Thanks for the quick response and the help.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s