百度提供有查询 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_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);
}
界面布局很简单,我们使用一个 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_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 下载的基本步骤:
- 使用 QNetworkRequest 构造请求(包括 URL 和 http header)
- 调用QNetworkAccessManager 的 get() 方法提交下载请求
- 使用 QNetworkReply ,保存与下载有关的一些属性,setProperty() 可以动态生成属性,而 property() 可以把属性取出来
- 响应QNetworkAccessManager 的 finished() 信号处理网络反馈(也可以连接 QNetworkReply 的readyRead() / downloadProgress() / error() / finished() 等信号进行更为详细的下载控制)
至于 http header 的设置,QNetworkRequest 提供了 setHeader() 方法用来设置常见的如 User-Agent / Content-Type 等 header ,Qt 没有定义的常见 header ,则需要调用 setRawHeader() 方法来设置,具体请参考 http 协议,这里不再细说。
好了,现在来看 onReplyFinished() 函数都干了什么勾当:
- 取出提交下载请求时设置的属性(字符串格式的 ip 地址)
- 调用 QNetworkReply::error() 检查是否出错
- 调用 QNetworkReply::attribute() 或者 http 状态码,查看 http 协议本身是否报错(如 404 / 403 / 500 等)
- 读取数据
- 调用 QNetworkReply::header() 方法获取 Content-Type 头部,查看字符编码,如果是 GBK 或者 GB2312 则转换为 utf-8 ( QJsonDocument 解析时需要 utf-8 格式)
- 解析 Json 数据
根据上面的解说对照代码,应该一切都很容易理解了。这里解释下 5 、 6 两个稍微复杂的步骤。
将 GBK 编码的文本数据转换为 utf-8 ,遵循下列步骤:
- 使用 QTextCodec 的静态方法codecForName()获取指定编码是个的 QTextCcodec 实例
- 调用QTextCodec 的 toUnicode() 方法转换为 unicode 编码的 QString 对象
- 调用 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 中有两种数据结构:
- key - value 对的集合,通常称为对象。
- 值的有序列表,通常称为数组。
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:资源文件系统qrc与assets.docxQt on Android:资源文件系统qrc与assets.docx
《Qt on Android 核心编程》PDF版本下载
<<Qt On Android核心编程>> 源码 qt5 android qtcreate
本书以“从零开始也能学会QT on Andriod开发”为目标,基于最新的QT SDK5.2,从QT基本机制讲起,帮助读者建立QT开发的概念
Qt on Android 核心编程Qt on Android 核心编程Qt on Android 核心编程Qt on Android 核心编程Qt on Android 核心编程Qt on Android 核心编程Qt on Android 核心编程
Qt on Android核心编程_完整版 PDF电子书
生成和解析简单的复杂的qtjson数据,比如在QJsonObject中添加QJsonArray数据,和解析这种复杂的数据,代码带有注释 。欢迎大家下载,
Qt on Android核心编程
演示如何在Qt on Android应用中通过JNI调用第三方Jar包
《Qt 基础与Qt on Android入门》课程的源码。
Qt on Android 核心编程源码.7z 我买了这本书,里面是相关的书中代码,因为现在已经找不到了,先上传上来
《Qt on Android核心编程》的示例代码
Qt On Android核心编程 源码
在Qt项目中启动一个监听SD卡插入的Android服务,并通过jni在该服务中发射Qt信号,另外在Qt中通过信号与槽机制成功获取该服务发射的信号。
Qt 实现的http请求,采用POST 方式 ,上传json数据内容的个人测试工具,支持中文转UTF-8的接口
Qt on Android核心编程_完整版 PDF电子书 第二部分
Qt,使用QT自带类,解析Json文件,写Json文件、、、、
在Qt中,把C++的值传送给JAVA,并主动主JAVA中获取值。
Qt on Android核心编程(样章)安晓辉著