Wie man Qt mit Android-Java-Code verbindet

Qt QML-Entwicklung
2020-11-18
7 Minuten
Wie man Qt mit Android-Java-Code verbindet

Die Softwareentwicklung ist anspruchsvoll und erfordert oft die Verwendung von Programmiersprachen oder Lösungen, die nicht Teil unseres Haupt-Technologie-Stacks sind. Dafür gibt es viele Gründe. Manchmal müssen wir bereits implementierte Lösungen verwenden, da es zu zeitaufwändig wäre, sie neu zu erstellen. Es kann auch Fälle geben, in denen wir unsere Haupttechnologie aus technischen oder finanziellen Gründen nicht verwenden können. In solchen Situationen kann die Möglichkeit, eine andere Programmiersprache zu verwenden, von Vorteil sein. Dies macht unsere Anwendungen flexibler.

Qt und QML bieten Lösungen für fast jedes Problem. Es gibt jedoch einige plattformspezifische Sonderfälle, bei denen die Verwendung von nativem Code die Dinge erheblich erleichtern kann – dank der Qt-Tools, die es ermöglichen, nativen Code in Ihr Projekt einzubinden. In diesem Beitrag werfen wir einen genaueren Blick darauf, wie man Android-Java-Code mit Qt verbindet. Los geht’s!

Wenn Sie Hilfe von Experten für Ihr Projekt benötigen, schauen Sie sich unsere Dienstleistungen für die mobile Entwicklung an.

 

Qt und Java verbinden – was wird benötigt?

Um Android-Java-Code in Qt aufzurufen, benötigen wir das Modul Qt Android Extras. Es enthält Klassen, die Ihnen helfen, Anwendungen für Android zu erstellen. Dieses Modul kann jedoch nur verwendet werden, wenn wir für unser Projekt das Android-Kit ausgewählt haben.

Das Android Extras Modul enthält die Klasse QAndroidJniObject, die APIs bereitstellt, um Java-Code aus C++ aufzurufen. Genau das, wonach wir suchen.

Um dieses Modul zu verwenden, müssen wir diese Deklaration in der .pro-Datei hinzufügen:

 

QT += androidextras

Pointing where additional Java files are located is also needed. In our case, we will stick to the “android/src/package_name” format:

  android { ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android OTHER_FILES += android/src/ScytheStudio }

 

Zeit für Praxis – Qt & Java!

Lassen Sie uns ein einfaches Beispiel für eine Java-Klasse erstellen – ein komplexerer Code wird später in diesem Beitrag vorgestellt. Denken Sie daran, die .java-Datei in den Ordner ANDROID_PACKAGE_SOURCE_DIR zu legen:

 

package ScytheStudio;

public class SomeJavaClass {

    public static int counter = 0;
    public int id;

    public SomeJavaClass() {
        this.id = counter;
        System.out.println("Created SomeJavaClass object with id: " + this.id);
        counter++;
    }

    public String sayHello() {
        return "SomeJavaClass object number: " + id + " say hello :)";
    }

    public static double multiply(double a, double b) {
        return  a * b;
    }
}

Jetzt können wir etwas Qt-Code schreiben. Beginnen Sie mit dem Hinzufügen der Import-Anweisung:

 

#include <QtAndroidExtras>
After that we need to check if the program is able to recognize our Java class. To do this we can use simple if statement:
//check if program can find our java class and use it

 

if(QAndroidJniObject::isClassAvailable("ScytheStudio/SomeJavaClass")) {
    .
    .
    .
else {
    qDebug() << "SOME JAVA CLASS UNAVAIABLE!";
}

Zu diesem Zeitpunkt sollten wir Zugriff auf eine benutzerdefinierte Java-Klasse haben. Um eine Instanz dieser Klasse zu erstellen, rufen Sie den Konstruktor von QAndroidJniObject mit dem Namen der Java-Klasse als Parameter auf:

 

QAndroidJniObject someJavaObject = QAndroidJniObject("ScytheStudio/SomeJavaClass");

Die Konsolenausgabe sollte wie folgt aussehen:

 

I System.out: Created SomeJavaClass object with id: 0

Um eine Objektmethode zu verwenden, rufen wir QAndroidJniObject::callObjectMethod auf. Obwohl wir den Rückgabetyp der Funktion im Java-Code festgelegt haben, entsprechen Java-Typen nicht den C++- und Qt-Typen. Wir müssen angeben, welchen Typ diese Methode zurückgibt – deshalb ist callObjectMethod eine Template-Funktion. Wir verwenden jstring, da die sayHello-Methode ein Java-String-Objekt zurückgibt.

 

//calling object method
qDebug() << someJavaObject.callObjectMethod<jstring>("sayHello").toString();

Sie sollten die folgende Nachricht in der Konsole sehen:

 

D libQt_code_x86.so: “SomeJavaClass object number: 0 say hello :)”

