Implementierung der Arbeit mit dem Long Poll-Server im VKontakte-Client für Sailfish OS

Einführung


Leider ist es in der modernen Welt selbst jetzt nicht immer möglich, die Vorteile der Push-Technologie zu nutzen, und manchmal müssen Sie Problemumgehungen implementieren, beispielsweise in Form von Long Poll, mit denen Sie den Mechanismus von Push-Benachrichtigungen emulieren können. Ein solcher Bedarf trat insbesondere bei der Implementierung des VKontakte-Clients für Sailfish OS auf .

In diesem Artikel werden die Prinzipien der Interaktion mit dem Long Poll VKontakte-Server nicht erläutert. Er enthält eine sehr detaillierte Dokumentation , und grundlegende Beispiele wurden bereits früher veröffentlicht. Stattdessen wird eine praktische Implementierung für eine bestimmte Plattform in Betracht gezogen.

Es versteht sich, dass der Leser mit der Entwicklung von Sailfish OS nicht nur in QML , sondern auch in C ++ vertraut ist.

Long Poll Kunde


Die Hauptclientklasse ist die LongPoll Klasse, die den Long Poll-Server abfragt und seine Antworten analysiert.

Die Methode getLongPollServer , deren Aufgabe es ist, Informationen zum getLongPollServer einer Verbindung zum Server getLongPollServer , wird während der Anwendungsinitialisierung aufgerufen, getLongPollServer Sie sofort Benutzeraktualisierungen erhalten können:

 /** *       Long Poll  . */ void LongPoll::getLongPollServer() { QUrl url("https://api.vk.com/method/messages.getLongPollServer"); //    API QUrlQuery query; query.addQueryItem("access_token", _accessToken); //  Access Token query.addQueryItem("v", "5.53"); //    API url.setQuery(query); //       _manager->get(QNetworkRequest(url)); //  GET-    } 

Wenn die Anforderung erfolgreich abgeschlossen wurde, werden die Verbindungsinformationen mit dem Long Poll-Server gespeichert und die Verbindung mit der Methode doLongPollRequest geöffnet:

 /* *      . * @:param: reply --    . */ void LongPoll::finished(QNetworkReply* reply) { QJsonDocument jDoc = QJsonDocument::fromJson(reply->readAll()); //    JSON if (_server.isNull() || _server.isEmpty()) { //      QJsonObject jObj = jDoc.object().value("response").toObject(); _server = jObj.value("server").toString(); //    _key = jObj.value("key").toString(); //    _ts = jObj.value("ts").toInt(); //     doLongPollRequest(); //    Long Poll  } else { // ... //     // ... } reply->deleteLater(); //     } 

In der Methode doLongPollRequest Long Poll werden die erforderlichen Verbindungsparameter an den Server übergeben:

 /* *     Long Poll . */ void LongPoll::doLongPollRequest() { QUrl url("https://" + _server); //    QUrlQuery query; query.addQueryItem("act", "a_check"); //     query.addQueryItem("key", _key); //   query.addQueryItem("ts", QString("%1").arg(_ts)); //    query.addQueryItem("wait", "25"); //  25   query.addQueryItem("mode", "10"); //       url.setQuery(query); //       _manager->get(QNetworkRequest(url)); //  GET-  Long Poll  } 

Es ist anzumerken, dass der Wert des mode gleich 10 erhalten wurde, indem die Option zum Empfangen von Anhängen (2) hinzugefügt und eine erweiterte Reihe von Ereignissen (8) zurückgegeben wurde.

Als Antwort auf das Öffnen einer Verbindung gibt der Server einen JSON zurück, der die neuesten Ereignisse enthält. Die Antwort wird in der finished Methode verarbeitet:

 /* *      . * @:param: reply --    . */ void LongPoll::finished(QNetworkReply* reply) { QJsonDocument jDoc = QJsonDocument::fromJson(reply->readAll()); //    JSON if (_server.isNull() || _server.isEmpty()) { // ... //    // ... } else { QJsonObject jObj = jDoc.object(); if (jObj.contains("failed")) { //       if (jObj.value("failed").toInt() == 1) { //    _ts = jObj.value("ts").toInt(); //      doLongPollRequest(); //    Long Poll  } else { _server.clear(); //    _key.clear(); //    _ts = 0; //     getLongPollServer(); //      } } else { //      _ts = jObj.value("ts").toInt(); //      parseLongPollUpdates(jObj.value("updates").toArray()); //     doLongPollRequest(); //    Long Poll  } } reply->deleteLater(); //     } 

Das failed Feld in der Antwort kann vier Werte annehmen, aber nur einer von ihnen, der einem entspricht, erfordert keine wiederholte Anforderung von Informationen, um eine Verbindung zum Long Poll-Server herzustellen. Aus diesem Grund wurde die Bedingung zum Code hinzugefügt.

 jObj.value("failed").toInt() == 1 

Die parseLongPollUpdates Methode ist ein einfacher Zyklus für alle eingehenden Ereignisse mit einer Überprüfung ihres Typs:

 enum LONGPOLL_EVENTS { NEW_MESSAGE = 4, //   INPUT_MESSAGES_READ = 6, //    OUTPUT_MESSAGES_READ = 7, //    USER_TYPES_IN_DIALOG = 61, //      USER_TYPES_IN_CHAT = 62, //      UNREAD_DIALOGS_CHANGED = 80, //     }; /* *   ,   Long Poll . * @:param: updates --    . */ void LongPoll::parseLongPollUpdates(const QJsonArray& updates) { for (auto value : updates) { //     QJsonArray update = value.toArray(); //    switch (update.at(0).toInt()) { //    case NEW_MESSAGE: emit gotNewMessage(update.at(1).toInt()); break; case INPUT_MESSAGES_READ: emit readMessages(update.at(1).toInt(), update.at(2).toInt(), false); break; case OUTPUT_MESSAGES_READ: emit readMessages(update.at(1).toInt(), update.at(2).toInt(), true); break; case USER_TYPES_IN_DIALOG: emit userTyping(update.at(1).toInt(), 0); break; case USER_TYPES_IN_CHAT: emit userTyping(update.at(1).toInt(), update.at(2).toInt()); break; case UNREAD_DIALOGS_CHANGED: emit unreadDialogsCounterUpdated(update.at(1).toInt()); break; default: break; } } } 

Der Code zeigt, dass der Client für jedes neue Long Poll-Ereignis ein Signal sendet, das von einem anderen Teil der Anwendung verarbeitet werden muss. Das Signalargument ist nicht das gesamte Ereignisobjekt, sondern nur die erforderlichen Teile. Beispielsweise überträgt das gotNewMessage Signal nur die Kennung einer neuen Nachricht, von der der vollständige Inhalt angefordert wird :

 void VkSDK::_gotNewMessage(int id) { _messages->getById(id); } 

Als Ergebnis dieser einzeiligen Funktion wird eine Anforderung an den VKontakte-Server gesendet, um vollständige Informationen über die Nachricht durch ihre Kennung zu erhalten, wobei das Objekt dieser Nachricht weiter erstellt wird. Abschließend wird ein der Benutzeroberfläche zugeordnetes Signal gesendet, das Daten zu einer neuen Nachricht überträgt, die im Benachrichtigungsfeld angezeigt wird :

 import QtQuick 2.0 //      QML import Sailfish.Silica 1.0 //      Sailfish OS import org.nemomobile.notifications 1.0 //      ApplicationWindow //   { // ... //   // ... Notification { //     id: commonNotification //    category: "harbour-kat" //   remoteActions: [ //  -    { "name": "default", "service": "nothing", "path": "nothing", "iface": "nothing", "method": "nothing" } ] } Connections { //     target: vksdk //    SDK  onGotNewMessage: { //      commonNotification.summary = name //      commonNotification.previewSummary = name //     commonNotification.body = preview //      commonNotification.previewBody = preview //     commonNotification.close() //      commonNotification.publish() //    } } } 

Dialogoberfläche


Basierend auf den Prinzipien der Client-Interaktion mit dem Long Poll-Server und den Prinzipien der Übertragung der empfangenen Informationen auf die Benutzeroberfläche können wir nun ein Beispiel für die Aktualisierung eines offenen Dialogs betrachten .

Das erste, was auffällt, ist die Connections Komponente:

 Connections { //     target: vksdk //    SDK  onSavedPhoto: { //       attachmentsList += name + ","; //       attachmentsBusy.running = false; //     } onUserTyping: { //       var tempId = userId; //      if (chatId !== 0) { //    tempId = chatId; //  ,     } if (tempId === historyId) { //       typingLabel.visible = true //      } } } 

Der onUserTyping Slot verarbeitet das Ereignis eines Satzes, den der Gesprächspartner der Nachricht festgelegt hat, indem er dem Benutzer die entsprechende Benachrichtigung anzeigt. Hier wird im ersten Schritt die Raumkennung erhalten (der Raum bedeutet den allgemeinen Begriff für Dialoge und Chats), und in der zweiten Stufe wird eine Benachrichtigung angezeigt, wenn die empfangene Kennung und die Kennung der aktuellen Raumkennung übereinstimmen.

Es ist zu beachten, dass eine Benachrichtigung über eine Reihe von Nachrichten zehn Sekunden lang angezeigt wird, wenn während dieser Zeit kein neues Ereignis aufgetreten ist, das die Benachrichtigung erneut aktiviert. Dies wird mit der Timer Komponente erreicht:

 Label { //     id: typingLabel //    anchors.bottom: newmessagerow.top //       width: parent.width //     horizontalAlignment: Text.AlignHCenter //      font.pixelSize: Theme.fontSizeExtraSmall //    color: Theme.secondaryColor //     text: qsTr("typing...") //    visible: false //      onVisibleChanged: if (visible) typingLabelTimer.running = true //     Timer { //    id: typingLabelTimer //    interval: 10000 //   --   onTriggered: typingLabel.visible = false //       } } 

Der onSavedPhoto Slot onSavedPhoto für die Verarbeitung des Endes des onSavedPhoto in Nachrichten verantwortlich, was den Rahmen des aktuellen Artikels onSavedPhoto .

Das zweite, was von Interesse ist, ist die Nachrichtenliste:

 SilicaListView { //    id: messagesListView //    //        : anchors.left: parent.left anchors.right: parent.right //            : anchors.top: parent.top anchors.bottom: typingLabel.top verticalLayoutDirection: ListView.BottomToTop //      clip: true //   ,     model: vksdk.messagesModel //    delegate: MessageItem { //     //        : anchors.left: parent.left anchors.right: parent.right //     : userId: fromId //   date: datetime //    out_: out //    read_: read //    avatarSource: avatar //    bodyText: body //   photos: photosList //     videos: videosList //     audios: audiosList //     documents: documentsList //     links: linksList //     news: newsList //       geoTile: geoTileUrl //     geoMap: geoMapUrl //         fwdMessages: fwdMessagesList //    Component.onCompleted: { //      if (index === vksdk.messagesModel.size-1) { //       //      : vksdk.messages.getHistory(historyId, vksdk.messagesModel.size) } } } VerticalScrollDecorator {} //      } 

Hier ist die MessageItem Komponente für die Anzeige einer einzelnen Nachricht verantwortlich. Ihre Berücksichtigung würde den Rahmen dieses Artikels sprengen.

Die Nachrichten selbst stammen aus dem Modell vksdk.messagesModel . Dieses Modell ist eine Liste von Message , die in Echtzeit mithilfe der Methoden addProfile , readMessages , addProfile , readMessages und clear aktualisiert werden können:

 /* *    . */ void MessagesModel::clear() { beginRemoveRows(QModelIndex(), 0, _messages.size()); //     _messages.clear(); //   _profiles.clear(); //    endRemoveRows(); //     //    : QModelIndex index = createIndex(0, 0, nullptr); emit dataChanged(index, index); } /* *      . * @:param: message --    . */ void MessagesModel::add(Message* message) { //   : beginInsertRows(QModelIndex(), _messages.size(), _messages.size()); _messages.append(message); //    endInsertRows(); //    //    : QModelIndex index = createIndex(0, 0, static_cast<void *>(0)); emit dataChanged(index, index); } /* *       . * @:param: message --    . */ void MessagesModel::prepend(Message* message) { //        : if (_messages.isEmpty()) return; if (message->chat() && _messages.at(0)->chatId() != message->chatId()) return; if (!message->chat() && _messages.at(0)->userId() != message->userId()) return; beginInsertRows(QModelIndex(), 0, 0); //    _messages.insert(0, message); //   endInsertRows(); //    //    : QModelIndex index = createIndex(0, _messages.size(), nullptr); emit dataChanged(index, index); } /* *       . * @:param: profile --    . */ void MessagesModel::addProfile(Friend* profile) { //   ,       if (_profiles.contains(profile->id())) return; _profiles[profile->id()] = profile; //    : QModelIndex startIndex = createIndex(0, 0, nullptr); QModelIndex endIndex = createIndex(_messages.size(), 0, nullptr); emit dataChanged(startIndex, endIndex); } /* *    . * @:param: peerId --  . * @:param: localId --    . * @:param: out --     . */ void MessagesModel::readMessages(qint64 peerId, qint64 localId, bool out) { //        : if (_messages.isEmpty()) return; if (_messages.at(0)->chat() && _messages.at(0)->chatId() != peerId) return; if (!_messages.at(0)->chat() && _messages.at(0)->userId() != peerId) return; foreach (Message *message, _messages) { //       if (message->id() <= localId && message->isOut() == out) //    message->setReadState(true); //    } //    : QModelIndex startIndex = createIndex(0, 0, nullptr); QModelIndex endIndex = createIndex(_messages.size(), 0, nullptr); emit dataChanged(startIndex, endIndex); } 

Allen fünf Methoden gemeinsam ist die Verwendung des dataChanged Signals, das angibt, dass die Daten im Modell aktualisiert wurden. Durch das SilicaListView dieses Signals werden die SilicaListView Elemente aktualisiert, um den aktuellen Status von Nachrichten anzuzeigen. Das Hinzufügen von Nachrichten zu SilicaListView erfolgt durch Aufrufen der endInsertRows beginInsertRows und endInsertRows , die die rowsInserted beginInsertRows bzw. endInsertRows senden. Infolgedessen sieht der Benutzer in Echtzeit neue Nachrichten und deren Status im Dialogfeld.

Fazit


In diesem Artikel wurde die Interaktion mit dem Long Poll-Server bei der Entwicklung für Sailfish OS am Beispiel der VK-Anwendung untersucht. Einige Funktionen der Client-Implementierung und eine Möglichkeit zum Aktualisieren der Benutzeroberfläche in Echtzeit wurden berücksichtigt. Der Code für die in diesem Artikel beschriebene Anwendung ist auf GitHub verfügbar .

Source: https://habr.com/ru/post/de413947/


All Articles