Developing with the Kanzi Android framework (droidfw)¶
Use the Kanzi Android framework (droidfw) when you want to create an application for the Android platform, and you intend to use Android APIs and services extensively.
Kanzi Android framework (droidfw) is a framework dedicated for developing Kanzi applications for Android. It exposes the Kanzi Java API, which allows you to write application and plugin code entirely in Java or Kotlin. You do not need to write any C++ or JNI code, but you can still use native Kanzi plugins. Kanzi Android framework (droidfw) provides strong integration with the Android UI, including support for multiple simultaneous Kanzi-based Views and flexible composition of Kanzi and Android UI elements. Kanzi Android framework (droidfw) integrates with the Android Choreographer which runs the Kanzi main loop tasks in the Android UI thread. This enables you to use the Kanzi Java API from your application code without the need for dispatcher mechanisms.
Learn how to start developing Kanzi applications for Android with the Kanzi Android framework (droidfw) by completing the Tutorial: Getting started with Kanzi Android framework (droidfw).
Creating an Android application in Kanzi Studio¶
In Kanzi Studio in the New Project window, select the template for your Android application:
Android application template creates a Kanzi Studio project with a Kanzi Android framework-based application.
Android application with Java plugin template creates a Kanzi Studio project with a Kanzi Android framework-based application that contains a Kanzi Engine Java plugin.
See Creating a Java Kanzi Engine plugin using a template and Using Java and Kotlin.
When you create a project with one of these templates, Kanzi Studio creates a complete Android application project in the <ProjectName>/Application/configs/platforms/android_gradle
directory.
To learn how to build and deploy your application, see Deploying Kanzi applications to Android.
Adding the Kanzi Android framework (droidfw) to an existing Android application¶
This procedure assumes that your Android application project uses the default directory structure.
To add the Kanzi Android framework (droidfw) to an existing Android application:
Copy the contents of
<ProjectName>/Application/bin
directory of your Kanzi Studio project to theapp/src/main/assets
directory of your Android Studio project.This way you import your kzb files into an Android package.
Introduce cmake to your project. This way you can link native Kanzi Engine plugins to your application.
Add to the
app/src/main/cpp/CMakeLists.txt
file:cmake_minimum_required(VERSION 3.5.1) project(<ProjectName>) if(NOT KANZI_ENGINE_BUILD) find_package(Kanzi REQUIRED CONFIG CMAKE_FIND_ROOT_PATH_BOTH HINTS "$ENV{KANZI_HOME}/Engine/lib/cmake") endif() include(kanzi-common) add_executable(<ProjectName> # This file is only needed because cmake does not allow an executable without any sources. <ProjectName>.cpp) target_link_libraries(<ProjectName> -Wl,--whole-archive Kanzi::kzdroidfw -Wl,--no-whole-archive) target_link_libraries(<ProjectName> Kanzi::kzui Kanzi::kzcoreui Kanzi::kzjava Kanzi::kzinterop) target_link_font_engine_backends(<ProjectName>) set_target_properties(<ProjectName> PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/bin") set_target_properties(<ProjectName> PROPERTIES VS_DEBUGGER_ENVIRONMENT "${KANZI_VS_DEBUGGER_ENVIRONMENT}") install_kanzi_libs_to_output_directory() install_kzbs_to_output_directory(${CMAKE_SOURCE_DIR}/bin) install_target_to_output_directory(<ProjectName>)
Add to the
app/src/main/cpp
directory an empty<ProjectName>.cpp
file.
Copy the
getkanzi.gradle
file from the<KanziWorkspace>/Templates/Android_application_template/Application/configs/platforms/android_gradle
directory to your Android project root directory.Gradle uses the
getkanzi.gradle
file to find Kanzi Engine and Kanzi Gradle plugins.In the
build.gradle
file in the Android project root directory, get the Kanzi Gradle plugins and add Kanzi as a dependency:buildscript { apply from: 'getkanzi.gradle' repositories { google() mavenCentral() flatDir { dirs getKanziPlugins().toString() } } dependencies { classpath "com.android.tools.build:gradle:4.1.3" classpath "com.rightware.gradle:kanzi:0.7.1" }
In the
app/build.gradle
file of your Android Studio project, add the Kanzi Gradle plugin, and import the Kanzi Android framework (droidfw) and its dependencies:apply plugin: 'com.rightware.gradle.kanzi' ... android { ... defaultConfig { ... externalNativeBuild { cmake { arguments "-DKANZI_LINK_FREETYPE=ON" arguments "-DKANZI_LINK_ITYPE=OFF" } } } ... externalNativeBuild { cmake { path file('src/main/cpp/CMakeLists.txt') } } } ... kanzi { appFramework 'kanziruntime-droidfw' } dependencies { ... implementation 'com.rightware.kanzi:kzjava@aar' }
In the
app/build.gradle
file of your Android Studio project, ensure that the Java language version is set to 8 (1.8) or higher:android { ... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }
Adding a Kanzi view¶
Kanzi exposes the KanziSurfaceView
and KanziTextureView
view types derived from the Android SurfaceView
and TextureView
respectively.
KanziSurfaceView
renders directly to a surface obtained from hardware composer, which makes its performance better. But it comes with the restriction that an application can render it only behind or on the top of the rest of the views in an Activity.KanziTextureView
renders to a texture like a regular view. This allows an application to mix its content freely with Android content and you can move, transform, animate, and even make it translucent.
See Android SurfaceView vs TextureView or Creating a transparent view.
You can declare a KanziView
in the Android layout and inflate it anywhere in the same way as a regular Android view.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.rightware.kanzi.KanziSurfaceView
android:id="@+id/view1"
app:name="View1"
app:kzbPathList="project1.kzb"
app:startupPrefabUrl="kzb://project1/Prefabs/View1" />
Configuring a Kanzi view¶
These view types expose various configurations that you can assign directly from a layout.
Attribute |
Use |
Format |
Default value |
---|---|---|---|
|
Name of the view. |
string |
|
|
Comma-separated list of kzb files. When set, Kanzi automatically loads these files when the view is attached. You can also pass a cfg file that contains a list of kzb files that you want to load. |
string |
|
|
URL of the startup prefab. When set, Kanzi asynchronously loads and instantiates this prefab as a child of the view. |
string |
|
|
Color used to clear the surface. |
color |
|
|
Flag to enable the clearing of the surface. |
boolean |
|
Alternatively, you can assign these configuration options with setters after inflation.
public class Activity1 extends FragmentActivity {
@Override
protected void onCreate(Bundle) {
...
// Inflate the content from the layout.
setContentView(R.layout.activity1);
// Get the reference to the view.
KanziView view = findViewById(R.id.view1);
// Set the name.
view.setName("View1");
// Set the kzb paths
view.setKzbPathList(new String[]{"project1.kzb"});
// Set the startup prefab.
view.setStartupPrefabUrl("kzb://project1/Prefabs/View1");
class CarMenu : FragmentActivity() {
override fun onCreate(icicle: Bundle?) {
...
// Inflate the content from the layout.
setContentView(R.layout.activity1)
// Get the reference to the view.
val view: KanziView = findViewById(R.id.view1)
// Set the name.
view.setName("View1")
// Set the kzb paths
view.setKzbPathList(arrayOf("project1.kzb"));
// Set the startup prefab.
view.setStartupPrefabUrl("kzb://project1/Prefabs/View1")
It is also possible to explicitly load kzb files during the activity onCreate
function.
@Override
protected void onCreate(Bundle) {
...
// Acquire a reference to the KanziRuntime.
mRuntimeRef = KanziRuntime.acquire(this);
// Load the kzb.
try {
mRuntimeRef.get().loadKzb("project1.kzb");
} catch (FileNotFoundException e) {
Log.e(e.getMessage());
return;
}
// Inflate the content from the layout.
setContentView(R.layout.activity1);
override fun onCreate(icicle: Bundle?) {
...
// Acquire a reference to the KanziRuntime.
mRuntimeRef = KanziRuntime.acquire(this)
// Load the kzb.
try {
mRuntimeRef.get().loadKzb("project1.kzb")
} catch (FileNotFoundException e) {
Log.e(e.getMessage())
return
}
// Inflate the content from the layout.
setContentView(R.layout.activity1)
Accessing the Kanzi Java API¶
When the node tree of your Kanzi view is initialized, Kanzi checks whether the root node of that node tree is a Screen
node. If the root node is not a Screen
node, Kanzi creates a Screen
node and attaches the node tree to that Screen
node. Kanzi uses the Screen
node to handle input and focus in the Kanzi view.
In a Kanzi view, to get the child of the Screen
node, use the getRoot
method. From this Root node, you have access to the node tree with Kanzi Java API.
// Get root.
Node root = view.getRoot();
// Look up a node using an alias.
Node button = root.lookupNode("#Button");
// Set property.
button.setProperty(ButtonConceptMetadata.ToggleStateProperty, 0);
// Add message handler.
button.addMessageHandler(ButtonConceptMetadata.ToggleStateMessage,
new Node.MessageSubscriptionFunction() {
@Override
public void handle(MessageArguments messageArguments) {
int toggleState = messageArguments.getArgument(ButtonConceptMetadata.ToggleStateProperty);
Log.i("Button State is " + toggleState);
}});
// Get root.
val root = view.getRoot();
// Look up a node using an alias.
val button = root.lookupNode("#Button");
// Set property.
button!!.setProperty(ButtonConceptMetadata.ToggleStateProperty, 0);
// Add message handler.
button.addMessageHandler(ButtonConceptMetadata.ToggleStateMessage,
Node.MessageSubscriptionFunction { messageArguments ->
val toggleState =
messageArguments.getArgument(ButtonConceptMetadata.ToggleStateProperty)
Log.i(TAG, "Button State is $toggleState")
})
Because the Kanzi Android framework (droidfw) runs in the Android UI thread, it is safe to invoke the Kanzi Java API interfaces directly from your application code. If you must use Kanzi from a different thread, you can post a task to the Android UI thread using View.post(Runnable) or similar functionality. To learn more about Kanzi Java API, see Using Java and Kotlin.
Observing state changes¶
You can implement KanziViewListener
to listen for state changes in KanziView
. Use this to inject custom initialization and un-initialization logic.
For example:
Use the
onAttachedToWindow()
callback to access domain, register Kanzi Engine Java plugins or manually setup the view's node tree.
view.addListener(new KanziViewListener() {
...
@Override
public void onAttachedToWindow(View view, Domain domain)
{
// Register a plugin.
domain.registerPlugin(new MyPlugin());
// Manually setup the node tree.
// Instantiate a node.
ObjectRef<TextBlock2D> text = TextBlock2D.create(domain, "text");
// Set as root of the view.
((KanziView) view).setRoot(text.get());
// Set properties on a node.
text.get().setProperty(TextBox2D.TextProperty, "Hello Kanzi!");
view.addListener(object : KanziViewListener {
...
override fun onAttachedToWindow(view: View, domain: Domain) {
// Register a plugin.
domain.registerPlugin(MyPlugin())
// Manually setup the node tree.
// Instantiate a node.
val text = TextBlock2D.create(domain, "text")
// Set as root of the view.
(view as KanziView).root = text.get()
// Set properties on a node.
text.get().setProperty(TextBox2D.TextProperty, "Hello Kanzi!")
Use the
onStartupPrefabLoaded()
callback to access and modify the node tree instantiated from the startup prefab that you have configured.
view.addListener(new KanziViewListener() {
...
@Override
public void onStartupPrefabLoaded(View view, Domain domain)
{
// Look up a child node starting from the root.
TextBox2D text = ((KanziView)view).getRoot().lookupNode("#Text");
// Set properties on a node.
text.setProperty(TextBox2D.TextProperty, "Hello world!");
view.addListener(object : KanziViewListener {
...
override fun onStartupPrefabLoaded(view: View, domain: Domain) {
// Look up a child node starting from the root.
val text = (view as KanziView).root.lookupNode<TextBox2D>("#Text")
// Set properties on a node.
text.setProperty(TextBox2D.TextProperty, "Hello world!")
To learn more about Kanzi Engine Java plugins, see Creating Kanzi Engine Java plugins.
Attaching Kanzi to an existing view¶
You can instantiate and attach a KanziViewAdapter
to an existing Android SurfaceView
or TextureView
.
@Override
protected void onCreate(Bundle) {
...
// Get reference to the view.
SurfaceView view = findViewById(R.id.view1);
// Instantiate an adapter and assign a view.
mAdapter = new KanziViewAdapter(view, "View1");
// Assign a startup prefab.
view.setStartupPrefabUrl("kzb://project1/Prefabs/View1");
override fun onCreate(icicle: Bundle?) {
...
// Get reference to the view.
val view = findViewById(R.id.view1)
// Instantiate an adapter and assign a view.
mAdapter = KanziViewAdapter(view, "View1");
// Assign a startup prefab.
view!!.setStartupPrefabUrl("kzb://project1/Prefabs/View1");
Using Kanzi from view-less contexts¶
You can also instantiate and use a KanziViewAdapter
without a view. For example, from a WallpaperService
.
class WallpaperEngine1 extends WallpaperService.Engine {
KanziViewAdapter mAdapter;
@Override
public void onCreate(SurfaceHolder) {
...
// Instantiate an adapter.
mAdapter = new KanziViewAdapter(getApplicationContext(), "WallpaperEngine1");
// Assign a startup prefab.
mAdapter.setStartupPrefabUrl("kzb://project1/Prefabs/Wallpaper");
private inner class WallpaperEngine1 : Engine() {
private var mAdapter: KanziViewAdapter? = null
override fun onCreate(surfaceHolder: SurfaceHolder) {
...
// Instantiate an adapter.
mAdapter = KanziViewAdapter(applicationContext, "WallpaperEngine1")
// Assign a startup prefab.
mAdapter!!.setStartupPrefabUrl("kzb://project1/Prefabs/Wallpaper")
Handling input¶
While KanziSurfaceView
and KanziTextureView
handle input events by default, an arbitrary view using KanziViewAdapter
must manually forward any input events that it wants Kanzi to process, through the handleTouchEvent()
, handlekeyEvent()
, and handleFocusChange()
interfaces.
All Kanzi views by default set focusableInTouchMode
to True
, unless otherwise set programmatically or through layout attributes. This makes them gain and keep focus when clicked on. See Android touch mode.
Handling orientation change¶
When the screen orientation changes, an Android application, by default, destroys and recreates the activity. This allows an application to use an alternate layout for portrait and landscape screens, but it also means that you lose all internal Kanzi states.
You can override this by adding the android:configChanges="orientation"
property to the AndroidManifest.xml
file. With this option, the activity is no longer destroyed on orientation changes, instead onConfigurationChanged()
is called on the activity. Forward this event manually to handleOrientationChange()
. This option avoids recreating the Kanzi controls and therefore all internal state is retained. See Android configuration changes.
Handling the Android lifecycle¶
While KanziSurfaceView
and KanziTextureView
handle window and surface events by default, an arbitrary view using KanziViewAdapter
must manually forward the window and surface events it wants Kanzi to process, through the handleAttachedToWindow()
, handleSurfaceCreated()
, handleSurfaceChanged()
, handleSurfaceDestroyed()
, and handleDetachedFromWindow()
interfaces.
Handling the user-visibility of views¶
Kanzi Android framework (droidfw) automatically and continuously tracks whether each connected view is visible to the user, and does not render the surface of hidden views.
To check whether a view is visible to the user, Kanzi Android framework (droidfw) considers:
The visibility state of the view
The visibility state of all ancestors of the view
The alpha of the view
The effectively visible portion of the view computed through
View.getGlobalVisibleRect()
An arbitrary view using KanziViewAdapter
must manually forward visibility events that it wants Kanzi to process through the handleVisibilityChanged()
interface.
Creating a transparent view¶
A KanziView
can show transparent content that allows the content behind that view to be visible.
To create a transparent view:
To enable transparency for a
KanziView
, edit theonCreate
method of the view activity:For
KanziSurfaceView
call thesetZOrderOnTop
andsetFormat
methods.@Override protected void onCreate(Bundle) { ... // Get reference to the view. KanziSurfaceView view = findViewById(R.id.view1); // Add transparency support to the view. view.setZOrderOnTop(true); view.getHolder().setFormat(PixelFormat.TRANSPARENT);
override fun onCreate(icicle: Bundle?) { ... // Get reference to the view. val view = findViewById(R.id.view1); // Add transparency support to the view. view!!.setZOrderOnTop(true); view!!.getHolder().setFormat(PixelFormat.TRANSPARENT);
For
KanziTextureView
call thesetOpaque
method.@Override protected void onCreate(Bundle) { ... // Get reference to the view. KanziTextureView view = findViewById(R.id.view1); // Add transparency support to the view. view.setOpaque(false);
override fun onCreate(icicle: Bundle?) { ... // Get reference to the view. val view = findViewById(R.id.view1); // Add transparency support to the view. view!!.setOpaque(false);
In the Android layout file, update the
KanziView
clear color to have transparency.app:clearColor="@android:color/transparent"
Edit the
application.cfg
to require a surface with alpha bits. See Surface properties.SurfaceBitsAlpha = 8
In Kanzi Studio select the node that shows the transparent content and in the Properties set the Background Brush property to < No Brush >.
Creating multiple views¶
You can activate multiple Kanzi-based views simultaneously within an app and access them across different Activities or Fragments.
Sharing resources between views¶
All views in the same app share a single KanziRuntime
along with all the loaded kzb files and resources. KanziRuntime
exposes the acquire()
interface to access the current instance or create a new instance, if it has not already been created.
Unloading Kanzi Engine¶
When there are no active views that contain Kanzi content, you can unload Kanzi Engine from the memory.
The KanziRuntime.Reference
class performs reference counting on the active KanziRuntime
instance. Each attached KanziView
automatically keeps a Reference
to KanziRuntime
, which is closed only when the view gets detached.
When all the Reference
instances have been closed the KanziRuntime
is unloaded.
If the KanziRuntime
has been unloaded, the next call to acquire()
initializes a new instance of KanziRuntime
, requiring the reload of all kzb files and resources.
Using Kanzi Engine Java plugins¶
To learn about creating Kanzi Engine Java plugins, see Creating Kanzi Engine Java plugins.
To use a plugin in your Android application, either:
Load the plugin during the initialization of an Activity.
@Override protected void onCreate(Bundle icicle) { ... KanziRuntime.Reference runtimeRef = KanziRuntime.acquire(this); runtimeRef.get().getDomain().registerPlugin(new MyPlugin()); ... }
override fun onCreate(icicle: Bundle) { ... val runtimeRef = KanziRuntime.acquire(this) runtimeRef.get().domain.registerPlugin(new MyPlugin()); ... }
Load the plugin when your application attaches a Kanzi view that uses the plugin.
view.addListener(new KanziViewListener() { ... @Override public void onAttachedToWindow(View view, Domain domain) { domain.registerPlugin(new MyPlugin()); ... } ... }
view.addListener(object : KanziViewListener { ... override fun onAttachedToWindow(view: View, domain: Domain) { domain.registerPlugin(MyPlugin()) ... } ... }
Using Kanzi Studio generated plugins¶
Kanzi Studio automatically generates plugins for some features, such as Code Behind, and adds the registration for these plugins to the KanziGenerated.java
.
To use these plugins, during the initialization of your activities or the attachment of your Kanzi views, call KanziGenerated.registerPlugins(domain)
. This is similar to the registration of individual plugins in Using Kanzi Engine Java plugins.
Using Kanzi Engine C++ plugins¶
To use a Kanzi Engine C++ plugin:
Export the Kanzi Engine plugin:
In Kanzi Studio open the project that contains the plugin code.
For example, open
<KanziWorkspace>/Tutorials/Data sources/Completed/Tool_project/XML data source.kzproj
.In the Library > Build Configurations select the plugin and in the Properties set:
CPU Architecture to match the architecture of your device
Build Profile to the configuration that you want to use
Select File > Export > Build Android Package.
Kanzi Studio builds the Kanzi Engine plugin in
<ProjectName>/Application/output/cmake/<PluginProjectName>/cmake/release/arm64-v8a/lib<PluginProjectName>.so
.For example, if you use the XML data source plugin, Kanzi Studio builds the plugin in
<KanziWorkspace>/Tutorials/Data sources/Completed/Application/output/cmake/XML_data_source/cmake/release/arm64-v8a/libXML_data_source.so
.
Add the built Kanzi Engine plugin to an application:
In Kanzi Studio select File > Close All projects and open or create a Kanzi Studio project using the Android application template.
Android application template creates a Kanzi Studio project with a Kanzi Android framework-based application.
In the Library right-click Kanzi Engine Plugin, select Import Kanzi Engine Plugin, and select the dll of the built plugin.
For example, select the
<KanziWorkspace>/Tutorials/Data sources/Completed/Application/lib/Win64/GL_vs2019_Release_DLL/XML_data_source.dll
plugin.This way you add Kanzi Engine plugin to a Kanzi Studio project.
In Windows Explorer in the project to which you want to add this Kanzi Engine plugin, create this directory structure
<ProjectName>/Application/lib/Android/release/arm64-v8a
.Copy the Kanzi Engine so plugin file from
<ProjectName>/Application/output/cmake/<PluginProjectName>/cmake/release/arm64-v8a/lib<PluginProjectName>.so
to the<ProjectName>/Application/lib/Android/release/arm64-v8a
directory.For example, copy the
<KanziWorkspace>/Tutorials/Data sources/Completed/Application/output/cmake/XML_data_source/cmake/release/arm64-v8a/libXML_data_source.so
file.Edit the
<ProjectName>/Application/CMakeLists.txt
file to add the Kanzi Engine plugin as an imported library dependency:add_library(<PluginProjectName> SHARED IMPORTED) set_target_properties(<PluginProjectName> PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/lib/Android/release/${ANDROID_ABI}/lib<PluginProjectName>.so") target_link_libraries(<ProjectName> <PluginProjectName>)
For example, to add the XML data source plugin, use:
add_library(XML_data_source SHARED IMPORTED) set_target_properties(XML_data_source PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/lib/Android/release/${ANDROID_ABI}/libXML_data_source.so") target_link_libraries(<ProjectName> XML_data_source)
This way you add the built native Kanzi Engine plugin to your Android application.
In Kanzi Studio select File > Export > Build Android Package.
Kanzi Studio builds and deploys your application to an Android device. When application starts running on your Android device, in the logcat you can see when the application loads the plugin.
For example, if you use the XML data source plugin, the logcat shows:
I/Kanzi: [info:generic] Loading plugin 'libXML_data_source.so'
Use the plugin in the Kanzi Android framework (droidfw) application.
For example, to learn how to use a Kanzi Engine data source plugin, see Using a data source.
Loading kzb files from a custom location¶
By default, Kanzi reads kzb files from the assets
directory of an APK.
To set a different location, call KanziRuntime.setResourceDirectory(String path)
.
After this call, Kanzi reads kzb files from the specified location across all views of the application.
For example, to load kzb files from the external storage directory:
mRuntimeRef.get().setResourceDirectory(Environment.getExternalStorageDirectory().toPath());
To revert to the default location, call the interface with an empty string.