Android SWIG example

This example shows how to construct a link between the Android Java implementation and cross-platform Kanzi code running on Android’s native side. Building a large scale JNI bridge manually can be time consuming, error prone, and hard to maintain. Here you can find instructions on how to expose your native Kanzi implementation on the Java side using SWIG.

You can find the example in <KanziWorkspace>/Examples/Android_SWIG.

SWIG

SWIG is a tool you can use to generate JNI code and Java wrappers for native-side C++ implementation. Using generated Java files and polymorphism you can implement cross-platform parts on the native side and Android specific parts in Java. See SWIG.

Note: Modify SWIG_PATH in <KanziWorkspace>/Examples/Android_SWIG/Application/configs/platforms/android/swig/run_swig.bat and run_swig.sh to point to your SWIG installation path.

Using polymorphism to connect the native code and Java wrappers

SWIG can generate Java wrappers for classes implemented in C++. To connect Java to the native Kanzi code, you have to implement a C++ interface. To implement part of the logic in Java you can override inherit the generated wrapper class and override virtual functions.

A common approach is to have an application wide singleton visible on the native and Java side. It typically has getters and setters for data providers and accessors for system features.

shared_data.hpp:

#ifndef SHARED_DATA 
#define SHARED_DATA

class SharedData
{
	public: 
		// Cross-platform accessor for shared data. 
		static SharedData& get(); 
		// ... 
	protected: 
		SharedData(); 
		// ... 
}; 

#endif

shared_data.cpp:

#include "shared_data.hpp" 

SharedData& SharedData::get()
{ 
	static SharedData sharedData; 
	return sharedData; 
} 

SharedData::SharedData() 
{ 
} 
// ...

The above C++ class is a singleton you can use to share objects between the native implementation and Java. To expose it on the Java side you have to setup SWIG for the Kanzi project.

Setting up the SWIG for the project

To enable SWIG for a Kanzi project:

  1. Create an instruction file for the SWIG. You can find the one used in this example in <KanziWorkspace>/Examples/Android_SWIG/Application/configs/swig/Android_SWIG.i.
    // These instructions expose the SharedData singleton.
    // The first line declares the module (Android_SWIG is the application name) and set the options.
    // To enable polymorphism, you need the “directors” flag.
    %module(directors="1") Android_SWIG
    // You need %feature("director") <ClassName>; for each class where you want to override virtual functions.
    %feature("director") SharedDataListener;
    %feature("director") AndroidSWIGListener;
    
    // General declarations 
    typedef bool kzBool; 
    %{ 
    #define true KZ_TRUE 
    #define false KZ_FALSE 
    %} 
    
    #pragma SWIG nowarn=302 // Warns about redefinition of kzBool 
    
    // Includes for the wrapper C++ source file.
    // To generate the wrapper for a C++ file add:
    // - #include line to be copied to the generated JNI code
    // - %include instruction to generate wrappers from it
    // The paths for %include are relative to the location of the instruction file.
    %{ 
    #include <system/kzs_header.h> 
    #include <system/kzs_types.h> 
    
    #include "shared_data.hpp" 
    #include "android_swig_controller.hpp" 
    %} 
    
    %include "../../../../../../../Engine/headers/system/common/include/system/kzs_header.h" 
    %include "../../../../../../../Engine/headers/system/common/include/system/kzs_types.h" 
    
    %include "../../../../src/shared_data.hpp" 
    %include "../../../../src/android_swig_controller.hpp"
  2. Create a batch file to run the SWIG. You can find the batch file for this example in <KanziWorkspace>/Examples/Android_SWIG/Application/configs/platforms/android/swig/run_swig.bat. This batch works in most cases and is sufficient for the basic SWIG setup. However, make sure that the paths and file names are correct for your application.
    @ECHO OFF 
    SETLOCAL 
    SET SWIG_PATH=..\..\..\..\..\..\..\Engine\tools\thirdparty\swig 
    
    SET PROJECT_PATH=..\..\..\.. 
    SET SWIG_INSTRUCTION_FILE=%PROJECT_PATH%\configs\swig\Android_SWIG.i 
    
    SET WRAPPER_SOURCE_FILE=%PROJECT_PATH%\platforms\android\src\Android_SWIG_wrap.cpp 
    
    SET JAVA_SOURCES_PATH=%PROJECT_PATH%\configs\platforms\android\src\com\rightware\kanzi\Android_SWIG 
    SET PACKAGE=com.rightware.kanzi.Android_SWIG 
    
    CALL %SWIG_PATH%\swig -java -verbose -c++ -outdir %JAVA_SOURCES_PATH% -package %PACKAGE% -o %WRAPPER_SOURCE_FILE% %SWIG_INSTRUCTION_FILE% 
    ENDLOCAL
    
  3. Run the batch file to generate the JNI CPP file and the Java wrappers. Once the batch file generates these files, you can find them in the directories defined in the batch file.