Die Verwendung der statischen Funktion multiply mag etwas komplizierter erscheinen. Wie zuvor müssen wir den Rückgabetyp, den Namen der Klasse und die Methoden angeben. Der einzige Unterschied zu Methoden ohne Argumente ist die Methodensignatur in callObjectMethod, die im Format „(Argument1, Argument2, Argument3…)ReturnType“ geschrieben wird.

Für unsere Methode verwenden wir zwei Ganzzahlen als Argumente und erwarten einen ganzzahligen Ergebniswert. Die Signatur einer Ganzzahl ist das Zeichen „I“. In der Dokumentation können Sie alle JNI-Typen und ihre Signaturen finden.

Unsere Methodensignatur sieht dann so aus: (II)I. In Klammern stehen zwei „I“ für unsere Argumente und ein „I“ außerhalb der Klammern für den Rückgabewert. Wenn wir eine Funktion hätten, die char und float als Argumente nimmt und eine Zeichenkette zurückgibt, würde ihre Signatur wie folgt aussehen: „(CF)Ljava/lang/String“.

Schließlich übergeben wir die Parameterwerte ( 2 und 3 ):

 

//calling static method
qDebug() << "2 * 3 equals: " << QAndroidJniObject::callStaticMethod<jint>("ScytheStudio/SomeJavaClass",                                                                                   "multiply",                                                                              "(II)I",
2, 3);

Wir können sehen, dass die Methode wie erwartet funktioniert:

 

D libQt_code_x86.so: 2 * 3 equals: 6

 

Beispiel aus der Praxis – Verwendung von androidextras

Stellen Sie sich vor, es gibt eine bestehende mobile Anwendung, die Sie pflegen. Der Kunde möchte einen einfachen Batteriesparmodus einrichten. Wir möchten den Stromverbrauch bei niedrigem Batteriestand reduzieren, indem wir beispielsweise das App-Theme auf dunkel stellen.

Beginnen wir mit der Erstellung der Klasse CustomBatteryManager. Diese Klasse prüft den aktuellen Batteriestand jede Sekunde und gibt ein entsprechendes Signal aus, wenn er sich ändert.

 

CustomBatteryManager.h

 

#ifndef CUSTOMBATTERYMANAGER_H
#define CUSTOMBATTERYMANAGER_H

#include <QObject>
#include <QtAndroidExtras>
#include <QTimer>

class CustomBatteryManager : public QObject
{
    Q_OBJECT

public:
    explicit CustomBatteryManager(QObject *parent = nullptr);

    Q_PROPERTY(int batteryLevel READ batteryLevel WRITE setBatteryLevel NOTIFY batteryLevelChanged)

    int batteryLevel() const;
    void checkBatteryLevel();

public slots:
    void setBatteryLevel(int batteryLevel);

signals:

    void batteryLevelChanged(int batteryLevel);

private:
    int _batteryLevel = 0;
    QTimer *_timer;

};

#endif // CUSTOMBATTERYMANAGER_H
CustomBatteryManager.cpp

 

#include "CustomBatteryManager.h"

CustomBatteryManager::CustomBatteryManager(QObject *parent) : QObject(parent), _timer(new QTimer())
{
    connect(_timer, &QTimer::timeout, this, &CustomBatteryManager::checkBatteryLevel);
    _timer->setInterval(1000);
    _timer->start();
}

int CustomBatteryManager::batteryLevel() const
{
    return _batteryLevel;
}

void CustomBatteryManager::checkBatteryLevel()
{
    QAndroidJniObject activity = QtAndroid::androidActivity();
    QAndroidJniObject context = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;");

    QAndroidJniObject batteryManager;
    QAndroidJniObject batteryManagerServiceName = QAndroidJniObject::fromString("batterymanager");
    jint batteryManagerCapacityConstant = QAndroidJniObject::getStaticField<jint>("android/os/BatteryManager", "BATTERY_PROPERTY_CAPACITY");

    batteryManager = context.callObjectMethod("getSystemService",
                                                  "(Ljava/lang/String;)Ljava/lang/Object;",
                                                   batteryManagerServiceName.object<jstring>());


    jint actualBatteryLevel = batteryManager.callMethod<jint>("getIntProperty", "(I)I", batteryManagerCapacityConstant);
    qDebug() << "ACTUAL BATTERY LEVEL: " << _batteryLevel;
    setBatteryLevel(actualBatteryLevel);
}

