• Qt on Android: http下载与Json解析


        百度提供有查询 ip 归属地的开放接口,当你在搜索框中输入一个 ip 地址进行搜索,就会打开由 ip138 提供的百度框应用,你能够在框内直接输入 ip 地址查询。我查看了页面请求,提取出查询 ip 归属地的接口,据此使用 Qt 写了个简单的 ip 归属地查询应用。能够在电脑和 Android 手机上执行。这里使用了百度 API ,特此声明,仅可作为演示使用,不能用作商业目的。

        版权全部 foruok,转载请注明出处( http://blog.csdn.net/foruok )。

        这个样例会用到 http 下载、布局管理器、编辑框、button、Json 解析等知识,我们会一一讲解。图 1 是在手机上输入 IP 地址的效果图:


               图1 输入 Ip 地址 

        再看图 2 ,是点击查询button后查询到的结果:


                 图2 IP 归属地查询结果

        好啦,如今我们来说程序。

        项目是基于 Qt Widgets Application 模板创建,选择 QWidget 为基类,具体创建过程请參考《Qt on Android:图文具体解释Hello World全过程》。项目的名字就叫 IpQuery ,创建项目完成后,打开project文件,为 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_OBJECT
    
    public:
        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);
    }
    

        界面布局非常easy,我们使用一个 QGridLayout 来管理 ip 地址编辑框、查询button以及用于显示结果的 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_OBJECT
    public:
        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;
    };
    #endif


        在 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 数据

        依据上面的讲解对比代码,应该一切都非常easy理解了。这里解释下 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),是轻量级的文本数据交换格式,具有自我描写叙述性,easy理解。尽管脱胎于 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 的归属地。好咧,对着这个格式再来看代码,就非常easy了:

        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 系列文章:

  • 相关阅读:
    数据结构基础(21) --DFS与BFS
    数据结构基础(20) --图的存储结构
    数据结构基础(19) --堆与堆排序
    数据结构基础(18) --哈希表的设计与实现
    数据结构基础(17) --二叉查找树的设计与实现
    数据结构基础(16) --树与二叉树
    数据结构基础(15) --基数排序
    数据结构基础(14) --链式队列的设计与实现
    在centOS6.5 上安装使用pipework
    数据结构基础(13) --链式栈的设计与实现
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/4244916.html
Copyright © 2020-2023  润新知