Android AAR package for native library-ThrowExceptions

Exception or error:

I’m looking for a way to package a native library into an AAR package, so it would be possible to use it via dependencies declaration in gradle script.

By native library I mean set of .cpp files or compiled static library and a set of header files. So, I mean that the app itself will call the library from native code, not from Java. In other words, the library needed to compile app’s native code. So that it will be possible to easily manage dependencies of native code.

Is it even possible?

So far I could only find a lot of questions/examples of how to make an AAR of JNI native library with .so file and its Java interface, so the lib just a Java lib with native implementation, but this is not what I need.

How to solve:

Found the following hacky solution to the problem:

Use Android Experimental Gradle plugin version 0.9.1.
The idea is to put library headers and static libraries into .aar.
The headers are put to ndkLibs/include and static libs to ndkLibs/<arch> for each architecture. Then, in the app, or another lib which depends on this packed lib we just extract ndkLibs directory from AAR to the build directory in the project. See the example gradle file below.

The build.gradle file for the library with comments:

apply plugin: "com.android.model.library"

model {
    android {
        compileSdkVersion = 25
        buildToolsVersion = '25.0.2'

        defaultConfig {
            minSdkVersion.apiLevel = 9
            targetSdkVersion.apiLevel = 9
            versionCode = 1
            versionName = '1.0'
        }
        ndk {
            platformVersion = 21
            moduleName = "mylib"
            toolchain = 'clang'
            abiFilters.addAll(['armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64']) //this is default
            ldLibs.addAll(['android', 'log'])
            stl = 'c++_static'
            cppFlags.add("-std=c++11")
            cppFlags.add("-fexceptions")
            cppFlags.add("-frtti")

            //Add include path to be able to find headers from other AAR libraries
            cppFlags.add("-I" + projectDir.getAbsolutePath() + "/build/ndkLibs/include")
        }

        //For each ABI add link-time library search path to be able to link against other AAR libraries
        abis {
            create("armeabi") {
                ldFlags.add("-L" + projectDir.getAbsolutePath() + "/build/ndkLibs/armeabi")
            }
            create("armeabi-v7a") {
                ldFlags.add("-L" + projectDir.getAbsolutePath() + "/build/ndkLibs/armeabi-v7a")
            }
            create("arm64-v8a") {
                ldFlags.add("-L" + projectDir.getAbsolutePath() + "/build/ndkLibs/arm64-v8a")
            }
            create("x86") {
                ldFlags.add("-L" + projectDir.getAbsolutePath() + "/build/ndkLibs/x86")
            }
            create("x86_64") {
                ldFlags.add("-L" + projectDir.getAbsolutePath() + "/build/ndkLibs/x86_64")
            }
            create("mips") {
                ldFlags.add("-L" + projectDir.getAbsolutePath() + "/build/ndkLibs/mips")
            }
            create("mips64") {
                ldFlags.add("-L" + projectDir.getAbsolutePath() + "/build/ndkLibs/mips64")
            }
        }
    }

    //Configure this library source files
    android.sources {
        main {
            jni {
                //This does not affect AAR packaging
                exportedHeaders {
                    srcDir "../../src/"
                }

                //This tells which source files to compile
                source {
                    srcDirs '../../src'
                }
            }
        }
    }
}

//Custom Maven repository URLs to download AAR files from
repositories {
    maven {
        url 'https://dl.bintray.com/igagis/android/'
    }
}

//Our custom AAR dependencies, those in turn are also packed to AAR using the same approach
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'io.github.igagis:libutki:+'
    compile 'io.github.igagis:libsvgdom:+'
    compile 'org.cairographics:cairo:+'
}


//===================================
//=== Extract NDK files from AARs ===
//This is to automatically extract ndkLibs directory from AAR to build directory before compiling any sources
task extractNDKLibs {
    doLast {
        configurations.compile.each {
            def file = it.absoluteFile
            copy {
                from zipTree(file)
                into "build/"
                include "ndkLibs/**/*"
            }
        }
    }
}
build.dependsOn('extractNDKLibs')
tasks.whenTaskAdded { task ->
    if (task.name.startsWith('compile')) {
        task.dependsOn('extractNDKLibs')
    }
}



