JUCE for Android with external shared libraries: library no found! [Solved]

I’m trying to export my JUCE app to Android, but am struggling with attaching pre-compiled shared libraries with ProJucer, three of them: mosquitto, openssl and crypto. The steps below are what I did first, it seemed to result in a running app, however at the next build try everything broke. Any advice on how to achieve a perfect ProJucer config is appreciated! I was able to build a running app by attaching all the libraries to the CMakeLists.txt file manually, however the best solution would be to use a ProJucer generated

Below is how the field in the ProJucer configuration were filled, at the end a part of the CMakeLists.txt file was modified by removing the square brackets around the library variable names.

Android: External Libraries to Link

mosquitto
ssl
crypto

Android:Debug and Release: Header Search Paths

../../../3rdparty/mosquitto/include

Android:Debug and Release: External Library Search Paths

../../../3rdparty/openssl-3.0.12/lib-android/${ANDROID_ABI}
../../../3rdparty/mosquitto/lib-android/${ANDROID_ABI}

Modifications to the CMakeLists.txt file:

original square brackets in the CMakeLists.txt

target_link_libraries( ${BINARY_NAME}

    ${log}
    ${android}
    ${glesv2}
    ${egl}
    "cpufeatures"
    "oboe"
    [[mosquitto]]
    [[ssl]]
    [[crypto]]
)

and what they should be

target_link_libraries( ${BINARY_NAME}

    ${log}
    ${android}
    ${glesv2}
    ${egl}
    "cpufeatures"
    "oboe"
    mosquitto
    ssl
    crypto
)

The result of this configuration is a app that builds, however when run on emulator or a real android device it crashes immediately with an error saying that libmosquitto.so was not found.

FATAL EXCEPTION: main
Process: com.genelec.nguiapp, PID: 11261
java.lang.UnsatisfiedLinkError: dlopen failed: library "libmosquitto.so" not found: 
needed by /data/app/~~Zq1IYVmdVOCMKHVYcvwQxw==/com.genelec.nguiapp-aJnshUE5XhI4Fk72M8QTqA==/lib/x86_64/libjuce_jni.so in namespace clns-6
at java.lang.Runtime.loadLibrary0(Runtime.java:1082)

clearly the library is not accessible, perhaps not attached to the built and burned app… but why? What have I missed?

I can share some thoughts, but I’m afraid I don’t have a clear-cut answer for you.

In my case I basically do the same (with using ${ANDROID_ABI} as part of the search path), and I think this approach makes sense.

The error you’re getting indicates that the dynamic library libmosquitto.so is not found which suggests the library itself is not installed on your device (as part of the app bundle).

I’m not sure how to solve this problem, but a quick google seems to suggest you have to place the .so files at a specific location inside your project folder. This this StackOverflow answer for more details.

Alternatively you might want to try an link the dependencies statically which would avoid the need of copying the libraries onto the users device (since the code would be embedded in the main app binary). This is what I do and it seems to be working well.

My current solution involves tweaking the oboe CMakeLists.txt file. I’m not an expert in Cmake, but wanted to point out that the object returned by find_library doesn’t seem to attach the library to the target, it seems to return just a path object. With this in mind the next step is to use add_library to create a proper reference to a SHARED IMPORTED library, that this time can be attached to oboe. Below piece of cmake is appended to oboe/CMakeLists.txt:

find_library(mosquitto_path "mosquitto" PATHS
        "../../../../../../3rdparty/mosquitto/lib-android/${ANDROID_ABI}"
        NO_CMAKE_FIND_ROOT_PATH
        NO_DEFAULT_PATH)

message(STATUS "mosquitto path: ${mosquitto_path}")

find_library(ssl_path "ssl" PATHS
        "../../../../../../3rdparty/openssl-3.0.12/lib-android/${ANDROID_ABI}"
        NO_CMAKE_FIND_ROOT_PATH
        NO_DEFAULT_PATH)

find_library(crypto_path "crypto" PATHS
        "../../../../../../3rdparty/openssl-3.0.12/lib-android/${ANDROID_ABI}"
        NO_CMAKE_FIND_ROOT_PATH)

add_library(
        mosquitto
        SHARED
        IMPORTED
)
set_property(TARGET mosquitto PROPERTY IMPORTED_LOCATION ${mosquitto_path})

add_library(
        ssl
        SHARED
        IMPORTED
)
set_property(TARGET ssl PROPERTY IMPORTED_LOCATION ${ssl_path})

add_library(
        crypto
        SHARED
        IMPORTED
)
set_property(TARGET crypto PROPERTY IMPORTED_LOCATION ${crypto_path})

target_link_libraries(oboe PUBLIC mosquitto ssl crypto)

When trying to put the same piece of code to app/CMakeLists.txt the result was as before - libraries not attached to the binary. It seems like it is not possible to attach anything to ${BINARY_NAME}. Why use find_library if the developer defining the ProJucer settings clearly knows where the libraries are at? Why not just ask the devs to provide a full path to the pre-compiled libraries?

On the other hand if it really is enough to insert those libs into a special location, then I think that information is not easy to find. I’d expect this to be a part of the Android tutorial document.

Without seeing the whole project I can’t give you any definitive answers or guidelines, but I can share some thoughts:

  • You shouldn’t have to manually edit the CMakeLists.txt of oboe, this indicates that probably something is wrong with the toplevel CMakeLists.txt

  • Instead of using find_library you can link a library directly like this: target_link_libraries(target PUBLIC path/to/libs/mosquitto.a)

  • You should definitely not link the dependencies to oboe.

Would you be able to provide a minimal reproducible example?

1 Like

