Android Studio: Package Native Libraries into APK

BLUF: See build.gradle files embedded…is there something I need to add to an android build.gradle file to get the desired result.

I am trying to get JOGAMPs JOAL (OpenAL) to package into an APK for my Android applications. The code compiles and generates the expected APK file but when executed on the device it gets a dlopen failed: library “//natives/android-aarch64//libgluegen-rt.so” not found.

I understand the error, or so I think. It cannot find the native library Nibelungen-rt.so.

I agree it’s not on the phone. I’m hoping to package it inside the APK. I suspect the error is telling me it cannot find it at the root of the classpath. Then IF it was in the APK I think it would find it. Or is it telling me it cannot find the .so file at the device level file system? I’m not sure.

For reference I created the same project first as a regular Java application in eclipse, using Gradle and shadowJar and I get the ‘fat’ jar I am expecting. At the root of the jar, there is a natives folder along with the expected folders and files. Runs as expected. Replicating as close as possible in a simple ‘empty activity’ project it compiles as well as installs and aborts.

I’d love to attach both the desktop and android projects for completeness but it looks like that’s not an option.

Included in this post is the android build.gradle from the app folder, followed by the build.gradle from the java application. Lastly the output from logcat in android studio with the output from the execution. Line of interest is … java.lang.UnsatisfiedLinkError: dlopen failed: library “//natives/android-aarch64//libgluegen-rt.so” not found … but the stack trace is there.

Am I misreading the error message? I know the APK does not contain a natives folder via inspection. Just not sure what I am chasing. I wouldn’t think I’d need to include all the sources manually and go down the NDK route but…

I’d appreciate any insight into what I am missing to build a ‘fat’ APK or ‘uber’ APK as I have seen it mentioned.

With respect to what I have tried, I’ve chased adding libs in the app folder, attempting the file tree route, many others I can’t even remember right now. I suspect its something small I am missing OR it’s just not possible. I’d hope for the former.

If there is something that needs clarification please ask. The development platform is ubuntu 19.10 up to date. The device is a galaxy S9. This is part of a larger OpenGL VR application which is working just fine. I want to move my positional audio engine from the desktop world to android.


ANDROID


    apply plugin: 'com.android.application'

    android {
        compileSdkVersion 29

        defaultConfig {
            applicationId "android.joal"
            minSdkVersion 22
            targetSdkVersion 29
            versionCode 1
            versionName "1.0"

            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }

        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
    }

    dependencies {
        implementation fileTree(dir: "libs", include: ["*.jar"])
        implementation 'androidx.appcompat:appcompat:1.1.0'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

        // Parts of JOAL needed to compile.
        implementation "org.jogamp.gluegen:gluegen-rt-android:2.3.2"
        implementation "org.jogamp.joal:joal-android:2.3.2"
        // Parts of JOAL (Native Libraries) needed to execute.
        // These I suspect should reside at the root of the APK at...
        // /natives/android-aarch64/*.so
        runtimeOnly "org.jogamp.gluegen:gluegen-rt-android:2.3.2:natives-android-aarch64"
        runtimeOnly "org.jogamp.joal:joal-android:2.3.2:natives-android-aarch64"

        testImplementation 'junit:junit:4.12'

        androidTestImplementation 'androidx.test.ext:junit:1.1.1'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    }

DESKTOP


plugins {
id ‘java’
id ‘eclipse’
id ‘com.github.johnrengelman.shadow’ version ‘5.2.0’
}
repositories {
    jcenter()
}

jar {
    manifest {
        attributes 'Main-Class': 'desktop.joal.App'
    }
}

dependencies {
    implementation "org.jogamp.gluegen:gluegen-rt:2.3.2"
   implementation "org.jogamp.joal:joal:2.3.2"

    runtimeOnly "org.jogamp.gluegen:gluegen-rt:2.3.2:natives-linux-amd64"
    runtimeOnly "org.jogamp.joal:joal:2.3.2:natives-linux-amd64"

    // Use JUnit test framework
    testImplementation 'junit:junit:4.13'
}

shadowJar {
    zip64 false
    baseName = jar.baseName

    exclude "**/*.sha1", "**/*.git"
}
assemble.dependsOn shadowJar


ANDROID LOGCAT


