
Visualisierung von Gesundheitsdaten
Da die Gesundheitsbranche immer mehr Daten generiert, liegt die Herausforderung nicht mehr nur in der Datensammlung, sondern in der Interpretation. […]
Willkommen zu einem weiteren Beitrag auf unserem Blog. Vielleicht werden einige unserer Stammleser bemerken, dass sie den Titel des heutigen Beitrags schon einmal gesehen haben. Und sie haben recht. 😉 Ende 2020 habe ich einen Beitrag über die Schnittstelle zwischen Java-Android-Code und Qt geschrieben. Sie finden diesen Eintrag hier.
Seit der Veröffentlichung dieses Blogbeitrags ist einige Zeit vergangen, und es hat sich viel geändert. Mit dem Einstieg von Qt 6 wurden viele der „Extras“-Module (einschließlich des Qt Android Extras-Moduls) standardisiert oder verschoben. Darüber hinaus empfiehlt Qt, qmake in Ihren Projekten zugunsten von CMake zu ersetzen.
Deshalb habe ich mich entschieden, diesen Blogbeitrag zu „aktualisieren“. Die Kenntnis darüber, wie man Java-Funktionen aufruft und mit benutzerdefinierten Java-Klassen kommuniziert, ist essenziell für den Aufbau von Qt-Android-Apps. Es ist auch unerlässlich, um Android-APIs und native Funktionen zu nutzen.
Wenn Sie den vorherigen Beitrag noch nicht gelesen haben, empfehle ich Ihnen dringend, dies zu tun. Die allgemeine Idee, wie man Code aus einer Java-Datei in Qt aufruft, ist gleich geblieben, daher werde ich stark auf dem vorherigen Beitrag aufbauen (das bedeutet jedoch nicht, dass er identisch ist). Wenn Sie den vorherigen Eintrag bereits gelesen haben, lassen Sie uns loslegen!
Im vorherigen Blogbeitrag haben wir Qt 5 und qmake verwendet. Zu dieser Zeit mussten wir ein Modul namens Qt Android Extras nutzen, um Android-Java-Code in Qt aufrufen zu können. Mit der Einführung von Qt 6 wurde dieses Modul jedoch entfernt.
Vielleicht haben Sie sich gedacht: „Moment mal, wie bitte, entfernt?! Wie soll das jetzt überhaupt funktionieren?!“. Keine Sorge, die Leute von der Qt Group wissen, was sie tun 😉 Die Stärke von Qt lag schon immer in seiner Plattformunabhängigkeit. Um diese Konsistenz zu wahren, wurde beschlossen, die „Extras“ zu entfernen.
Allerdings wurde beschlossen, die bestehenden Lösungen nicht zu zerstören, sondern sie nur in anderen Modulen mit ähnlicher Funktionalität zu ersetzen (wenn Sie mehr darüber lesen möchten, empfehle ich Ihnen, einen Blick auf diesen Beitrag zu werfen).
Die interessanten Klassen QAndroidJniObject und QAndroidJniEnvironment wurden durch QJniObject und QJniEnvironment ersetzt. Übrigens steht JNI für Java Native Interface. Wenn Sie sich die offizielle Dokumentation ansehen, werden Sie feststellen, dass diese Klassen genau dieselbe Funktionalität bieten. Darüber hinaus können Sie sehen, dass die Konsistenz tatsächlich verbessert wurde.
Nun, da Qt plattformübergreifend ist, warum sollte der Name „Android“ in der Klassennamensgebung auftauchen? Dies könnte irreführend sein und suggerieren, dass man Java-Code nur von Android aus aufrufen kann (obwohl wir Java-nativen Code aufrufen können, der nichts mit Android-Java-Methoden zu tun hat).
Im vorherigen Beitrag habe ich gezeigt, wie man eine einfache Java-Funktion aus einer Java-Klasse in Qt aufruft und ein Beispiel für eine Android-Anwendung präsentiert, die den Licht-/Dunkelmodus je nach Batteriestand automatisch anpasst. All dies unter Verwendung des Java Native Interface (JNI).
Diesmal werden wir eine andere Funktionalität nutzen. Wir erstellen eine Anwendung, die empfangene SMS-Nachrichten anzeigt. Bitte beachten Sie, dass diese Anwendung zu Bildungszwecken erstellt wird und keinen realen geschäftlichen Wert darstellen muss 😉.
Lassen Sie uns zunächst mit der Struktur unseres Projekts beginnen. Zuerst erstellen wir ein leeres QML-Anwendungsprojekt und fügen dann den Ordner src/com/ScytheStudio hinzu, in dem wir die Datei SmsReceiver.java erstellen. Dieses Pfadformat entspricht der Standardnomenklatur von Java. Schauen wir uns nun an, wie unser Java-Code aussehen sollte.
package com.ScytheStudio; import java.util.List; import java.util.ArrayList; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.telephony.SmsMessage; import android.util.Log; public class SmsReceiver extends BroadcastReceiver { public static List<String> messagesList = new ArrayList<String>(); @Override public void onReceive(Context context, Intent intent) { if (intent.getAction() != null && intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) { Bundle bundle = intent.getExtras(); if (bundle != null) { Object[] pdus = (Object[]) bundle.get("pdus"); if (pdus != null) { for (Object pdu : pdus) { SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pdu); String messageBody = smsMessage.getMessageBody(); messagesList.add(messageBody); } } } } } public static String getLastMessage() { if (!messagesList.isEmpty()) { return messagesList.remove(0); } else { return ""; } } }
Keine Sorge, ich erkläre bereits, was all dies macht. Zuerst definieren wir unser Paket, welches com.ScytheStudio ist. Dann kommt der Importbereich, in dem wir alle benötigten Klassen hinzufügen. Danach definieren wir unsere SmsReceiver-Java-Klasse. Diese erweitert die Funktionalität der BroadcastReceiver-Klasse, die es uns ermöglicht, auf Ereignisse zu hören. Als nächstes sehen wir die Erstellung einer statischen Liste vom Typ String – messagesList. Hier speichern wir unsere Nachrichten. Lassen Sie uns das als eine Art Puffer behandeln, der von unserer benutzerdefinierten Java-Klasse verwendet wird.
Als nächstes sehen wir die überschreibene onReceive-Funktion. Keine Sorge, wenn Sie diesen Java-Code nicht verstehen. In unserem Fall ruft diese Funktion sich selbst jedes Mal auf, wenn das Ereignis, auf das wir hören, ausgelöst wird (Empfangen einer SMS-Nachricht). Die nächsten Schritte im Code stellen sicher, dass wir keine NULL-Objekte erhalten haben und dass wir ein Ereignis vom entsprechenden Event erhalten haben. Wir extrahieren dann die Informationen, dekodieren sie und speichern sie in unserem Puffer.
In dieser Klasse gibt es zusätzlich eine Methode getLastMessage(), die die letzte Nachricht aus der Liste zurückgibt und sie dann löscht. Genau diese Funktion rufen wir aus unserem Qt-Code auf. Nachdem wir die Nachricht „abgerufen“ haben, entfernen wir sie aus unserem Puffer.
Um das Ereignis des Empfangs einer Textnachricht erfassen zu können, müssen wir zuerst unseren Receiver in gewisser Weise „registrieren“. Dazu müssen wir eine AndroidManifest.xml-Datei erstellen und sie in den Android-Ordner einfügen. Qt Creator ermöglicht es uns, diese auf einfache Weise zu generieren. Alles, was wir tun müssen, ist, zu Projects->Build->Build Steps->Build Android APK zu gehen und im Abschnitt Application->Android Customization auf die Schaltfläche Create templates zu klicken, wie es in der Grafik unten gezeigt wird:
Nachdem wir den Assistenten für die Manifestdatei durchlaufen haben, sollten wir die Standard-AndroidManifest.xml-Datei erhalten. Diese muss nun entsprechend angepasst werden. Wir müssen die entsprechenden Berechtigungen hinzufügen, damit unsere App SMS-Nachrichten lesen kann. Dazu fügen wir die folgenden Einträge hinzu:
<uses-feature android:name="android.hardware.telephony"/> <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.BROADCAST_SMS"/> <uses-permission android:name="android.permission.RECEIVE_SMS"/>
Dann geben wir im Anwendungsbereich unsere Klasse als Receiver an und das Ereignis, auf das sie hört. Das sieht wie folgt aus:
<receiver android:name="com.ScytheStudio.SmsReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver>
Es ist auch wichtig, die richtigen Berechtigungen auf Ihrem Telefon/Emulator nach der Installation der App zu setzen (gehen Sie in die Einstellungen Ihres Telefons, suchen Sie unsere App und gewähren Sie ihr die entsprechenden Berechtigungen).
Jetzt, wo der Java-Teil erledigt ist, geht es an den spannenderen Teil. 😉 Lassen Sie uns die Dateien SmsReciever.h und SmsReciever.cpp erstellen (vorerst leer) und sie zur CMakeLists.txt im qt_add_executable()-Befehl hinzufügen. In dieser Funktion fügen wir auch die Dateien SmsReciever.java und AndroidManifest.xml hinzu, damit sie Teil unseres Projekts werden. Die gesamte CMakeLists.txt-Datei sollte wie folgt aussehen:
cmake_minimum_required(VERSION 3.16) project(2023JavaQt VERSION 0.1 LANGUAGES CXX) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 6.5 REQUIRED COMPONENTS Quick) qt_standard_project_setup(REQUIRES 6.5) qt_add_executable(app2023JavaQt main.cpp SmsReceiver.h SmsReceiver.cpp android/src/com/ScytheStudio/SmsReceiver.java android/AndroidManifest.xml MANUAL_FINALIZATION ) qt_add_qml_module(app2023JavaQt URI 2023JavaQt VERSION 1.0 QML_FILES Main.qml ) # Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. # If you are developing for iOS or macOS you should consider setting an # explicit, fixed bundle identifier manually though. set_target_properties(app2023JavaQt PROPERTIES # MACOSX_BUNDLE_GUI_IDENTIFIER com.example.app2023JavaQt MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} MACOSX_BUNDLE TRUE WIN32_EXECUTABLE TRUE ) set_property(TARGET app2023JavaQt APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android ) target_link_libraries(app2023JavaQt PRIVATE Qt6::Quick ) include(GNUInstallDirs) install(TARGETS app2023JavaQt BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) qt_finalize_executable(app2023JavaQt)
Was sich von der normalen CMakeLists.txt-Datei unterscheidet, die von Qt Creator erstellt wird, sind die Zeilen 17, 37 und 52. Zu Beginn fügen wir das Flag MANUAL_FINALIZATION hinzu, das CMake anzeigt, dass der Befehl qt_add_executable() im weiteren Verlauf des Codes „modifiziert“ wird. Dann geben wir in Zeile 37 an, wo sich die Android-Dateien, die wir erstellt haben, befinden (in unserem Fall der android-Ordner). Schließlich schließen wir in Zeile 52 die Finalisierung unserer ausführbaren Datei mit dem Befehl qt_finalize_executable() ab.
Kommen wir nun zurück zu unserer SmsReciever.h-Datei. In dieser Klasse wollen wir alle Nachrichten speichern, um sie grafisch in einer Liste anzuzeigen. Wir möchten auch, dass diese Liste mit neuen Nachrichten aktualisiert wird. Erstellen wir also etwas wie das folgende:
#ifndef SMSRECEIVER_H #define SMSRECEIVER_H #include <QObject> #include <QTimer> class SmsReceiver : public QObject { Q_OBJECT Q_PROPERTY(QStringList smsMessagesList MEMBER m_smsMessagesList NOTIFY smsMessagesListChanged) public: explicit SmsReceiver(QObject *parent = nullptr); signals: void smsMessagesListChanged(); private: QStringList m_smsMessagesList; QTimer* m_timer; }; #endif // SMSRECEIVER_H
Unsere Q_PROPERTY wird vom Typ QStringList sein, da wir im Rahmen dieses Beispiels nur Textnachrichten betrachten. Mit dem MEMBER-Flag ermöglichen wir es uns, automatisch Standard-Getter- und Setter-Funktionen zu erstellen, was die Menge an Code reduziert, die wir schreiben müssen. Zusätzlich erstellen wir ein QTimer-Objekt, das wir dann verwenden, um unsere Nachrichtenliste zu „aktualisieren“. Dies liegt daran, dass es auf der Ebene der Java-Klasse nicht möglich ist, Signale wie in Qt zu senden (sehr schade).
Was die SmsReciever.cpp-Datei betrifft, sollte sie folgendermaßen aussehen:
#include "SmsReceiver.h" #include <QDebug> #include <QJniObject> SmsReceiver::SmsReceiver(QObject *parent) : QObject{parent} { connect(&m_timer, &QTimer::timeout, this, [this]() { qDebug() << "Checking new messages..."; QString lastMessage = QJniObject::callStaticMethod<jstring>("com/ScytheStudio/SmsReceiver", "getLastMessage").toString(); if (!lastMessage.isEmpty()) { m_smsMessagesList.append(lastMessage); emit smsMessagesListChanged(); } }); m_timer.setInterval(1000); m_timer.start(); }
Hier sehen wir eine Kombination aus einem Timer-Signal (das jede Sekunde aktualisiert wird) und einer benutzerdefinierten Lambda-Funktion. Wir sehen hier die Verwendung von QJniObject::callStaticMethod(…), also lassen Sie uns versuchen, dies zu analysieren. Wir rufen eine statische Java-Methode (callStaticMethod()) aus der Klasse com/ScytheStudio/SmsReciever (erster Parameter) auf. Diese Methode heißt getLastMessage (zweiter Parameter). Da wir wissen, dass diese Methode uns einen String zurückgibt, geben wir den Rückgabetyp an, d.h. „“, in spitzen Klammern (für alle Datentypen-Konvertierungen siehe die Qt-Dokumentation).
Unsere Anwendung ist fast fertig. Alles, was noch bleibt, ist, die main.cpp- und main.qml-Dateien zu ändern. Da ich im main.cpp-Datei lediglich eine Instanz unseres SmsReciever erstelle und es registriere und in der QML-Datei eine ListView erstelle, werde ich die Erklärung des Codes überspringen und einfach zeigen, wie es aussehen sollte:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include <QDebug> #include "SmsReceiver.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; SmsReceiver smsReceiver; engine.rootContext()->setContextProperty("smsReceiver", &smsReceiver); QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.loadFromModule("2023JavaQt", "Main"); return app.exec(); } import QtQuick import QtQuick.Window import QtQuick.Controls Window { width: 640 height: 480 visible: true title: qsTr("SmsReceiver") color: "#10403f" ListView { id: messagesListView anchors { fill: parent margins: 10 } model: smsReceiver.smsMessagesList spacing: 5 delegate: Rectangle { id: messageDelegate width: messagesListView.width height: Math.max(50, messageText.height + 20) color: "#218165" radius: 15 Text { id: messageText anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter margins: 10 } text: modelData font.pixelSize: 18 color: "white" wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } } }
Jetzt müssen wir nur noch unsere Android-App bauen, und wir sind fertig. Sehen wir uns im folgenden GIF an, wie alles funktioniert. Für die Zwecke dieses Tutorials verwenden wir einen Android-Emulator, der es ermöglicht, das Senden einer Textnachricht an den Emulator zu „simulieren“. Die angegebene Nummer ist eine künstliche Nummer, die der Emulator für Tests verwendet.
Wir können also sehen, dass alles perfekt funktioniert. Das war’s für heute. Wenn du in diesem Bereich mehr professionelle Hilfe benötigst, denke daran, dass wir auch Beratung anbieten. Ich hoffe, dir hat dieser Beitrag gefallen. Bis bald!
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!Da die Gesundheitsbranche immer mehr Daten generiert, liegt die Herausforderung nicht mehr nur in der Datensammlung, sondern in der Interpretation. […]
Produkte aus der STM32-Familie sind seit langer Zeit ein beliebtes Ziel für eingebettete Qt-Anwendungen. Eine der beliebtesten Optionen war über […]
Ich begrüße Sie zu einem weiteren Blogbeitrag. Der letzte Beitrag behandelte eine Form der Kommunikation zwischen Geräten mit Qt Bluetooth […]