//=================================
//=== pack library files to aar ===
//This stuff re-packs the release AAR file adding headers and static libs to there, but removing all shared (.so) libs, as we don't need them. The resulting AAR is put to the project root directory and can be uploaded to Maven along with POM file (you need to write one by hand).

def aarName = name

task copyNdkLibsToAAR(type: Zip) {
    baseName = aarName
    version = "\$(version)"
    extension = 'aar.in'
    destinationDir = file('..') //put resulting AAR file to upper level directory

    from zipTree("build/outputs/aar/" + aarName + "-release.aar")
    exclude('**/*.so') //do not include shared libraries into final AAR
    from("../../src") {
        exclude('makefile')
        exclude('soname.txt')
        exclude('**/*.cpp')
        exclude('**/*.c')
        into('ndkLibs/include')
    }
    from("build/intermediates/binaries/debug/lib"){
        include('**/*.a')
        into('ndkLibs')
    }
}

build.finalizedBy('copyNdkLibsToAAR')

###

From this Link, it doesn’t look like it is possible. I am pasting below the contents:

Anatomy of an AAR file

The file extension for an AAR file is .aar, and the Maven artifact type should be aar as well. The file itself is a zip file containing the following mandatory entries:

  • /AndroidManifest.xml
  • /classes.jar
  • /res/
  • /R.txt

Additionally, an AAR file may include one or more of the following optional entries:

  • /assets/
  • /libs/name.jar
  • /jni/abi_name/name.so (where abi_name is one of the Android supported ABIs)
  • /proguard.txt
  • /lint.jar

As said above, the mandatory entry includes a jar. You can however can give a try by manually deleting the jar file by unzipping the aar and zip back again. I am not sure whether it will work though.

###

Although i haven’t personally tried to, i found a number of steps here:

Probably indirectly [tried with shared lib once a while ago], and I personally do not think it is worthwhile:

  • build your lib first to generate a static lib, and one aar
    [model.library does not tuck *.a into libs directory though]
  • unzip your aar, and put *.a into libs folder
  • find a place for your headers file
  • zip it up back to aar inside your app, make aar to be your dependent lib, so it will be extracted into exploded-aar folder; then the colorful picture appears.
  • add the intermediate explodeded aar directory into include path
    I think it is too hacky and may not be a good idea to impose those into your customers.

traditional way of distributing lib and header files directly is still better, comparing to the above hacking.
For building libs, cmake way is much better, checkout hello-libs in master-cmake branch, hope this helps

###

Manually hacking the gradle scripts works, but is painful and error-prone.

I’ve recently found a plugin that magically bundles the headers into the AAR files and extracts them and sets up the build scripts when adding the dependency: https://github.com/howardpang/androidNativeBundle

On the reusable library:

  • Add the export plugin:

    apply plugin: 'com.ydq.android.gradle.native-aar.export'
    
  • Define where the header files are:

    nativeBundleExport {
        headerDir = "${project.projectDir}/src/main/jni/include"
    }
    

On the module that uses it:

  • Add the import plugin:

    apply plugin: 'com.ydq.android.gradle.native-aar.import'
    
  • add include ${ANDROID_GRADLE_NATIVE_BUNDLE_PLUGIN_MK} to each module that depends on it in your Android.mk:

    include $(CLEAR_VARS)
    LOCAL_SRC_FILES := myapp.cpp \
    LOCAL_MODULE := myapp
    LOCAL_LDLIBS += -llog
    include ${ANDROID_GRADLE_NATIVE_BUNDLE_PLUGIN_MK}
    include $(BUILD_SHARED_LIBRARY)
    

Leave a Reply

Your email address will not be published. Required fields are marked *