Connecting Java to the native Kanzi

Now that the SharedData singleton is exposed to the Java side, you can start implementing the interfaces for the native logic and adding setters and getters for those.

In the example application, there is a class called AndroidSWIGController, which you can use to rotate the Android character and toggle lights in the scene.

Below is an example of this implementation in the android_swig_controller.hpp.

class 
AndroidSWIGController 
{ 
	public: 
		AndroidSWIGController(struct KzuProject* project); 
		
		// Set the rotation property (0...1) 
		void setRotation(kzFloat value); 
		
		// Toggle lights in the scene 
		void toggleLights(kzBool isOn); 
		
		//...

For example, the setRotation function sets a custom property type value for a node:

void AndroidSWIGController::setRotation(kzFloat value) 
{
	struct KzuPropertyType* rotationPropertyType; 
	struct KzuObjectNode* androidNode; 
	
	kzuProjectLoaderLoadPropertyType(project, ROTATION_PROPERTY, &rotationPropertyType); 
	kzuProjectLoaderLoadObjectNode(project, ANDROID_NODE, &androidNode); 
	kzuObjectNodeSetFloatProperty(androidNode, rotationPropertyType, value); 
}

Kanzi utilizes Android’s GLSurfaceView so it runs in GLThread. This means you have to call functions having Kanzi calls in GLThread. GLSurfaceView has a convenient function called queueEvent to do just that. Below is an example of this implementation in the AndroidSWIGActivity.java.

queueEvent(new Runnable() { 
	public void run() { 
		if (SharedData.get().getController() != null) SharedData.get().getController().setRotation((float)progress / 100); 
	} 
});

Connecting the native Kanzi to Java

To connect a call to Java from C++ you need to add a virtual function in the C++ interface, enable SWIG directors feature for that class, override the function in Java class, and provide the instance of that to the native side using the SharedData.

The example application uses this approach for two listeners to react to changes on the native side. The first one waits for the moment when the AndroidSWIGController is ready to be used. The second one listens for the rotation value changes to update a Java label. Both use the same approach.

Introduce a listener class:

class SharedDataListener 
{ 
	public: 
		// Constructs a SharedDataListener. 
		SharedDataListener() {}; 

		// Destructs the SharedDataListener. 
		virtual ~SharedDataListener() {}; 
		
		// This is a callback called when AndroidSWIGController is ready. 
		virtual void onControllerReady() {}; 
};

Add functions to register and unregister the listeners:

// Register a new listener 
void registerListener(SharedDataListener* listener); 

// Unregister a listener 
void unRegisterListener(SharedDataListener* listener);

Add director feature for the listener class in the SWIG instruction file:

%feature("director") SharedDataListener;

On the Java side, create and register the listener:

sharedDataListener = new SharedDataListener() { 
	// This function will be called from GL Thread
	// This shows the Android UI when the AndroidSWIGController is ready.
	// Note that as the onControllerReady is called from the GL Thread, the code affecting the Android UI cannot be called directly, but inside a runnable and using Activity’s runOnUiThread function.
	public void onControllerReady() 
	{ 
		// Register the observer to update the rotation value text view 
		androidSWIGListener = new AndroidSWIGListener() { 
			@Override 
			// This function will be called from GL Thread 
			public void onRotationChanged(final int newValue) { 
				// We need to run code affecting 
				Android UI in UI Thread AndroidSWIGActivity.this.runOnUiThread(new Runnable() { 
public void run() { 
	TextView rotationValue = (TextView) controlsView.findViewById(R.id.rotation_value); 

	if (rotationValue != null) 
		rotationValue.setText(String.valueOf(newValue)); 

} 
				}); 
			} 
		}; 
		
	SharedData.get().getController().registerListener(androidSWIGListener); 

	// We need to run code affecting Android UI in UI Thread 
	AndroidSWIGActivity.this.runOnUiThread(new Runnable() { 
		public void run() { 
			AndroidSWIGActivity.this.addContentView(controlsView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 
			} 
		}); 
	} 
}; 

SharedData.get().registerListener(sharedDataListener);

Unregister the listener on onDestroy:

SharedData.get().unRegisterListener(sharedDataListener);

See also

Deploying Kanzi applications to Android