Qt on Android http下载与Json解析

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

    百度提供有查询 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;};#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 数据

    根据上面的解说对照代码,应该一切都很容易理解了。这里解释下 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 系列文章:

           

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
这里写图片描述
你好! 这是你第一次使用 **Markdown编辑器** 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block var foo = 'bar'; 

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t &ThinSpace; . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

gantt
        dateFormat  YYYY-MM-DD
        title Adding GANTT diagram functionality to mermaid
        section 现有任务
        已完成               :done,    des1, 2014-01-06,2014-01-08
        进行中               :active,  des2, 2014-01-09, 3d
        计划一               :         des3, after des2, 5d
        计划二               :         des4, after des3, 5d
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图::

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件或者.html文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值