Monday, November 24, 2014

QML wrappers for native Android components

TL;DR It might be simpler than you think to wrap native UIs with the QtQml module, but it does require a good understanding of the underlying platform

In my previous post, I showed how you can work with platform-native classes and use the QtAndroidExtras module to create UIs from code. One of the downsides was the extra code complexity you had to manage. The "Qt way" of doing UIs is, however, by using QML, for the reasons of brevity, development speed and flexibility. I cobbled together a custom Android component module to demonstrate this (sources available on GitHub) - the 40+ thick line C++ UI example suddenly becomes a lot more readable and fun to work with.

Qt does have a solution for native-looking UIs on Android in the form of QtQuick.Controls, so my reasoning here is more to show

  • what are the advantages of custom components
  • how custom components are done
  • what are the pitfalls of custom component sets
What are the advantages? If there is no native-looking component set, it's quite clear - you simply have no alternative. If QtQuick.Controls does exist for your platform (see the case of Android), there is still a number of reasons why you might consider going custom. First, you get the full functionality and availability of the platform-native set. QtQuick.Controls gives you something that is closer to a lowest common denominator, with a lot of platform-specific components/functionality missing and occasionally some input method quirks. By skipping this, you create UIs which are not just "close-to-platform-native", but effectively *are* platform native. Second, you get the full performance. By having a wrapper, there will still be a slight penalty for loading Qt, and parsing/constructing the QML objects, but there is no compromise once the UI is instantiated. I personally also like that you can use the platform-native documentation and examples, since you're just using QML syntax, but class names, properties and general usage pattern remain the same as on the platform.

How does one go about creating a new component set? As you've seen from the first QML snippet, all we really need is the QtQml module. If you start creating with a completely custom *native* component set (ie not deriving from an existing QtQuick or QtQuick.Controls base) you get a really basic set of components to work with, namely Component, QtObject, Binding, Connections, Timer. All you native components will be actually QObject derived C++ objects, registered with the QML system, as described here. The classes themselves are still plain Qt classes, here's how my activity.h looks like:

After that, you need to register every such object with the QML engine (usually in your main.cpp) :

qmlRegisterType("OutQross.Android", 0, 1, "Activity");

With this we can start instantiating our custom objects from QML. The QtObject class/element unfortunately does not have a lot of the functionality normally coming from Item (including having a default children property), so I created an OutQrossItem helper class for this purpose, which is the base class for all my Qml elements.

Finally, let's see the gritty innards of a custom component class, an Android Button in this case

As you can see, I still use the same UI inflate paradigm as Android to actually instantiate the button (this way we also avoid the question of the order of object initialization or property settings), and you can also see how a property can we wrapped. This means that the full power of QML - bindings, signals/slots and JS are available to us as the properties are mapped to the native components. This is crucial if you are building your own component sets, as otherwise you're just making a syntax converter - and this is also the answer to how this is different to just converting QML to Android .XML UIs or vice versa.

For completeness' sake, let's mention that the .APK was 6.9MB and the memory usage of the HelloWorld app was 61510kb (about halfway between the version in my previous blog post and the "full" QtQuick experience)

There are some pitfalls as well. First, while not rocket science, it's actual work. If you have an extensive GUI widget set or library (like Android), you probably want to use or write some sort of wrapper code generator as manually creating and maintaining a full wrapper set can be daunting. Second, there might be specific UI caveats - for example, in the case of my Android example, to keep it simple, I'm not actually running on the UI thread. This means that in my particular implementation while native component and property updates happen via the bindings, they might not immediately appear on the screen. This can be resolved, but it does require a very good understanding of how the native platform works. My advice is that you shouldn't consider writing wrappers because you don't know how the platform-native UIs work - it should primarily be done to accelerate future development and prototyping.

Monday, November 17, 2014

Native UI in Qt on Android (without QtQuick.Controls)

TL;DR - Yes, you can do a fast, no-compromise native UI without QtQuick on Android, and you'll use a lot less resources than a full QtQuick app, at the cost of somewhat messy code.

Nowadays Qt for Android comes with a nice QtQuick-compatible set of native-looking Android controls in the form of QtQuick.Controls. This blog post is not about them :) Let's go a bit off the beaten path - can we create a Qt application with a graphical, performant, native UI without using QtQuick (or QWidgets)? What are the advantages and disadvantages to such an approach?

