Android AAR package for native library
= UPDATE 2020-06-20 =
Nowadays, there is a nice plugin for that, works pretty well. Thanks to its author and to @Paulo Costa for pointing to it.
= OBSOLETE =
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')
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 yourAndroid.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)