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", 
  "(Landroid/content/Context;)V", 
  activity.object());

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
Disadvantages

  • 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!

3 comments:

  1. Hi,I have one query regarding using JNI in android. Currently i am working on hybrid i.e Qt/QML application.I have separate .java file under android-sources folder.I am calling one static method in java class from c++. In that static method i’m setting new content using m_instance.setContentView(R.layout.main). As i am running this on UI thread, it perfectly displays content from mail.xml. But after completing my task i am not able to unload this view and go back to Qml view.So my question is that, can i use UI loaded in native activity as well as in QML as per my need or am i doing something wrong??

    ReplyDelete
  2. It is really a great work and the way in which u r sharing the knowledge is excellent.
    Thanks for helping me to understand basic concepts. As a beginner in android programming your post help me a lot.Thanks for your informative article.
    Android Training in velachery | Android Training institute in chennai

    ReplyDelete
  3. This is a wonderful article, Given so much info in it, These type of articles keeps the users interest in the website, and keep on sharing more ... good luck.
    Android Training in chennai | Android Training

    ReplyDelete