In a blog post Artem Marchenko lists a couple of approaches how a Qt based native-looking UI would be possible, but these all include QML in one form another, which is not quite what we're after in this particular case. Another interesting project is Ben Lau's QAndroidRunner which combines native UIs and QML. Here, however, we'll focus how far can you get without ever touching QML or QtQuick.

The key to using Android classes and resources is the QAndroidExtras module (it's an add-on module that has been around since Qt 5.2). The class I'm heavily relying on is QAndroidJNIObject which allows Java class/object instantiation and manipulation. Thus, the two includes we'll use to enable creating native objects are

#include <QtAndroidExtras/QAndroidJniObject>
#include <QtAndroidExtras/QtAndroid>

When it comes to Android UIs, the first stop is the Activity - on Android activities are how we interact with the user. This is effectively a window on which we put all our widgets and UI elements. Luckily, starting with Qt 5.3, we have a simple way of retrieving an activity with the QtAndroid::androidActivity function.

QAndroidJniObject activity = QtAndroid::androidActivity();

Let's get to the meat of the matter - we'll need to create native layout and widget objects and set their parameters from Qt. Here's how that looks like

QAndroidJniObject layout("android/widget/RelativeLayout", 

The first parameter is the Java class name, the second the Java method signature (see here for a few more examples, "javap -s" is your friend), and the third parameter is the actual object used as the parameter (as activity is a QAndroidJniObject, we need to use the object() method). With the call above we create a Layout and assign it to the activity.

We can also call methods of our objects:

button.callMethod<void>("setTextColor", "(I)V", -1); // Color.WHITE

Putting all these together, we can create a basic, but full-fledged Android application:

...resulting in...

This "Hello world" code is clearly quite a bit more complicated than it's QML counterpart:

The complexity downside is quite obvious - it's hard to beat QML's brevity and clarity. Another, perhaps not immediately visible downside is that this particular style of C++ code is more error-prone - there is no type safety as the objects are generated dynamically, so on errors, it's easy to end up with NULL objects and segfaults.

Why would anyone use it, then? Let's take a look at resource consumption:

The APK size for the non-QML Android version of Hello World is 5,663,420 bytes, while the APK with the Controls included is 10,406,706. The difference could be even bigger, though, as the default android mkspec includes a few extra Qt modules. It should be possible to get the minimum APK size down to around 3MB. If you are using Ministro, this might not be as big of an issue, but for self-contained applications, or embedded, this can shave a few precious megabytes off of the application.

It's not just flash storage and network bandwidth we can save though - there is a memory-usage difference as well. While adb dumpsys meminfo is not a perfect way to measure memory usage, it is indicative:

40761 kB: org.qtproject.example.QtJavaHelloWorld
77531 kB: org.qtproject.example.QmlHelloWorld

While a ~35MB of minimum framework tax might not sound like much in the era of devices with several gigabytes of RAM, using a complex QML structure can inflate the difference further. On embedded and legacy devices every megabyte counts, so even this 35MB difference can help (plenty of low-end Android devices with 256-512MB of RAM).

There are other benefits to not using QtQuick controls - controls are a "lowest common denominator" approach designed to easy cross-platform development. It does not contain all UI widgets and elements, nor functionality offered by Android APIs. By using the approach as demonstrated, there is no compromise - the full UI arsenal is at our disposal, at the exact same performance as for regular Java apps.

Finally, the QtQuick controls version depends on the Qt version shipped with the application. In the non-Controls version we always get the native controls, with native styling and behavior, even if the platform release is newer than what our QtQuick.Controls version supports.

To summarize, the advantages to a Controls-less approach are:
  • Native UI performance
  • Smaller APK size (currently at least ~5MB less, potentially ~7MB)
  • Smaller memory footprint (35MB for Hello world, more as app complexity increases)
  • Full UI functionality available, regardless of Qt version
  • Styling always latest platform-native

  • Not cross-platform
  • Significantly increased code complexity, especially with more complex UIs
  • Harder to debug due to dynamic nature and lack of tooling support

It's actually possible to mitigate some of the disadvantages, so stay tuned for further posts on this topic!