Sviluppo di applicazioni Android con QtQuick

Matteo Baracani

bara@develer.com

Chi sono

Perché Qt?

  • "Facilmente" portabile
  • Possibilità di riutilizzare librerie di applicazioni desktop
  • Non utilizzare "troppo" linguaggi nativi della piattaforma

Argomenti del talk

  • Configurazione dell'ambiente
  • Creazione di interfacce scalabili
  • Stile dell'applicazione
  • Interfacciamento Java/C++
  • Creazione di servizi
  • Comunicazione tra servizi e l'activity principale

Configurazione dell'ambiente

Cosa serve

  • Qt >= 5.4 
  • Android SDK Tools o Android Studio
    • Android SDK platform >= 15
    • Intel x86 Emulator Accelerator
  • Android NDK
  • JDK v6 o successiva

Configurare un progetto

  • ​Creazione progetto Qt
  • Creazione template del progetto Android
  • Subclass delle classi QtActivity, QtService..

impostazioni progetto android

  • ​Impostare firma package
  • Impostare SDK da usare
  • Creare template del progetto
    • Modifica manifest (da GUI o come plain text)
    • Creazione subclass di Activity/Service/BroadcastReceiver

Subclass activity

package qtday17;

import org.qtproject.qt5.android.bindings.QtActivity;

public class MyActivity extends QtActivity {
}
  1. Aggiungere nuova classe Activity




     
  2. Modificare Manifest
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
                  android:name="qtday17.MyActivity"
                  android:label="-- %%INSERT_APP_NAME%% --"
                  android:screenOrientation="unspecified"
                  android:launchMode="singleTop">

Stile dell'applicazione

Custom style

  1. Modificare il file .pro

     
  2. Aggiungere la scelta dello stile nel main()


     
QT += quickcontrols2
#include <QQuickStyle>

// ...

int main(int argc, char *argv[]) {

    // ...
    QGuiApplication app(argc, argv);

    QQuickStyle::setStyle("Material");

    // ...

}

Customize material design

  • QML
  • Variabili d'ambiente

Possibilità per la personalizzare varie parti dello stile (per esempio background, foreground, theme)

Esistono shades e colori Material predefiniti

Material.background: Material.color(Material.Red, Material.Shade900)

Creazione dell'interfaccia

Testo

Qt fornisce un'unità di misura scalabile, font.pointSize, che però non tiene conto della densità di pixel del display.

Componenti

Non esiste un'unità di misura scalabile. 

Scalabilità (problemi)

Immagini

Android associa risorse diverse in base alla risoluzione dello schermo

Con Qt possiamo utilizzare SVG

Scalabilità (soluzione 1)

namespace {
    const double REFERENCE_DPI = 150;
}

Units::Units(QObject *parent) :
    QObject(parent)
{
    // due to QTBUG-35701 we cannot use phisycaldotsperinch * devicepixelratio on android
#ifdef Q_OS_ANDROID
    QAndroidJniObject qtActivity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", 
        "activity", "()Landroid/app/Activity;");
    QAndroidJniObject resources = qtActivity.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
    QAndroidJniObject displayMetrics = resources.callObjectMethod("getDisplayMetrics",
        "()Landroid/util/DisplayMetrics;");
    int dpi = displayMetrics.getField<int>("densityDpi");
#else
    auto screen = qApp->primaryScreen();
    int dpi = screen->logicalDotsPerInch() * qApp->devicePixelRatio();
#endif

    scale_ratio = dpi / REFERENCE_DPI;
}

int Units::dp(int pixels)
{
    return ceil(pixels * scale_ratio);
}

Scalabilità (soluzione 2)

int main(int argc, char *argv[]) {

    ....

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

    ....
}
  • Introdotto in Qt 5.6
  • Supporto cross-platform per l'high-DPI scaling
  • Consente ad applicazioni scritte per schermi low-DPI di girare, senza cambiamenti, su schermi high-DPI

Scalabilità (soluzione 3)

Applicazione sviluppata per monitor obiettivo, interfaccia pixel perfet

Problema: su device dimensioni di schermo diverse lo spazio potrebbe non essere occupato correttamente

Scalabilità (soluzione 3)

#define REFERENCE_WIDTH 1280
#define REFERENCE_HEIGHT 800

double calculateRatio() {
    auto qtActivity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative",
                                                                "activity",
                                                                "()Landroid/app/Activity;");
    auto windowManager = qtActivity.callObjectMethod("getWindowManager",
                                                     "()Landroid/view/WindowManager;");
    auto defaultDisplay = windowManager.callObjectMethod("getDefaultDisplay",
                                                         "()Landroid/view/Display;");
    auto point("android/graphics/Point");
    defaultDisplay.callMethod<void>("getRealSize", "(Landroid/graphics/Point;)V", point.object());
    auto width = point.getField<int>("x");
    auto height = point.getField<int>("y");

    return qMin((float)width / REFERENCE_WIDTH, (float)height / REFERENCE_HEIGHT);
}


int main(int argc, char *argv[]) {
    .....

    qputenv("QT_SCALE_FACTOR", QString::number(calculateRatio()).toLocal8Bit());
    QGuiApplication app(argc, argv);

    .....
}

Integrazione

Java - C++

Cosa serve

  • Nel file di .pro  alla configurazione di Qt aggiungere il modulo androidextras
  • Includere   <QAndroidJniObject>
  • Includere   <QAndroidJniEnvironment>

