To those of you who were at the Contributor’s Summit, I said that I would write a few more technical blogs about Qt 5 for Android in the near future. This first blog accompanies my session at the CS quite well, as it will give some insight into how the different pieces in the Qt 5 for Android port fit together.
When developing platform ports of Qt, we’re doing our best to hide the piping, so that a developer using Qt can get as far as possible without even considering the target platforms of their application.
However, there are times when it’s useful to understand more about the inner workings of Qt, either if you want to contribute code to Qt itself, when there’s an error and you don’t understand why, or if your application requires a degree of platform integration which we have not yet been able to facilitate. In this blog, I’ll focus on the Qt for Android port in particular.
It is outside the scope of this blog to explain how the Qt Platform Abstraction API (QPA) works. It is also outside the scope to give a detailed introduction to Android development, and the life span of Android applications. It should hopefully be both understandable and useful without any in-depth knowledge of either technology, however, and if you wish to know more, there is documentation and blogs on both topics to be found on the Internet.
Suffice it to say: Qt abstracts away the windowing system in an API called “QPA”, so that platform-dependent code can be isolated to a plugin. This plugin will handle everything from putting graphics on the screen to propagating events from the windowing system and into your Qt event loop. Android is one such platform, but different in many respects from the other platforms supported by Qt, as it is inherently a Java-based platform. Android applications are Java applications, running on a virtual machine called “Dalvik”. This poses some extra challenges when integrating with the C++ framework Qt.
Which brings us to the “Java Native Interface” (or JNI for short). This is the communication layer between Java and C, and is used by Qt when passing data back and forth between the operating system and the platform plugin. In Qt, we are working on some convenience APIs around the JNI APIs to help you combine Qt code with JNI code if you would like your code to interoperate with Java.
At the very top of levels, a Qt for Android application consists of two parts:
- The Qt application:
- An Android application launcher:
This is the cross-platform code and resources that you, as the app developer, manage yourself, and which are summarized by your qmake .pro file.
This is generated for you by Qt Creator the first time you connect your project to a Qt for Android Kit.
The latter consists of the following:
- A subclass of android.app.Application:
- A subclass of android.app.Activity:
- Interfaces for connecting to the Ministro service:
- Other meta-data
This maintains bindings to Qt using Java’s Reflection API.
This is the application entry point. In Android, an application can consist of several activities, responding to different so-called intents. However, by default a Qt application will only consist of a single activity which can be launched from the Android application grid. Several system events are delivered to the main activity of your application and are then propagated to Qt by this subclass. The QtActivity class also handles loading the native binaries based on the selected deployment method, and launching the application’s main() function.
Ministro is a deployment mechanism where the Qt libraries are downloaded and maintained by an external service on the target device, and serves to minimize the amount of space used by each Qt application. The interfaces are used to communicate with the service in the case where this deployment mechanism is selected.
This is the heart of the application meta-data on Android. At some point you will have to edit this to set things such as your application name, package name, version code, icon, permissions, etc. and Qt Creator provides you with a convenient editor for the most common parts of the manifest. In Qt Creator 2.8 and up, you can simply click on the AndroidManifest.xml to open the editor. If you need to customize beyond the options in this editor, click on the XML Source tab in the top right corner.
There are a set of extra files used to store additional information about your application. This is e.g. information on the selected deployment mechanism in Qt Creator, an Android layout used for showing a splash screen, translations of Ministro UI text, etc.
When Qt Creator sets up your project for a Qt 5 for Android Kit, it will copy these files from the directory $QT/src/android/java. It will then make modifications to the files based on your deployment settings, your target Android version, etc. When developing a regular Qt application, you don’t have to modify any of this yourself, with the exception of the AndroidManifest.xml, and even that can wait until you actually want to deploy your application to users or market places. At that point you will probably want to set some application specific data, such as the name and icon.
The final piece of the puzzle is the code which resides in Qt. It consists of the following:
- QtActivityDelegate.java and other Java files:
- The platform plugins:
This will set up the UI for your app (just a single SurfaceView which Qt can draw into), and take care of the communication back and forth between the Android OS and QPA. When your application’s activity receives events from the operating system, it will call functions in the QtActivityDelegate and these will propagate into Qt.
Yes, that’s a plural. There are two platform plugins in Qt for Android which cater to two different use cases. The first is a raster-based plugin, which is used for QtWidget-based apps which do not depend on OpenGL. This mimics some of the behavior in a traditional desktop windowing system which allows multiple top-level, non-full-screen windows that can be stacked. The other is GL-based, and used e.g. for Qt Quick 2 applications, which depend on OpenGL ES 2. This has limited support for multiple top-levels (they will all become full screen) so it does not suit the traditional desktop application UI equally well.
When it’s started, the Qt for Android application will be just a regular Java application. The entry point will be in QtActivity.java which can be found under android/src/… in your project directory. This code will first check your project meta-data, which is contained in android/AndroidManifest.xml and android/res/values/libs.xml, to see which deployment mechanism has been selected. Qt Creator will update the meta-data based on your project settings. For more insight into the different values here, you can try selecting different deployment mechanisms in Qt Creator and running your application, subsequently browsing the meta-data to see what has changed.
There are three different deployment mechanisms supported, each of which has a slightly different start-up code path:
- Bundle Qt libraries in APK:
- Use Ministro service to install Qt:
- Deploy local Qt libraries to temporary directory:
At start-up, the application will have to copy some of the bundled files into a cache. This is necessary due to some limitations in Qt, and is something which will be improved in Qts to come.
If the Ministro service has not yet been installed on the device, your application will ask its user to install it, redirecting them to the market place. Once the service is available, your application will query it for the required Qt libraries and files, downloading anything that’s missing.
The necessary files have already been deployed to a readable directory structure on the device’s internal storage by Qt Creator before launching the app, and no further preparation is necessary.
Once the preparation is done, the application will first explicitly load Qt (and other) libraries listed in android/res/values/libs.xml in the order given. When that is done, it will load the platform plugin, which serves as both the QPA plugin and the communication layer between Qt and Java. This plugin will first register a set of native callbacks that are called from Java as reactions to Android events and it will register its QPA interfaces. Once this is done, the application will load the final library: The one produced as your application binary. Regular “app” templates in qmake produce shared libraries when built for Android, since the application entry point is actually in Java. This shared library is loaded, and its main() function is then called on a new thread, so that the Android and Qt event loops are running in parallel.
At this point, your application takes over and can run in its thread with no regard to the origin of the input events it is getting.
Just a couple of notes at the end: Bug reports are very welcome, but please file them in the official bug report form, as reports that are entered in the comment field of this blog are very hard to track. In the wiki, there is also a list of devices on which Qt 5 has been tested and confirmed to work. If you are working on a device which is not already in the list, and if you have a moment to spare, we would be very thankful if you could add the device to the list.
Beyond that: If you have questions about Qt for Android, one way to get answers is to find us on IRC. The ones of us in Digia are usually on #necessitas on Freenode during Norwegian business hours. Thanks for reading!