Removing most of the Java "Windowing" code from Activity


#1

I have a simple application that ideally use only the audio stuff from Juce.
I created an entry point like this:

class  MyApp: public JUCEApplicationBase
{
public:
    .................
}

// this generates boilerplate code to launch our app class:
START_JUCE_APPLICATION (MyApp)

Within MyApp Class I instantiate an object of type:

class MyAudio : AudioSource
{
public:
    MyAudio();
    ~MyAudio();

    void setAudioChannels(int numInCh, int numOutCh);
    void shutDownAudio();

private:
    AudioDeviceManager audioDevice;
    AudioSourcePlayer audioSourcePlayer;

    // Inherited via AudioSource
    void prepareToPlay(int samplesPerBlockExpected, double sampleRate) override;
    void releaseResources() override;
    void getNextAudioBlock(const AudioSourceChannelInfo & bufferToFill) override;
};

Up to here everything work as expected!


Than I tried to remove all the Java code that I don't need (I don't want to use any "Windowing" code from Juce). Ideally I'd like to have only:

public class Android2 extends Activity
{
    static
    {
        System.loadLibrary ("Introducer1");
    }

    @Override
    public void onCreate (Bundle savedInstanceState)
    {
        super.onCreate (savedInstanceState);

        launchApp (getApplicationInfo().publicSourceDir,
                   getApplicationInfo().dataDir);
    }

    private native void launchApp (String appFile, String appDataDir);
}

I found that the boostrap code is in "juce_android_Windowing.cpp".
I tried to comment out the Inizialize_GUI() function:

//juce_android_Windowing.cpp
   
    android.initialise (env, activity, appFile, appDataDir);

    DBG (SystemStats::getJUCEVersion());

    JUCEApplicationBase::createInstance = &juce_CreateApplication;

    //initialiseJuce_GUI();

but that did not work.
When the following function


android.initialise (env, activity, appFile, appDataDir);


is called and the Jave code is not there, the program crash with an "Abort" exception.
If I try to comment out some of the class that I don't need like for example:

//#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
// METHOD (drawBitmap,       "drawBitmap",    "([IIIFFIIZLandroid/graphics/Paint;)V") \
// METHOD (getClipBounds,    "getClipBounds", "()Landroid/graphics/Rect;")

//DECLARE_JNI_CLASS (CanvasMinimal, "android/graphics/Canvas");
//#undef JNI_CLASS_MEMBERS

I'm getting compiling error because other portion of the code is using those classes.

I wonder if there is a way of accomplish what I'm looking for or if all the Java class/methid must be there and there's no way around it.
Do You have any suggestion on how to proceed?

Thank You
Alessandro


#2

As I understand it, you basically want to use your own Activity class instead of the JUCE one?

The problem at the moment is that the Java side of all of JUCE's audio functionality (and a lot lot more) is implemented as subclasses/methods of the JuceActivity class. So yes, if the JuceActivity Java class is not there then basically no JUCE stuff will work at all.

We could perhaps change that architecture at some point because it's not ideal (doesn't support use cases like yours). But it'd require a *massive* refactoring of all our Android code and a lot of effort. Unfortunately we don't have the resources to do this right now.

Maybe you could go ahead and see if you can make it work and then share it here?


#3

Hi Timur,
Thank You for the quick replay.
I'll try and see what I can do. I''l share my result here on the forum to update people who may be interested.
Alessandro


#4

Why don't you subclass the Juce Activity and alter it as needed? If that doesnt work for you, you can also carefully edit the generated activity and then put it in your VCS... But thats not ideal. I've used both methods to customise the Java side of an app. 


#5

Hi Adamski,
I cannot subclass it because I'm using Mono (Xamarin) and even though it should be possible to subclass java with C# it's painful and the solution does not look clean to me.
Thank You for the suggestion anyway!
Alessandro


#6

Oh wow, you're mixing two different cross-platform frameworks! That is bold. Good luck!

Just being curious: any specific reason you don't want to use JUCE for the rest of your app?


#7

 

I don't think this will work, as the C++ end of JUCE will still keep calling the Java activity for doing graphics, audio, etc. Subclassing it won't remove that dependency.


#8

Hi Timur,

I managed to make it work only with the following subroutine in Java "land":

 

  • getAndroidSDKVersion()
  • audioManagerGetProperty()
  • setCurrentThreadPriority()
  • hasSystemFeature()
  • createNewThread() ¬†¬†¬†¬†¬†¬†¬†


I'm still working with a Java project but I think rewriting this subroutine in C# should be feasible.
Basically what I've done is making a "empty method" for the methods than I don't want to be declared in JAVA (those methods will never be called from C++) :


#define CREATE_JNI_METHOD_EMPTY(methodID, stringName, params)    methodID = nullptr;   


Then I checked during initialization of JNIClassBase in order to make sure that I don't call the "undefined methods".
This last piece is still an hack and I need to refine it but it works.
If You are interested and You accept pull request I may make one.
Alessandro


#9

I just wanted to chime in and say I'm doing a similar thing with Unity. Basically I took the Introjucer generated JuceActivity code, renamed it to JuceJavaFunctionalityProvider, removed the inherirance, removed the Activity overrides, mocked the Activity overrides required by JNI, and passed in the actual activity in the constructor, so that public activity code could be called (instead of using 'this').

As for the "massive refactoring" I would disagree about the amount of work required. You simply need to split the code into GUI stuff, and non-GUI stuff, put the GUI related JNI code behind a flag (so that it doesn't fail on the asserts when initializing), and then have the JuceActivity own a JuceJavaFunctionalityProvider (or whatever you want to call it), and enforce registering a JuceJavaFunctionalityProvider if you are not using the GUI stuff.

Currently I'm looking into scripting the activity creation, and applying a diff to the file (which will need to be updated when there are changes) as part of our build process. Would really like it if I didn't have to do this!  

 


#10

Isn't that already the case..? e.g. all the JNI calls to do GUI stuff are in the juce gui modules. If you don't include those modules, then it won't use those java classes (?)


#11

I'm referring to the JNI class initialization, which is in juce_Android_JNIHelpers, and IIRC everything is initialized in the android initialization code no matter what modules you have enabled. This will fail on asserts if those Java classes/methods aren't available.

However, I am compiling the GUI stuff, because I found that to be required to get AudioUnits working (even when not using their GUIs, not sure if this is still the case), and I wanted to keep my configurations the same across platforms for simplicity's sake.


#12

At the end I came up with a mixed solution:

  • from one side I used SaBer suggestion (Thank You!) to move all the specific Java code on a class separated from the main Activity (I discovered that in Xamarin You can mix-in Java and use reflection... pretty cool)
  • from the other side, in order to keep at the minimum the code in the "special" java class, I kept using my first proposed solution (that basically substitute the each method call from C++ in java land during the initialization process with methodToBeCalled=nullptr) which basically allow You to keep only the class declaration in Java without all the methods.

I'm not particularly happy with the solution but it works.
Alessandro