JNI: framework che permette l'interoperabilità Java - codice nativo

QAndroidJniObject: fornisce le API per chiamare codice Java da C++

QAndroidJniEnvironment: fornisce l'accesso al JNI Environment

Chiamare una funzione Java da C++

bool allow;
QtAndroid::androidActivity().callMethod<void>("allowLockScreen", "(Z)V", allow);

Java

C++

public void allowLockScreen(final boolean allow) {

    runOnUiThread(new Runnable() {

        @Override
        public void run() {
            if (allow)
                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
            else
                getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        }
    });
}

Creazione oggetti Java in C++

public class PrintInformation {
  ...
  public PrintInformation(String value) {
    ...
  }
}

public class MyActivity extends QtActivity {
  
  public static void setColorInfo(PrintInformation print_information) {
    ...
  }
}
auto str = QAndroidJniObject::fromString(QString("hello"));
QAndroidJniObject value("org/qt/PrintInformation", "(Ljava/lang/String;)V",
  str.object<jstring>());
QAndroidJniObject::callStaticMethod<void>("org/qt/MyActivity",
  "setColorInfo", "(Lorg/qt/PrintInformation;)V", value.object());

Implementazione funzioni Java native in C++ (1)

public class MyActivity extends QtActivity {
  private static native void printerFound(String name, String mac, int type, int index);
}
class ConfigurationManager {

  static void printerFound(JNIEnv *env, jobject thiz, jstring name, 
    jstring mac, jint printer_type, jint index) {
    ...
  }
}

Java

C++

Implementazione funzioni Java native in C++ (2)

static JNINativeMethod methods[] {{"printerFound",
  "(Ljava/lang/String;Ljava/lang/String;II)V",
  (void*)ConfigurationManager::printerFound}};

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
{
    JNIEnv* env; // get the JNIEnv pointer.
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
        return JNI_ERR;
    // search for Java class which declares the native methods
    jclass javaClass = env->FindClass("org/qt/MyActivity");
    if (!javaClass)
        return JNI_ERR;

    // register our native methods
    if (env->RegisterNatives(javaClass, methods,
                             sizeof(methods) / sizeof(methods[0])) < 0) {
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

Qt & Servizi android

creazione del service

  • Togliere il commento alla parte relativa ai service nel manifest
  • Settare l'attributo "android:process" del tag service
  • Settare il valore del mata-data android.app.arguments  (opzionale)
  • Aggiungere
<meta-data android:name="android.app.background_running" android:value="true"/>

unleash the service

package qtday17;

import android.content.Context;
import android.content.Intent;

import org.qtproject.qt5.android.bindings.QtService;

public class MyService extends QtService {
    public static void startMyService(Context ctx) {
        ctx.startService(new Intent(ctx, MyService.class));
}
  • Subclass QtService (Java)
  • Definire chiamata al metodo di start
QAndroidJniObject::callStaticMethod<void>("com/prova/MyService", "startMyService",
                                          "(Landroid/content/Context;)V",
                                          QtAndroid::androidActivity().object());

make them talk

QTremoteobjects

  • Modulo IPC sviluppato per Qt
  • Permette comunicazione intraprocesso usando le funzionalità di Qt (signal/slot/property)
  • Uno slot chiamato su un Replica viene propagato al vero oggetto
  • Update su un Source vengono propagati a tutti i Replica

code time: SERVER side (1)

QT += remoteobjects
  • Creare un pro per il server
  • Aggiungere il modulo al progetto

     
  • Creazione file .rep


     
  • Aggiungere il file .rep al file .pro
class SimpleCounter {
    PROP(int count=0);
}
REPC_SOURCE += simplecounter.rep

code time: server side (2)

#include <QCoreApplication>

#include "simplecounter.h"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    SimpleCounter simple_counter;
    QRemoteObjectHost n(QUrl(QStringLiteral("local:replica")));
    n.enableRemoting(&simple_counter);

    return app.exec();
}

code time: server side (3)

#include "simplecounter.h"

#include <QTimer>

SimpleCounter::SimpleCounter(QObject *parent) :
    SimpleCounterSimpleSource(parent)
{
    timer = new QTimer(this);
    timer->setInterval(1000);
    connect(timer, &QTimer::timeout, this, &SimpleCounter::timeout);

    timer->start();
}

void SimpleCounter::timeout()
{
    setCount(count() + 1);
}

code time: Client side (1)

QT += remoteobjects
  • Aggiungere il modulo al progetto

     
  • Aggiungere .so server al progetto del client

     
  • Aggiungere il file .rep al file .pro
ANDROID_EXTRA_LIBS = $$OUT_PWD/libserver.so
REPC_REPLICA += simplecounter.rep

code time: Client side (2)

QRemoteObjectNode repNode;
QSharedPointer<SimpleCounterReplica> ptr;
repNode.connectToNode(QUrl(QStringLiteral("local:replica")));
ptr = QSharedPointer<SimpleCounterReplica>(repNode.acquire<SimpleCounterReplica>());
bool res = ptr->waitForSource();

QObject::connect(ptr.data(), &SimpleCounterReplica::countChanged, [](int count) {
    qDebug() << "Count changed" << count;
});

Fine

Domande?

Qt su Android

By Matteo Baracani

Qt su Android

  • 622