在Sailfish OS的VKontakte客户端中使用Long Poll服务器实现工作

引言


不幸的是,即使在现在,在现代世界中,也并非总是能够利用推送技术的优势有时有时有必要实施变通办法,例如以Long Poll的形式,它可以让您模仿推送通知的机制。 特别是在为Sailfish OS实现VKontakte客户端时出现了这种需求。

本文不会讨论与Long Poll VKontakte服务器进行交互的原理-它具有非常详细的文档 ,并且基本示例已在较早时发布。 相反,将考虑针对特定平台的实际实现。

据了解,读者不仅熟悉QML中的Sailfish OS开发,还熟悉C ++中的Sailfish OS 开发

长投票客户


主要客户端类是LongPoll类,该类查询Long Poll服务器并解析其响应。

在应用程序初始化期间调用getLongPollServer方法,其任务是获取信息以打开与服务器的连接,该方法使您可以立即接收用户更新:

 /** *       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-    } 

如果请求成功完成,则保存与Long Poll服务器的连接信息,并使用doLongPollRequest方法打开连接:

 /* *      . * @: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(); //     } 

doLongPollRequest Long Poll方法中,必要的连接参数传递到服务器:

 /* *     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  } 

值得注意的是, mode字段的值等于10,是通过添加接收附件的选项(2)并返回扩展的事件集(8)获得的。

作为对打开连接的响应,服务器返回包含最新事件的JSON。 响应以finished方法处理:

 /* *      . * @: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(); //     } 

响应中failed字段可以采用四个值,但是只有一个值等于1,不需要重复请求信息即可连接到Long Poll服务器。 因此,将条件添加到了代码中。

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

parseLongPollUpdates方法是对所有传入事件进行检查的简单周期:

 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; } } } 

该代码显示,对于每个新的长轮询事件,客户端都会发送一个信号 ,该信号必须由应用程序的另一部分进行处理。 signal参数不是整个事件对象,而只是其必要部分。 例如, gotNewMessage信号仅发送gotNewMessage的标识符,通过该标识符请求其完整内容:

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

作为此单行功能的结果,将向VKontakte服务器发出请求,以通过进一步创建此消息对象来通过其标识符获取有关消息的完整信息。 总之,将发送与用户界面关联的信号,传输有关新消息的数据,该消息显示在通知面板中:

 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() //    } } } 

对话介面


现在,基于客户端与Long Poll服务器交互的原理以及将接收到的信息传输到用户界面的原理,我们可以考虑一个更新开放对话的示例。

引起您注意的第一件事是“ Connections组件:

 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 //      } } } 

onUserTyping插槽通过向用户显示相应的通知来处理消息的对话者设置的事件。 在这里,第一步,获得房间标识符(房间是指对话和聊天的通用术语),第二步,如果接收到的标识符和当前房间的标识符匹配,则显示通知。

值得注意的是,如果在这段时间内没有新的事件再次激活该通知,则将在十秒钟内显示有关一组消息的通知。 这可以通过使用Timer组件来实现:

 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 //       } } 

onSavedPhoto插槽负责处理消息中图像的加载结束,这超出了本文的范围。

感兴趣的第二件事是消息列表:

 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 {} //      } 

在这里, MessageItem组件负责显示一条消息。 它的考虑超出了本文的范围。

消息本身取自vksdk.messagesModel模型。 此模型是Message对象的列表,可以使用addprependaddProfilereadMessagesclear方法实时更新:

 /* *    . */ 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); } 

这五种方法的共同点是使用dataChanged信号,该信号指示数据已在模型中更新。 发出此信号将更新SilicaListView元素以显示消息的当前状态。 通过调用beginInsertRowsendInsertRows来向rowsAboutToBeInserted添加消息,该endInsertRows分别发送rowsAboutToBeInsertedrowsInserted 。 结果,用户将在对话框中实时看到新消息及其状态。

结论


本文以VK应用程序为例,研究了为Sailfish OS开发时与Long Poll服务器的交互。 考虑了客户端实现的一些功能以及实时更新用户界面的方法。 GitHub上提供了本文描述的应用程序代码。

Source: https://habr.com/ru/post/zh-CN413947/


All Articles