2020-06-27 20:21:04.656 8729-8729/? E/Zygote: isWhitelistProcess - Process is Whitelisted
2020-06-27 20:21:04.658 8729-8729/? E/Zygote: accessInfo : 1
2020-06-27 20:21:04.672 8729-8729/? I/android.joal: Late-enabling -Xcheck:jni
2020-06-27 20:21:04.693 8729-8729/? E/android.joal: Unknown bits set in runtime_flags: 0x8000
2020-06-27 20:21:04.704 8729-8729/? D/ActivityThread: setConscryptValidator
2020-06-27 20:21:04.705 8729-8729/? D/ActivityThread: setConscryptValidator - put
2020-06-27 20:21:04.878 8729-8729/android.joal I/MultiWindowDecorSupport: updateCaptionType >> DecorView@28d56d5, isFloating: false, isApplication: true, hasWindowDecorCaption: false, hasWindowControllerCallback: true
2020-06-27 20:21:04.878 8729-8729/android.joal D/MultiWindowDecorSupport: setCaptionType = 0, DecorView = DecorView@28d56d5
2020-06-27 20:21:04.916 8729-8729/android.joal W/android.joal: Accessing hidden method Landroid/view/View;->computeFitSystemWindows(Landroid/graphics/Rect;Landroid/graphics/Rect;)Z (greylist, reflection, allowed)
2020-06-27 20:21:04.917 8729-8729/android.joal W/android.joal: Accessing hidden method Landroid/view/ViewGroup;->makeOptionalFitsSystemWindows()V (greylist, reflection, allowed)
2020-06-27 20:21:04.966 8729-8729/android.joal D/AndroidRuntime: Shutting down VM
2020-06-27 20:21:04.969 8729-8729/android.joal E/AndroidRuntime: FATAL EXCEPTION: main
Process: android.joal, PID: 8729
java.lang.UnsatisfiedLinkError: dlopen failed: library “//natives/android-aarch64//libgluegen-rt.so” not found
at java.lang.Runtime.load0(Runtime.java:938)
at java.lang.System.load(System.java:1631)
at com.jogamp.common.jvm.JNILibLoaderBase.loadLibraryInternal(JNILibLoaderBase.java:624)
at com.jogamp.common.jvm.JNILibLoaderBase.access$000(JNILibLoaderBase.java:63)
at com.jogamp.common.jvm.JNILibLoaderBase$DefaultAction.loadLibrary(JNILibLoaderBase.java:106)
at com.jogamp.common.jvm.JNILibLoaderBase.loadLibrary(JNILibLoaderBase.java:487)
at com.jogamp.common.os.DynamicLibraryBundle$GlueJNILibLoader.loadLibrary(DynamicLibraryBundle.java:421)
at com.jogamp.common.os.Platform$1.run(Platform.java:317)
at java.security.AccessController.doPrivileged(AccessController.java:43)
at com.jogamp.common.os.Platform.(Platform.java:287)
at com.jogamp.common.os.Platform.initSingleton(Platform.java:355)
at com.jogamp.openal.ALFactory.(ALFactory.java:68)
at com.jogamp.openal.ALFactory.getAL(ALFactory.java:123)
at android.joal.MainActivity$App.initialize(MainActivity.java:57)
at android.joal.MainActivity.onCreate(MainActivity.java:30)
at android.app.Activity.performCreate(Activity.java:7955)
at android.app.Activity.performCreate(Activity.java:7944)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1307)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3423)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2147)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:7811)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1076)

Maybe it makes more sense to attempt to frame the problem a little more and a little more back history.

In the past, and currently, I am able to make JOAL do ‘something’ by installing the prepackaged APKs below. All APKs are Version 2.4.0-rc-20200306 and it takes all four for the JOAL test to function.

jogamp-android-launcher.apk
joal-test-android.apk
joal-android-aarch64.apk
gluegen-rt-android-aarch64.apk

These APKs allow the JOAL test application to function and a sound is played. It appears the ‘JogAmp’s Android Activity Launcher’ ‘Features daisy chained APK incl. native libraries, reassembling the Java CLASSLOADER and library path experience’. Reading this two parts stick out. ‘native libraries’ and ‘reassembling the Java CLASSLOADER and library path’. I suspect this is the magic to get this to work but I am unclear how.

The error coming from Android Studio ‘dlopen failed: library “//natives/android-aarch64//libgluegen-rt.so” not found.’ indicates to me that the application is NOT attempting to load ‘global’ resources, those ‘other’ libraries on the device, but rather its telling me its looking (or wants to) in the APK itself.

So maybe the first question to answer is…

What does the community think the error message is saying?

Additionally…does anyone know if the sources for the APKs listed above exists? Maybe there is information there to be revealed. If I can generate those APKs on my development system and get them to execute I’ll have a ‘working template’.

So small progress…I’ve got the libs packages in the APK but at /lib//*.so but adding the following to the APK.

android {

sourceSets {
main {
jniLibs.srcDirs ‘src/main/jniLibs/natives’
}
}
}

So maybe another way around this…anyway to change the path the JOAL package is trying to load from? Its looking in //natives///.so. MAybe I can make it point to //lib///.so.

Also…I see if the source there is a DEBUG flag in the JOGAMP sources…any way to get that to a TRUE so it spits out all the debug statements being overlooked.

Someone has to have done this? If not using JOGAMP then anyone have a good source for a recent android OpenAL implementation I can include from source?

Its been a month since I posted this original dilemma of mine. Since then I have attempted to get other variants of OpenAL for Android to compile and be usable withing a Java domain all ending in the same result of failing with a DLL Open error. I was able to get the JOGAMP libraries into a ‘lib’ folder but the JOGAMP libraries are looking for them in ‘natives’ so no joy.

So, I spent some time researching the “oh I’ll just do it myself” approach. After about 20 hours or so I ended up with a working surrogate using Android’s AudioTrack on its own thread in streaming mode using the OpenAL specification as inspiration. I will say I was rather impressed. Multi-sample spatial audio with distance fall-off in a VR environment worked really well. The latency is not what the Internet said, roughly 250ms. The delay from emission of sample and visual representation to hearing the sample is not evident unless I slow the thread to a 10hz cycle or lower. It plugged into the interface shared by both the desktop and Android variants. Test users said there was no noticeable latency and were able to identify which moving sources were emitting what sample in the VR environment.

Thus I have a working Android 3D Audio model (with no external dependency) that when side by side with the desktop variant using JOGAMP OpenAL sound and act virtually identical. Is it a perfect solution to a JNI implementation…nope! I suspect there are GC concerns and potential latency problems. Does it meet the need, solve an immediate problem and function adequately…absolutely! Funny I spent less time building from the ground up than trying to get other ‘available’ solutions to simply link in. Is the new implementation complete…no! It needs Doppler and other distance effects. The mixer is a simple summing algorithm suspect to over gain that may require clamping. I’ve feed dozens of simultaneous samples to it, looping and single cycle, and thus far no over-gain concerns. But for a ‘listener is here looking this way’ and ‘source X is here, source Y is here, …’ it works really well.

I hope someday there is a clean way to get these pre-builts in and usable but for now success.