void CustomBatteryManager::setBatteryLevel(int batteryLevel)
{
    if (_batteryLevel == batteryLevel) {
       return;
   }

    _batteryLevel = batteryLevel;
    emit batteryLevelChanged(_batteryLevel);
}

Schauen Sie sich den Konstruktor genauer an. Um den aktuellen Batteriestand zu überprüfen, verwenden wir einen Timer, der jede Sekunde die Funktion checkBatteryLevel aufruft.

In der checkBatteryLevel-Methode verwenden wir die Android BatteryManager-Klasse, um den Batteriestand zu ermitteln.
Zuerst müssen wir den aktuellen Kontext der Anwendung abrufen. Dazu verwenden wir QtAndroid::androidActivity(), die ein Handle zur Hauptaktivität dieser Anwendung zurückgibt, und rufen dann getApplicationContext für das Activity-Objekt auf, um den Kontext zu erhalten.

Danach wollen wir das BatteryManager-Objekt erstellen. Dazu verwenden wir die Methode getSystemService. Diese Methode nimmt den Namen eines bestimmten Dienstes als Argument.

Wir müssen also in der Android-Kontextdokumentation nach dem Namen des Dienstes suchen, an dem wir interessiert sind. Für den Batteriemanager lautet er „batterymanager“. Speichern wir diesen Wert in der Hilfsvariable batteryManagerServiceName.

Jetzt müssen Sie nur noch die bereits erwähnte Methode getSystemService mit dem richtigen Argument aufrufen.

Als Nächstes erstellen wir die jint-Variable batteryManagerCapacityConstatnt, da der aktuelle Batteriestand als statische Konstante in der Klasse Battery Manager gespeichert ist. Schließlich erhalten wir den Batteriewert mit der getIntProperty-Methode mit batteryManagerCapacityConstatn als Argument und setzen unseren _batteryLevel-Wert auf den erhaltenen Wert.

Um unseren CustomBatteryManager zu verwenden, können wir ihn in QML einbinden. Wenn Sie nicht wissen, wie das geht, können Sie einen Blick in die Qt-Dokumentation zu diesem Thema werfen.

Implementieren Sie nun eine einfache Benutzeroberfläche, um den benutzerdefinierten Batteriemanager zu testen.

 

import QtQuick.Window 2.2
import QtQuick 2.15

Window {
  id: root
  visible: true

  ListView {
    id: listView

    anchors.fill: parent

    model: 50

    delegate: Rectangle {
      id: delegateItem

      width: listView.width
      height: 50

      color: customBatteryManager.batteryLevel > 30 ? "white" : "black"

      Behavior on color {
        PropertyAnimation {
          duration: 300
        }
      }
      Text {
        id: delegateText

        anchors {
          left: parent.left
          leftMargin: 10
          verticalCenter: parent.verticalCenter
        }

        text: qsTr("element") + " : " + index

        color: customBatteryManager.batteryLevel > 30 ? "black" : "white"
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter

        Behavior on color {
          PropertyAnimation {
            duration: 300
          }
        }
      }
    }
  }
}

Zum Testen verwende ich einen Android-Emulator und ändere den Batteriestand über die Emulatoroptionen.

 

 

Zusammenfassung

Das ist das Ende dieses Tutorials. Sie wissen jetzt, dass Sie Android-Java-Code in Qt einfach verwenden können. Sie haben gelernt, wie man Objekte erstellt, Methoden aufruft und wie man sie alle am Beispiel unserer Anwendung verwendet. Dank des neu erworbenen Wissens werden Sie in der Lage sein, Ihre Projekte zu bereichern.

Wenn ihr noch wissenshungrig seid, schaut euch weitere Tutorials im Scythe-Studio Blog an und stellt sicher, dass euch unser LinkedIn Profil gefällt – so verpasst ihr keinen neuen Beitrag 🙂

 

Scythe-Studio - Qt Developer

Jakub Wincenciak Qt Developer

Brauchen Sie Qt QML-Entwicklungsdienste?

service partner

Kommen wir zur Sache: Es ist eine Herausforderung, Top-Qt-QML-Entwickler zu finden. Helfen Sie sich selbst und starten Sie die Zusammenarbeit mit Scythe Studio – echten Experten im Qt C++ Framework.

Entdecken Sie unsere Fähigkeiten!

Neueste Artikel

[ 156 ]