Breaking Changes in JUCE’s low-level Android code

With commit 9a4548a (on develop), we have completely re-structured JUCE’s low-level Android code. Although no public JUCE APIs (any APIs listed on docs.juce.com) have changed, this commit will likely introduce breaking changes if you used any of JUCE’s internal JNI helper macros (modules/juce_core/juce_android_JNIHelpers.h), invoked native android code directly or relied on JUCE’s auto-generated java code.

  • Before any JUCE API function is called, you must now invoke the Threads::initialiseJUCE method. This method can also be called directly from java via com.roli.juce.Java.initialiseJUCE(). The Projucer will add java code to your project to invoke this method automatically (see next point for more details) - so in most cases you do not need to do anything. However, if you did not use the Projucer to generate your project, you are creating a library which is used in non-JUCE apps or using the Projucer’s “Custom Android Application” field (see next point), then you will need to invoke Threads::initialiseJUCE once manually, before you call any other JUCE API methods.
  • JUCE apks no longer derive directly from android.app.Application but from a new JUCE java class called com.roli.juce.JuceApp. This is a very simple class which ensures that the juce shared library is loaded and the com.roli.juce.Java.initialiseJUCE() method (see above) is automatically invoked on app startup. If your apk needs to derive from a different class then this can be customised in the Projucer’s “Custom Android Application” field. Note, however, that you are then responsible for loading the JUCE shared library (via System.loadLibrary) and calling com.roli.juce.Java.initialiseJUCE()
  • setEnv is no longer necessary and has been removed. getEnv now uses the current java vm to retrieve the current thread’s JNIEnv variable and will return a valid value as long as you have called Threads::initialiseJUCE on initialisation.
  • The X macros JNI_CLASS_MEMBERS now requires an extra CALLBACK parameter. This X macro placeholder can be used to list any native jni callbacks in the class you are defining and can be used as a replacement for the now removed JUCE_JNI_CALLBACK macro. This allows you to define any jni callbacks inline in your class:
class MyClass
{
public:
…
private:
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  METHOD (someJavaMethod, "someJavaMethod","(I)V") \
  CALLBACK (jniCallback, "jniCallback", "()V")

DECLARE_JNI_CLASS (MyClass, "com/acme/MyClass")
#undef JNI_CLASS_MEMBERS

static void JNIEXPORT jniCallback (JNIEnv* env, jobject javaInstance)
{
...
}
};

In addition to being more elegant (as the jni callbacks are now inline to the class which they are used), the callback is also resolved at runtime and not via the symbol name (as was the case with JUCE_JNI_CALLBACK). This is important to avoid conflicts if different versions of your code (with the same callback name and class) is used in one apk (which can happen if your project is an android library). See this example project for a more detailed example.

  • JUCE_JNI_CALLBACK macro has been removed. See the notes to JNI_CLASS_MEMBERS for an alternative
  • Desktop::Displays's totalArea and userArea now properly reflect the whole screen’s area and the area of the screen not covered by the status/button bars respectively. Previously, totalArea and userArea was identical on Android and referred to the area not covered by the status and button bar.
  • JUCE no longer uses the Projucer-generated JuceAppAcitivity as the main activity class. Previously, all java code was inside this auto-generated file. Now JUCE’s java code has been split up into several java files located in the native/java folder in each JUCE module which uses java. This java code was then compiled into byte-code which is directly integrated into JUCE’s binaries. This ensures that 1. only the java code necessary for the modules that you are using is added to your build, and 2. that you can use a shared/static library built with JUCE, without requiring the app using the library to include any extra java files.
6 Likes

What if I want to call Juce from Flutter’s FFI, bypassing Java/Kotlin completely, as described here? Do I really need to call initialiseJUCE (Context appContext); on Android?

There’s no java context to be passed

2 Likes