Okay, so I made the minimal example - GitHub - anthonio9/juce-android-example: Repository with the purpose of resolving the JUCE issue of external library integration for Android.
A simple project that uses mosquitto library, which on the other hand depends on OpenSSL, and crypto, all three are precompiled and available in the 3rdparty directory. Builds are configures for Linux and Android, on Linux all works fine if mosquitto and openssl are installed on the system.

The JuceAndroidExample.jucer references external libraries to link: ssl, mosquitto and crypto, however when launching the app, below errro is thrown immediately:

FATAL EXCEPTION: 
Process: com.yourcompany.juceandroidexample, PID: 7170
java.lang.UnsatisfiedLinkError: dlopen failed: library "libssl.so" not found: 
needed by 
/data/app/~~yxKM4oV01B-ByOJY85WHDg==/com.yourcompany.juceandroidexample-iuQxmX9XwEYII140PVsWnA==/lib/x86_64/libjuce_jni.so in namespace clns-6
at java.lang.Runtime.loadLibrary0(Runtime.java:1082)
at java.lang.Runtime.loadLibrary0(Runtime.java:1003)
at java.lang.System.loadLibrary(System.java:1661)
at com.rmsl.juce.Java.<clinit>(Java.java:31)
at com.rmsl.juce.JuceApp.onCreate(JuceApp.java:35)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1316)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6998)
at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2236)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java: at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

Would you be able to provide a configuration of this project that you consider correct according to JUCE best practices?

The current libraries require dynamic linkage, would you be able to link the dependencies statically? That solves a lot of hassle in this regard.

1 Like

I managed to compile and upload the static libraries for all three. Checkout the latest commit in the same repository. Hope that helps!

I’ve also figured out that maybe using cmake ${PROJECT_SOURCE_DIR} could help with figuring out the paths and included that in the jucer confing. This approach seems to add the static libraries to the binary, however in the end new errors come up:

ld: error: undefined symbol: stderr
ld: error: undefined symbol: stdin
ld: error: undefined symbol: __sendto_chk
ld: error: undefined symbol: getgrgid_r
ld: error: undefined symbol: __write_chk

clang++: error: linker command failed with exit code 1 (use -v to see invocation)

What are these related to?

EDIT:

I’m afraid those are symbols somehow missing from mosquitto. getgrgid_r is present in mosquitto code. Similar with tcsetattr which lack of is thrown for x86. I suppose those are missing.

Okay, I think the solution to this problem is found now.

First, static libraries are much easier to put into the binary than shared libraries. Projucer handles the config generation was static libraries just right, just like @ruurdadema mentioned.

Second, it’s important that the Minimum SDK Version matches the version for which the static libraries were build with Android NDK. This makes sure that problems with lacking symbols like stderr or getgrgid_r which are supposed to come with the system will be available for the binary.

And finally, third, under Extra Library Search Paths put a very obvious path to the static libraries directory, obvious enough to not be mixed with shared libraries by Cmake’s find_library function.

Knowing all that, below is the right config for the example of inlcuding precompiled mosquitto, OpenSSL and crypto libraries, this time STATIC.

Android: External Libraries to Link

mosquitto_static
ssl
crypto

Android:Debug and Release: Header Search Paths

${PROJECT_SOURCE_DIR}/../../../../3rdparty/mosquitto/include

Android:Debug and Release: Extra Library Search Paths

${PROJECT_SOURCE_DIR}/../../../../3rdparty/openssl/lib-android/${ANDROID_ABI}/static/
${PROJECT_SOURCE_DIR}/../../../../3rdparty/openssl/lib-android/${ANDROID_ABI}/static/
${PROJECT_SOURCE_DIR}/../../../../3rdparty/mosquitto/lib-android/${ANDROID_ABI}/static/

${PROJECT_SOURCE_DIR} is not necessary, but helps to set the paths clearly once it’s known what is exactly the CMake project source dir.

That’s it, thanks for help.

I am in a similar position as you were @rockerr. In my case, I am trying to link the mysqlclient and C++ connector mysqlcppconn libraries. Following your advice, which so far has helped me include the header file libraries, I have got this:

Android : External Libraries to Link

mysqlclient
mysqlcppconn8-static

Android:Debug and Release: Header Search Paths

${PROJECT_SOURCE_DIR}/../../../../third-party/mysql/include
${PROJECT_SOURCE_DIR}/../../../../third-party/mysqlCPP/include

Android:Debug and Release: Extra Library Search Paths

${PROJECT_SOURCE_DIR}/../../../../third-party/mysql/lib
${PROJECT_SOURCE_DIR}/../../../../third-party/mysqlCPP/lib64

Still, I get several

"undefined symbol" errors and also “ld: error: undefined symbol: _get_driver_instance_by_name”. Similar to errors I had when unsuccessfully linking to the library on Windows or MacOs

I am also slightly confused as to what ${ANDROID_ABI} means in this sense, but I gather it tells Android Studio to look for the library aimed at the correct CPU architecture (?).

In my case, mysqlclient and cppconn do not provide an Android build, but since the code works C+±wise, I do not see why it could not work anyway?

Yes, ANDROID_ABI tells the architecture, there are a few options for this, JUCE supports arm64-v8a · armeabi-v7a · x86_64 · x86 (or at least those are the default ones set by Projucer).

In my opinion you have to provide those libraries in the correct architecture, unless you somehow tell Projucer to build them from source. If you’re attaching pre-build libraries then it’s crucial to have them compiled to the right architectures, however it’s your choice which architectures are supported by your app. Try to compile mysqlclient for android, this answer on stackoverflow should help - Android studio provides the cross-compilation tools, which you’ll have to use. It could be possible that if you’re only architecture is x86_64, then the original library builds will work fine.