Qt on Android: http下載與Json解析

來源:互聯網
上載者:User

標籤:qt on android   json   文本編碼格式   qt 網路編程   c   

    百度提供有查詢 ip 歸屬地的開放介面,當你在搜尋方塊中輸入一個 ip 地址進行搜尋,就會開啟由 ip138 提供的百度框應用,你可以在框內直接輸入 ip 地址查詢。我查看了頁面請求,提取出查詢 ip 歸屬地的介面,據此使用 Qt 寫了個簡單的 ip 歸屬地查詢應用。可以在電腦和 Android 手機上運行。這裡使用了百度 API ,特此聲明,僅可作為示範使用,不能用作商業目的。

    著作權 foruok,轉載請註明出處( http://blog.csdn.net/foruok )。

    這個例子會用到 http 下載、布局管理器、編輯框、按鈕、Json 解析等知識,我們會一一解說。圖 1 是在手機上輸入 IP 位址的:


           圖1 輸入 Ip 地址 

    再看圖 2 ,是點擊查詢按鈕後查詢到的結果:


             圖2 IP 歸屬地查詢結果

    好啦,現在我們來說程式。

    項目是基於 Qt Widgets Application 模板建立,選擇 QWidget 為基類,具體建立過程請參考《Qt on Android:圖文詳解Hello World全過程》。項目的名字就叫 IpQuery ,建立項目完成後,開啟工程檔案,為 QT 變數添加網路模組,因為我們查詢 IP 時要使用。如下面代碼所示:

QT       += core gui network
    項目模板給我們產生 widget.h / widget.cpp ,修改一下代碼,先把介面搞起來。首先看標頭檔 widget.h :

#ifndef WIDGET_H#define WIDGET_H#include <QWidget>#include <QPushButton>#include <QLineEdit>#include <QLabel>#include "ipQuery.h"class Widget : public QWidget{    Q_OBJECTpublic:    Widget(QWidget *parent = 0);    ~Widget();protected slots:    void onQueryButton();    void onQueryFinished(bool bOK, QString ip, QString area);protected:    QLineEdit *m_ipEdit;    QPushButton *m_queryButton;    QLabel *m_areaLabel;    IpQuery m_ipQuery;};#endif // WIDGET_H

    再看 widget.cpp :
#include "widget.h"#include <QGridLayout>Widget::Widget(QWidget *parent)    : QWidget(parent), m_ipQuery(this){    connect(&m_ipQuery, SIGNAL(finished(bool,QString,QString))            ,this, SLOT(onQueryFinished(bool,QString,QString)));    QGridLayout *layout = new QGridLayout(this);    layout->setColumnStretch(1, 1);    QLabel *label = new QLabel("ip:");    layout->addWidget(label, 0, 0);    m_ipEdit = new QLineEdit();    layout->addWidget(m_ipEdit, 0, 1);    m_queryButton = new QPushButton("查詢");    connect(m_queryButton, SIGNAL(clicked()),            this, SLOT(onQueryButton()));    layout->addWidget(m_queryButton, 1, 1);    m_areaLabel = new QLabel();    layout->addWidget(m_areaLabel, 2, 0, 1, 2);    layout->setRowStretch(3, 1);}Widget::~Widget(){}void Widget::onQueryButton(){    QString ip = m_ipEdit->text();    if(!ip.isEmpty())    {        m_ipQuery.query(ip);        m_ipEdit->setDisabled(true);        m_queryButton->setDisabled(true);    }}void Widget::onQueryFinished(bool bOK, QString ip, QString area){    if(bOK)    {        m_areaLabel->setText(area);    }    else    {        m_areaLabel->setText("喔喲,出錯了");    }    m_ipEdit->setEnabled(true);    m_queryButton->setEnabled(true);}

    介面布局很簡單,我們使用一個 QGridLayout 來管理 ip 地址編輯框、查詢按鈕以及用於顯示結果的 QLabel 。QGridLayout 有 addWidget() / addLayout() 等方法可以添加控制項或子布局。還有 setColumnStretch() / setRowStretch() 兩個方法來設定行、列的展開的係數。樣本中設定 ip 編輯框所在列的展開係數為 1 ,設定看不見的第 4 行的展開係數為 1 ,因為我們的小程式的控制項充滿不了整個手機螢幕,這樣設定後在手機上顯示會比較正常。

    我還在 Widget 建構函式中把 QPushButton 的訊號 clicked() 串連到 onQueryButton() 槽上,在槽內調用 IpQuery 類進行 ip 查詢。另外還串連了 IpQuery 類的 finished() 訊號和 Widget 類的 onQueryFinished() 槽,當查詢結束後把結果顯示到 m_areaLabel 代表的標籤上。

    好啦, Widget 解說完畢,咱們接下來看看我實現的 IpQuery 類。我們在項目中添加兩個檔案 ipQuery.h / ipQuery.cpp 。先看 ipQuery.h :

#ifndef IPQUERY_H#define IPQUERY_H#include <QObject>#include <QNetworkAccessManager>#include <QNetworkReply>class IpQuery : public QObject{    Q_OBJECTpublic:    IpQuery(QObject *parent = 0);    ~IpQuery();    void query(const QString &ip);    void query(quint32 ip);signals:    void finished(bool bOK, QString ip, QString area);protected slots:    void onReplyFinished(QNetworkReply *reply);private:    QNetworkAccessManager m_nam;    QString m_emptyString;};

    在 IpQuery 類體中聲明了兩個 query() 函數,分別接受 QString 和 uint32 兩種格式的 ip 地址。還聲明了一個 finished() 訊號,有指示成功與否的布爾參數 bOK 、輸入的 ip 地址 ip 、返回的歸屬地 area 。最後定義了一個槽 onReplyFinished() ,響應 QNetworkAccessManager 類的 finished() 訊號。

    再看 IpQuery.cpp :

#include "ipQuery.h"#include <QJsonDocument>#include <QByteArray>#include <QHostAddress>#include <QJsonObject>#include <QNetworkRequest>#include <QJsonArray>#include <QTextCodec>#include <QDebug>IpQuery::IpQuery(QObject *parent)    : QObject(parent)    , m_nam(this){    connect(&m_nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(onReplyFinished(QNetworkReply*)));}IpQuery::~IpQuery(){}void IpQuery::query(const QString &ip){    QString strUrl = QString("http://opendata.baidu.com/api.php?query=%1&resource_id=6006&ie=utf8&format=json").arg(ip);    QUrl url(strUrl);    QNetworkRequest req(url);    req.setRawHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");    req.setHeader(QNetworkRequest::UserAgentHeader, "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36");    QNetworkReply *reply = m_nam.get(req);    reply->setProperty("string_ip", ip);}void IpQuery::query(quint32 ip){    QHostAddress addr(ip);    query(addr.toString());}void IpQuery::onReplyFinished(QNetworkReply *reply){    reply->deleteLater();    QString strIp = reply->property("string_ip").toString();    if(reply->error() != QNetworkReply::NoError)    {        qDebug() << "IpQuery, error - " << reply->errorString();        emit finished(false, strIp, m_emptyString);        return;    }    int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();    //qDebug() << "IpQuery, status - " << status ;    if(status != 200)    {        emit finished(false, strIp, m_emptyString);        return;    }    QByteArray data = reply->readAll();    QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString();    //qDebug() << "contentType - " << contentType;    int charsetIndex = contentType.indexOf("charset=");    if(charsetIndex > 0)    {        charsetIndex += 8;        QString charset = contentType.mid(charsetIndex).trimmed().toLower();        if(charset.startsWith("gbk") || charset.startsWith("gb2312"))        {            QTextCodec *codec = QTextCodec::codecForName("GBK");            if(codec)            {                data = codec->toUnicode(data).toUtf8();            }        }    }    int parenthesisLeft = data.indexOf(‘(‘);    int parenthesisRight = data.lastIndexOf(‘)‘);    if(parenthesisLeft >=0 && parenthesisRight >=0)    {        parenthesisLeft++;        data = data.mid(parenthesisLeft, parenthesisRight - parenthesisLeft);    }    QJsonParseError err;    QJsonDocument json = QJsonDocument::fromJson(data, &err);    if(err.error != QJsonParseError::NoError)    {        qDebug() << "IpQuery, json error - " << err.errorString();        emit finished(false, strIp, m_emptyString);        return;    }    QJsonObject obj = json.object();    QJsonObject::const_iterator it = obj.find("data");    if(it != obj.constEnd())    {        QJsonArray dataArray = it.value().toArray();        QJsonObject info = dataArray.first().toObject();        QString area = info.find("location").value().toString();        emit finished(true, strIp, area);    }}

    IpQuery 的實現簡單直接,發起一個網路請求,解析返回的 Json 資料。

    我在 IpQuery 建構函式中串連 QNetworkAccessManager 的 finished() 訊號和 IpQuery 的 onReplyFinished() 槽。

    關鍵的函數有兩個 query(const QString&) 和 onReplyFinished(QNetworkReply*) 。先看 query() 函數,它示範了使用 QNetworkAccessManager 進行 http 下載的基本步驟:

  1. 使用 QNetworkRequest 構造請求(包括 URL 和 http header)
  2. 調用 QNetworkAccessManager 的 get() 方法提交下載請求
  3. 使用 QNetworkReply ,儲存與下載有關的一些屬性,setProperty() 可以動態產生屬性,而 property() 可以把屬性取出來
  4. 響應 QNetworkAccessManager 的 finished() 訊號處理網路反饋(也可以串連 QNetworkReply 的 readyRead() / downloadProgress() / error() / finished() 等訊號進行更為詳細的下載控制)

    至於 http header 的設定,QNetworkRequest 提供了 setHeader() 方法用來設定常見的如 User-Agent / Content-Type 等 header ,Qt 沒有定義的常見 header ,則需要調用 setRawHeader() 方法來設定,具體請參考 http 協議,這裡不再細說。

    好了,現在來看 onReplyFinished() 函數都幹了什麼勾當:

  1. 取出提交下載請求時設定的屬性(字串格式的 ip 地址)
  2. 調用 QNetworkReply::error() 檢查是否出錯
  3. 調用 QNetworkReply::attribute() 或者 http 狀態代碼,查看 http 協議本身是否報錯(如 404 / 403 / 500 等)
  4. 讀取資料
  5. 調用 QNetworkReply::header() 方法擷取 Content-Type 頭部,查看字元編碼,如果是 GBK 或者 GB2312 則轉換為 utf-8 ( QJsonDocument 解析時需要 utf-8 格式)
  6. 解析 Json 資料

    根據上面的解說對照代碼,應該一切都很容易理解了。這裡解釋下 5 、 6  兩個稍微複雜的步驟。

    將 GBK 編碼的文本資料轉換為 utf-8 ,遵循下列步驟:

  1. 使用 QTextCodec 的靜態方法 codecForName() 擷取指定編碼是個的 QTextCcodec 執行個體
  2. 調用 QTextCodec 的 toUnicode() 方法轉換為 unicode 編碼的 QString 對象
  3. 調用 QString 的 toUtf8() 方法轉換為 utf-8 格式

    更詳細的說明請參看 QTextCodec 類的 API 文檔。

    最後我們說下 Json 資料解析。從 Qt 5.0 開始,引入了對 Json 的支援,之前你可能需要使用開源的 cJson 或者 QJson ,現在好了,官方支援,用起來更踏實了。

    Qt 可以解析文本形式的 Json 資料,使用 QJsonDocument::fromJson() 方法即可;也可以解析編譯為二進位的 Json 資料,使用 fromBinaryData() 或 fromRawData() 。對應的,toJson() 和 toBinaryData() 則可以反向轉換。

    我們的樣本調用 fromJson() 方法來解析文本形式的 Json 資料。  fromJson() 接受 UTF-8 格式的 Json 資料,還接受一個 QJsonParseError 對象指標,用於輸出可能遇到的錯誤。一旦 fromJson() 方法返回,Json 資料就都被解析為 Qt 定義的各種 Json 對象,如 QJsonObject / QJsonArray / QJsonValue 等等,可以使用這些類的方法來查詢你感興趣的資料。

    做個簡單的科普,解釋下 Json 格式。

    JSON 指的是 JavaScript 物件標記法(JavaScript Object Notation),是輕量級的文本資料交換格式,具有自我描述性,容易理解。雖然脫胎於 JavaScript 語言,但它是獨立於語言和平台的。

    Json 中有兩種資料結構:

  1.     key - value 對的集合,通常稱為對象。
  2.     值的有序列表,通常稱為數組。

    Json 對象在花括弧中表示,最簡單的樣本:

{"ip":"36.57.177.187"}
    如你所見,一對花括弧表示一個對象,key 和 value 之間用冒號分割,而 key - value 對之間使用逗號分割。一個對象可以有有多個 key-value 對。下面是兩個的樣本:

{    "status":"0",    "t":"1401346439107"}

    Json 中的值有六種基本類型:布爾、浮點數、字串、數組、對象、null 。此時你當想到嵌套了吧,是的:一個值可以是一個對象,而對象又可以展開繼續嵌套;一個值可以是數組,而數組內是一系列的基本類型的值或對象……所以,使用嵌套,真是可以表述非常複雜的資料結構,但是但是,其實不那麼好讀了……

    Json 數組在方括弧中表示,最簡單的樣本,只有基本類型:

["baz", null, 1.0, 2]

    數組內的值之間用逗號分割。

    看個複雜點的樣本:

[  "name":"zhangsan",   {    "age":30,    "phone":"13588888888",    "other": ["xian", null, 1.0, 28]  }]

    數組內包含了簡單字串值,還有對象,對象內又包含了 key - value 對、 數組……

    在 Qt 中,QJsonValue 代表了 Json 中的值,它有 isDouble() / isBool() / isNull() / isArray() / isObject() / isString() 六個方法來判定值的類型,然後有 toDouble() / toBool() / toInt() / toArray() / toObject() / toString() 等方法用來把 QJsonValue 轉換為特定類型的值。

    QJsonObject 類代表對象,它的 find() 方法可以根據 key 找 value ,而 keys() 可以返回所有 key 的列表;它還重載了 "[]" 操作符,接受字串格式的 key 作為下標,讓我們像使用數組一樣使用 QJsonObject 。

    QJsonArray 類代表數組,它有 size() / at() / first() / last() 等等方法,當然了,它也重載了  "[]" 操作符(接受整形數組下標)。

    OK,到此為止,基礎知識介紹完畢,來看我們 ip 查詢時要處理的 Json 資料吧:

{    "status":"0",    "t":"1401346439107",    "data":[      {        "location":"安徽省宿州市 電信",        "titlecont":"IP地址查詢",        "origip":"36.57.177.187",        "origipquery":"36.57.177.187",        "showlamp":"1",        "showLikeShare":1,        "shareImage":1,        "ExtendedLocation":"",        "OriginQuery":"36.57.177.187",        "tplt":"ip",        "resourceid":"6006",        "fetchkey":"36.57.177.187",        "appinfo":"", "role_id":0, "disp_type":0      }    ]}

    根對象內名為 "data" 的 key 是個數組,而該數組內只有一個對象,這個對象內名為 "location" 的 key 對應的值是 ip 的歸屬地。好咧,對著這個格式再來看代碼,就非常簡單了:

    QJsonParseError err;    QJsonDocument json = QJsonDocument::fromJson(data, &err);
    這兩行根據資料產生 QJsonDocument 對象。然後我們來找根對象名為 "data" 的 key 對應的值,看代碼:

    QJsonObject obj = json.object();    QJsonObject::const_iterator it = obj.find("data");
    找到 data 對應的值,使用 toArray() 轉換為 QJsonArray ,使用 QJsonArray 的 first() 方法取第一個元素,使用 toObject() 轉為 QJsonObject, 再使用 find() 方法,以 "location" 為 key 尋找,把結果轉為 String 。說來話長,代碼更明白:
        QJsonArray dataArray = it.value().toArray();        QJsonObject info = dataArray.first().toObject();        QString area = info.find("location").value().toString();        emit finished(true, strIp, area);

    好啦,這個樣本全部解說完畢。最後看下電腦上的運行效果,圖 3 :

    

            圖 3 電腦運行

    著作權 foruok,轉載請註明出處( http://blog.csdn.net/foruok )。


我的 Qt on Android 系列文章:

  • Qt on Android:圖文詳解Hello World全過程
  • Windows下Qt 5.2 for Android開發入門
  • Qt for Android 部署流程分析
  • Qt on Android:將Qt調試資訊輸出到logcat中
  • Qt on Android: Qt 5.3.0 發布,針對 Android 改進說明
  • Qt on Android Episode 1(翻譯)
  • Qt on Android Episode 2(翻譯)
  • Qt on Android Episode 3(翻譯)
  • Qt on Android Episode 4(翻譯)
  • Qt for Android 編譯純C工程
  • Windows下Qt for Android 編譯安卓C語言可執行程式
  • Qt on Android: Android SDK安裝

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.