博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Qt on Android: http下载与Json解析
阅读量:6518 次
发布时间:2019-06-24

本文共 10638 字,大约阅读时间需要 35 分钟。

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

    版权全部 foruok,转载请注明出处( )。

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

           图1 输入 Ip 地址 

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

             图2 IP 归属地查询结果

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

    项目是基于 Qt Widgets Application 模板创建,选择 QWidget 为基类,具体创建过程请參考《》。项目的名字就叫 IpQuery ,创建项目完成后,打开project文件,为 QT 变量加入网络模块,由于我们查询 IP 时要使用。如以下代码所看到的:

QT       += core gui network
    项目模板给我们生成 widget.h / widget.cpp ,改动一下代码,先把界面搞起来。首先看头文件 widget.h :

#ifndef WIDGET_H#define WIDGET_H#include 
#include
#include
#include
#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 
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 
#include
#include
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;};#endif

    在 IpQuery 类体中声明了两个 query() 函数,分别接受 QString 和 uint32 两种格式的 ip 地址。还声明了一个 finished() 信号,有指示成功与否的布尔參数 bOK 、输入的 ip 地址 ip 、返回的归属地 area 。最后定义了一个槽 onReplyFinished() ,响应 QNetworkAccessManager 类的 finished() 信号。

    再看 IpQuery.cpp :

#include "ipQuery.h"#include 
#include
#include
#include
#include
#include
#include
#include
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,转载请注明出处(  )。

我的 Qt on Android 系列文章:

你可能感兴趣的文章
objective-c block 旧版详解
查看>>
路径问题
查看>>
Linux基础之常见命令用法(一)
查看>>
stackless python初体验
查看>>
配置Cargo
查看>>
获取当前项目的根目录的方法
查看>>
mount理解
查看>>
从git中永久物理删除私密文件(改写git历史)
查看>>
《SEO字典》解读meta robots标签
查看>>
matplotlib动画入门(1):基本概念
查看>>
为什么使用 Redis及其产品定位
查看>>
rebuild myself rebuild my world
查看>>
创建IOS应用程序通用下的Setting以及读取方式
查看>>
mysql delete|删除 命令的注意点
查看>>
pip: command not found 一系列报错
查看>>
手机端js自动触发音频播放的问题
查看>>
服务器日志
查看>>
干掉ubuntu中的overlay scrollbar
查看>>
五大成功企业家的成功创业箴言
查看>>
How to Upgrade zabbix 2.2 to 2.4
查看>>