Implementasi kerja dengan server Long Poll di klien VKontakte untuk Sailfish OS

Pendahuluan


Sayangnya, bahkan sekarang, di dunia modern, tidak selalu mungkin untuk mengambil keuntungan dari manfaat teknologi push dan kadang-kadang perlu untuk menerapkan solusi, misalnya, dalam bentuk Long Poll, yang memungkinkan Anda untuk meniru mekanisme pemberitahuan push. Secara khusus, kebutuhan seperti itu muncul ketika menerapkan klien VKontakte untuk Sailfish OS .

Artikel ini tidak akan membahas prinsip-prinsip interaksi dengan server Long Poll VKontakte - ini memiliki dokumentasi yang sangat terperinci, dan contoh-contoh dasar telah dipublikasikan sebelumnya . Sebaliknya, implementasi praktis untuk platform tertentu akan dipertimbangkan.

Dapat dipahami bahwa pembaca tidak asing dengan pengembangan OS Sailfish tidak hanya di QML , tetapi juga di C ++ .

Pelanggan Polling Panjang


Kelas klien utama adalah kelas LongPoll , yang menanyakan server Long Poll dan mem-parsing responsnya.

Metode getLongPollServer , yang tugasnya memperoleh informasi untuk membuka koneksi ke server, dipanggil selama inisialisasi aplikasi, yang memungkinkan Anda untuk segera menerima pembaruan pengguna:

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

Jika permintaan berhasil diselesaikan, informasi koneksi dengan server Long Poll disimpan dan koneksi dibuka menggunakan metode 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(); //     } 

Dalam metode Polling Panjang doLongPollRequest , parameter koneksi yang diperlukan diteruskan ke server:

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

Perlu dicatat bahwa nilai bidang mode , sama dengan 10, diperoleh dengan menambahkan opsi untuk menerima lampiran (2) dan mengembalikan rangkaian acara yang diperpanjang (8).

Sebagai tanggapan untuk membuka koneksi, server mengembalikan JSON yang berisi peristiwa terbaru. Respons diproses dalam metode 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(); //     } 

Bidang yang failed dalam respons dapat mengambil empat nilai, tetapi hanya satu dari mereka, sama dengan satu, tidak memerlukan permintaan berulang untuk informasi untuk terhubung ke server Long Poll. Untuk alasan ini, kondisi ditambahkan ke kode.

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

Metode parseLongPollUpdates adalah siklus sederhana untuk semua acara yang masuk dengan centang jenisnya:

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

Kode menunjukkan bahwa untuk setiap acara Long Poll baru, klien mengirim sinyal yang harus diproses oleh bagian lain dari aplikasi. Argumen sinyal bukan objek acara keseluruhan, tetapi hanya bagian yang diperlukan. Misalnya, sinyal gotNewMessage hanya mentransmisikan pengidentifikasi pesan baru, yang dengannya konten lengkapnya diminta :

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

Sebagai hasil dari fungsi single-line ini , permintaan dibuat ke server VKontakte untuk mendapatkan informasi lengkap tentang pesan oleh pengidentifikasi dengan penciptaan lebih lanjut dari objek pesan ini. Sebagai kesimpulan, sebuah sinyal dikirim terkait dengan antarmuka pengguna, mentransmisikan data tentang pesan baru, yang ditampilkan di panel notifikasi:

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

Antarmuka dialog


Sekarang, berdasarkan prinsip-prinsip interaksi klien dengan server Long Poll dan prinsip-prinsip mentransfer informasi yang diterima ke antarmuka pengguna, kita dapat mempertimbangkan contoh memperbarui dialog terbuka.

Hal pertama yang menarik perhatian Anda adalah komponen 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 //      } } } 

Slot onUserTyping memproses peristiwa yang diatur oleh lawan bicara pesan dengan menampilkan notifikasi yang sesuai kepada pengguna. Di sini, pada langkah pertama, pengidentifikasi ruangan diperoleh (ruangan berarti istilah umum untuk dialog dan obrolan), dan pada tahap kedua, pemberitahuan ditampilkan jika pengidentifikasi yang diterima dan pengidentifikasi dari pencocokan kamar saat ini.

Perlu dicatat bahwa pemberitahuan tentang sekumpulan pesan ditampilkan selama sepuluh detik, jika selama waktu ini belum ada acara baru yang mengaktifkan kembali pemberitahuan tersebut. Ini dicapai dengan menggunakan komponen 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 //       } } 

Slot onSavedPhoto bertanggung jawab untuk memproses akhir pemuatan gambar dalam pesan, yang berada di luar cakupan artikel saat ini.

Hal kedua yang menarik adalah daftar pesan:

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

Di sini, komponen MessageItem bertanggung jawab untuk menampilkan satu pesan. Pertimbangannya berada di luar cakupan artikel ini.

Pesan itu sendiri diambil dari model vksdk.messagesModel . Model ini adalah daftar objek Message yang dapat diperbarui secara waktu nyata menggunakan metode add , prepend , addProfile , readMessages , dan clear :

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

Umum untuk semua lima metode adalah penggunaan sinyal dataChanged , yang menunjukkan bahwa data telah diperbarui dalam model. SilicaListView sinyal ini memperbarui elemen SilicaListView untuk menampilkan status pesan saat ini. Menambahkan pesan ke SilicaListView disediakan dengan memanggil endInsertRows rowsAboutToBeInserted dan rowsInserted masing-masing mengirim rowsAboutToBeInserted dan rowsInserted . Akibatnya, pengguna akan melihat pesan baru dan statusnya secara real time dalam dialog.

Kesimpulan


Artikel ini meneliti interaksi dengan server Long Poll ketika mengembangkan untuk Sailfish OS menggunakan aplikasi VK sebagai contoh. Beberapa fitur dari implementasi klien dan cara untuk memperbarui antarmuka pengguna secara real time dipertimbangkan. Kode untuk aplikasi yang dijelaskan dalam artikel ini tersedia di GitHub.

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


All Articles