Build juce shared library for Android, stuck

Dear Juce Experts,

I'm trying to compile Juce library for Android, so I can link to something like libjuce.so and my C++ code can utilize awesome classes like AudioSampleBuffer etc.  So here's what I did:

1. Projucer an Android gui application, compiled it with Android Studio successfully. 

2. in path_to_my_folder\AndroidStudio\app\build\intermediates\binaries\debug\armeabi-v7a\lib\armeabi-v7a, I found libjuce_jni.so

3. Write an Android.mk to link to this libjuce_jni.so, while including JuceHeader.h in the correct folder 

4. Then when I'm trying to link to it this way, I got:

"/../../JuceLibraryCode/modules/juce_opengl/../../../../JUCE/modules/juce_opengl/juce_opengl.h:59:20: fatal error: GL/gl.h: No such file or directory  #include <GL/gl.h> "

Apparently in this juce_opengl.h, it think JUCE_WINDOWS is active and not JUCE_ANDROID flag. 

 

At this point I'm banging my head and stuck, CouldJu help? Am I doing this whole thing right, or did I miss the correct way to set some flag to get around this?

Thanks in advance.

Another option might be to import your existing C++ code into your JUCE project (maybe wrap it in a JUCE module) and compile it for Android... that way you save messing around with static libraries which in my experience can be a bit of a pain.

It seems like you are not defining JUCE_ANDROID. Try setting this from the compiler command line.

Building C++ libraries for Android, whether static or dynamic, is currently not supported.

The reason is that a lot of JUCE functionality on Android relies on our JUCE AppActivity class written in Java. But if you are building a library, you don't have that activity class.

I'll put an assert or error message into the Introjucer making clear that this is not supposed to work.

Is this still true?

Well it’s semi-true. Timur is right that a lot of JUCE code needs JUCE’s AppActivity to be the app activity of your android app. Basically (almost) any code that needs to call into java will likely fail when linked as a shared/static library.

Off the top of my head you won’t be able to use the following:

  • juce_gui_basics/juce_gui_extra: everything
  • juce_graphics: all font and text stuff
  • juce_events: anything to do with the message loop
  • juce_core: anything that requires loading a URL, realtime audio threading priorities, some stuff in SystemStats (a lot of stuff in JUCE indirectly uses SystemStats :frowning:)
  • juce_audio_devices: everything
  • juce_opengl: everything

I think most of the stuff in juce_audio_basics, juce_audio_processors and juce_dsp should be fine.

1 Like

is this still true?

It was suggested above the issue was the dependence on the App Activity class… does this later announcement - Breaking Changes in JUCE’s low-level Android code - suggest this changed the situation?

what is current status for building Android Static/Dynamic Libraries?

No this is not true anymore. You can now build Android Static/Dynamic libraries as long as you observer some of the notes mentioned in the post you linked:

2 Likes

Thank you for the prompt answer @fabian, much appreciated

Hi, having problems initialising JUCE in a shared library. Anyone got any pointers on this? thx

Using JUCE inside a native shared library on Android depends on how your shared library is used by your Android app. There are three common options:

  1. Your android app is a JUCE app. Then you really shouldn’t need to do anything. Let the Projucer create your android project and simply add the shared libraries you want to use to the Projucer. Once your android project is created, you could choose to continue in Android Studio, for example.

  2. You android app is a native kotlin/java app and directly loads your native shared library which uses JUCE. For this, you will probably have created your native android app with Android Studio. For the shared library, create a shared library JUCE project with the Projucer (or cmake). Make sure that your JUCE project compiles fine. Then your native android app will need to load the shared library from Java/Kotlin via System.loadLibrary. After this you will need to call com.roli.juce.Java.initialiseJUCE() from Java on every thread that will call into your native shared library.

  3. Your android app is a native kotlin/java app which loads some third-party shared library which in turn loads your shared library which uses JUCE. Here, in addition to calling com.roli.juce.Java.initialiseJUCE() on every thread, you first need to call JNI_OnLoad (which is defined in JUCE) before calling anything else (including com.roli.juce.Java.initialiseJUCE()). This is because Java/Kotlin’s System.loadLibrary normally does this automatically (as in 2) but here, your shared library isn’t being loaded by Java/Kotlin but by another native library.

See this thread for more information:

ok, thanks. So the situation I’m currently trying to get working is a JUCE android app and a shared library using juce. I’ve been using DynamicLibrary to load the library though - which ultimately calls dylib - are you saying that I shouldn’t be doing this but should should load the library from java?

( I don’t know exactly what the final target architecture will be yet, it’ll probably be 2 I would imagine, but would like to get this use case working as a POC)

Thx

Hmm probably a dumb question, but if your android app is already a JUCE app, why not just copy the code of your DynamicLibrary into the JUCE app’s project?

I’ve been using DynamicLibrary to load the library though - which ultimately calls dylib - are you saying that I shouldn’t be doing this but should should load the library from java?

It’s ok for you to do it this way. Then you essentially have the third case that I mentioned above, i.e. you need to call JNI_OnLoad symbol inside the loaded dynamic library (you can do this from JUCE) and then call JNIClassBase::initialiseAllClasses (env, context) once and then Thread::initialiseJUCE (env, context) for any thread that needs it. Make sure to always call the symbol inside the loaded library and not the symbol in the JUCE app’s copy of JUCE.

I’m not entirely sure that this will work smoothly though as you now have two instances of JUCE trying to register Java classes. Java will call into these classes and potentially be routed into the wrong JUCE shared library instance. I’ve never tried this before.

1 Like

No, a sensible question, but we want to mimic the target environment as much as possible which will be loading a dynamic library and having to deal with the fact that it’s running juce. We want the setup to be able to run on Mac as well as Java as we’ll be doing most of our dev that way due to Android being a pain to develop with :).

Ok, looks like the piece I’ve been missing in the JNI_OnLoad. I’ve just looked at this and it requires a java VM pointer to be passed in - how do I get this in the C++ that’s loading the library to pass across to the first function call into the library?

So, heres an example of what I’m currently trying implement:

auto library = std::make_unique<juce::DynamicLibrary>();
library->open( "mylib.so" );

auto *p_init_fn = ( void ( * )( void ) )m_p_library->getFunction( "init_fn" );
( *p_init_fn )();

on the calling side which calls into a function in the library

void init_fn() {
#if JUCE_ANDROID
//    juce::JNI_OnLoad( (JavaVM *)p_vm, nullptr );
//    juce::JNIClassBase::initialiseAllClasses( (JNIEnv *)p_jni_env, (jobject)p_context );
//    juce::Thread::initialiseJUCE( p_jni_env, p_context );
#endif

    juce::MessageManager::getInstance();
    do_stuff;
}

so this works fine mac, windows etc as doesn’t need to do the JNI stuff - however, for Android I don’t have the vm, jni env, or the context needed to call these functions.

thx

The app that’s loading the mylib.so will already have a pointer to the VM here:

1 Like

Thx, got the OnLoad working, but still don’t have a context for the call to initialiseAllClasses(), should this be passed over from the loading process also? We’re not actually going with the dynamic loading so this isn’t actually necessary any more, but would like to get it working for completeness and understanding on my part.

THx

Yes, you would also pass the context over.