第四章 天气预报
第一节 功能描述
这是一款基于网络的应用,利用App Inventor中的web客户端组件,从互联网上抓取公开发布的信息,并将这些信息以适当的方式呈现出来。这款天气预报应用的数据来源是和风天气网,与开发相关的详细技术文档请参见http://heweather.com/documents/api,应用的具体功能描述如下:
- 根据城市名称查询天气信息:用户在文本输入框中输入城市名称,点击查询按钮,将获得该城市的相关信息;
- 从网络返回的信息分为六类,用户界面上一个时间只能显示一类信息:
- 空气质量指数(不是所有城市都具有该项信息):各种污染物的含量及空气质量的总体评价;
- 城市基本信息;城市的代码、经纬度、及数据更新时间(当地时间及UTC 时间);
- 七日天气预报:包含当天在内的未来七日的天气预报;
- 小时天气预报:当前时刻之后的当日天气预报,每隔3小时有一组数据;
- 天气实况;当前时间区间内的天气状况;
- 生活指数:包括舒适、洗车、穿衣、感冒、运动、旅游及紫外线等七项指数。
- 用户从下拉列表中选择所要显示的信息种类;
- 时钟显示:显示当前的日期及时间(精确到分钟);
- 设置默认选项:用户可以根据需要设置每次应用启动时默认的显示信息,包括:
- 默认的城市;
- 默认的信息种类;
- 保存数据:用户可以保存已经获得的数据,以便在没有网络连接时,也可以查看近期的天气信息;
本章实现前三项功能,后三项功能将在第五章中实现。本章完成之后的应用如图4-0所示。
第二节 预备知识
本书假设读者为初学者,如果你已经对开发网络应用有所了解,可以跳过本节。
一、Web API简介
网络一词早在互联网出现之前就已经存在很久了,我们经常听说的有公路网、铁路网、电力网、燃气及自来水管网等等。从用途上来说,这些网络大致可分为两类:通路网及资源网。举例说明,北京地铁就是一个发达的通路网,网络上流动的是人,流动的方向可以是任意两个站点之间(点对点流动);而电力网就是一个资源网,网络上流动的是电能。无论哪一种网络,都与我们的日常生活息息相关。在物质文明如此发达的今天,人们须臾离不开电,电是一种能源,能源是从它的生产地流向使用者,最终提供给使用者的是建筑物墙壁上的220V交流电插座,这个插座称为电力能源(俗称电源)的“接口”。使用者用一种规格型号相匹配的插头,从插座上取出能源。所谓规格型号相匹配,举例来说,对于国产电器来说,插头的形状及电压都是匹配的,但是从美国购买的电器就不可以直接使用墙上的220V电源(插头形状不同,而且要求110V电压)!
互联网同时具有通路网及资源网的特点,不同的是,互联网中流动的是信息。信息在互联网中的流动既可以是点对点的,例如我们使用的聊天工具,也可以是从信息的生产者流向使用者,如新闻类的网站。我们这里介绍的Web API,它的背后是一个信息源,信息从提供者流向使用者,从这一点上讲,它与资源网相似。例如我们即将开发的应用,天气信息由和风天气网提供,我们要想获得这些信息,就必须有一个规格型号相匹配的“插头”,从和风天气网所提供的“插座”上获取信息,这个“插座”就是我们所说的Web API,而“插头”就是我们要编写的程序(一系列的指令)。
API是英文application programming interface的缩写,译为“应用程序接口”。这里的“接口”是软件开发技术中的术语,你可以将它理解为一个信息“插座”,是一个用于获取信息的“连接装置”。
在软件开发技术中,API还有另外一层含义。通常将程序的使用者划分为两类:人类用户及非人类用户。为人类用户设计的程序要给使用者提供一个可见的操作界面,也就是平时我们在电脑或手机屏幕上看到的画面,其中可能有按钮、图片、文字等等,术语叫做“用户界面”,也就是英文的User Interface,这里的Interface与API中的Interface具有同样的含义。为非人类用户提供的程序被称为API,这里的非人类使用者是网络中某台设备上的另一段程序!在即将完成的天气预报应用中,我们要写一段程序,从和风天气网中抓取数据,因此我们说,我们的应用调用了和风天气网的天气预报API。由于被调用的程序(以及数据)来自互联网,因此这个API被称称作Web API。
对于网络应用的开发者来说,既要熟悉自己即将开发的应用,同时也要了解Web API的调用方法。
二、HTTP协议
互联网的精髓在于信息的传递。在没有互联网的年代里,信息的传递方式有电话、电报及书信等,其中书信是最常见的方式。你写好一封信,把它装在信封里,信封上要写明收信人地址、姓名(很久以前还没有邮编),还要写发信人地址,然后贴上邮票,将信投到离你最近的邮筒中。现在的年轻人,你们没有过这样的经历吧?
信封上收发信人的地址位置是有规定的,如果你给国内的人寄信,收信人地址写在左上方,发信人地址写在右下方,收信人姓名写在中间,而且通常字号要大一些。地址的书写顺序也是有规定的,由大到小,即:某省、某市、某区、某某街道、楼号、房间号;但是如果你的信要寄往美国,信封必须使用英语书写,而且地址的写法正好相反,左上方为发信人,右下方为收信人,要先写收发信人的姓名(其实是名姓),地址的书写顺序从小到大,即:房间号、楼号、街道名称、市名、州名,最后是国名(USA)。
笔者没有去考证邮政系统的这种惯例的由来,但这些对于每个人来说,似乎是天经地义的事,中国邮局和美国邮局各自依照自己的一套约定,扮演者信使的角色,准确无误地将信件从发信人传递给收信人。这里所说的“一套约定”就是“协议”——为了保证信息以正确的方式传递,参与信息传递的各方必须遵守的共同约定。,假设你在寄往美国的信封上,用中文书写地址、姓名,你的信件将会被中国邮局退回(除非你的信封上没有写明发信人地址)。
上面的文字用于说明通信必须遵循相关的约定,才能成功地实现信息的传递。这个结论同样适用于互联网,而且,互联网通信的协议更为复杂,尽管如此,你还是可以从互联网通信的协议中,看到传统通信协议的影子。
HTTP协议是网络(信息)传输协议中的一种,为称为“超文本传输协议(HyperText Transfer Protocol)”,为了理解这7个汉字之间的关系,我们引入另一种网络传输协议——FTP协议,也称为“文件传输协议(File Transfer Protocol)”。比较这两个协议的名称,你会发现它们传递的信息的形式不同,一个超文本,另一个是文件。文件是我们所熟悉的,它们可能是文本、图片、声音、视频等等,FTP协议不关心所传递文件的具体内容,只关心数据的完整性;而HTTP协议关注的恰恰是所传递的内容。所谓“超文本”也是一种文本(文字及符号),与普通的文本比较,它们的差别是,超文本中的部分文本带有链接,这些链接指向另外一些超文本。因此HTTP从本质上关注的是文本的传输,即内容的传输。
在信息的传输过程中,有两个角色是最重要的,它们是信息的提供者以及信息的请求者。可以将传输协议理解为两个角色之间进行对话时使用的一种语言,它设定了对话过程中两者之间的关系,并规定了对话的语言要素(名词、动词、数量词等)以及语法规则(主谓宾定状补等),以下是协议中与本应用相关的内容:
- 角色:
- 服务器:在网络通信中,提供信息的一方被称为服务器;
- 客户端:在网络通信中,向服务器发出信息请求的一方被称为客户端;
- 词汇:
- 资源:服务器上供客户端访问的信息统称为资源,包括数据及文件。
- URL(Uniform Resource Locator):统一资源定位符,俗称网址,是服务器上某项资源的具体位置,网址中只允许使用ASCII 字符,非ASCII字符必须经过编码才能使用在网址中(现在的浏览器可以将非ASCII码自动转换为ASCII码)。
- 请求:每次通信均由客户端发起,请求的目标可能是数据或文件;请求时用URL来说明请求的目标,并按照信息提供方(服务器)的要求设置请求方法及请求的首部信息;
- 请求方法:常用的方法有GET、POST、PUT:
- GET:用于向服务器端请求数据,并读取返回结果,GET方法可以使用参数来说明请求的具体内容;
- POST:用于向服务器提交数据,并获得数据处理的返回结果;
- PUT:用于向指定位置上传资源
- 请求首部:采用键值对列表的方式提供请求中URL以外的相关约定信息;
- 响应:服务器端在收到来自客户端的请求之后,根据请求的内容予以回应;
- 规则(由信息提供方制定):
- 请求的格式:规定了请求指令的内容及格式;
- 响应的格式:规定了返回信息的内容及格式。
就以我们即将访问的和风天气API为例。首先来看一个最简单的请求,一个表示天气状况的图片,文件名为100.png,具体的访问地址如下:
http://files.heweather.com/cond_icon/100.png
我们将上述地址直接输入到浏览器的地址栏中,并按回车,这等于向服务器发出了一个请求,我们会得到服务器的响应,并获得请求的目标,如图4-1所示,是一个长宽均为100像素的图片。
以上操作完成了一次请求与响应,在浏览器地址栏中输入的网址就是客户端发出的请求指令,其中“http://” 表示此次请求遵从http协议,“files.heweather.com”表示服务器的地址,“/cond_icon/“表示被访问的资源在服务器中的位置(文件夹),”100.png”代表被访问资源的名称。这次请求返回的信息正是我们所请求的图片,它显示在浏览器中。
上面是一次简单的请求响应,它访问的是一个公开的资源,任何人只要在浏览器中输入网址,都可以得到这个回应。我们再来试试下面的网址:
https://api.heweather.com/x3/weather?city=北京
同样在浏览器地址栏中输入上述网址,看看会得到怎样的响应。
先来比较一下两次请求中网址的变化:
表4-1 比较两次请求的网址
注意到第二次的网址中多出一个“?”以及问号后面的“city=北京”,这其中暗含了以下内容:
- 此次请求的目标不是文件,而是数据;
- 此次请求采用的方法是GET,因为GET方法允许以“?”标志提供数据的查询条件,“?”后面的内容是具体的查询条件,也称为参数;
- 这次请求中只有一个查询条件,即city=北京,其中“city”为参数名,是数据提供方规定的,“北京”是参数值,由请求方设置;
- weather是数据提供方提供的服务,其背后是一段程序,你可以将其理解为App Inventor中的过程,它可以被网络用户访问,而city就是这个过程的参数,要调用这个过程,就必须提供必要的参数。
因此,第二个网址请求的是北京的天气预报信息,现在来看看服务器返回的结果。这是一段JSON数据(稍后将解释JSON数据),内容是错误提示,其中的 {“status”:“invalid key”},意思是{“状态?”:“非法的钥匙”}。稍后我们再解释这条信息的含义,下面我们再来试试输入下面的网址(在网址中隐去了四个字符):
https://api.heweather.com/x3/weather?city=北京&key=e****98b16c14abc8b518f7789fbd755
将得到下面的响应:
这一次的请求是成功的,因为我们按照信息提供方的要求提供了必要的数据,下面我们来解释一下网址中的内容,如图4-4所示。
图中已经给出了标注,与图4-2中输入的网址相比,这里包含了两个参数:city及key,其中的city用于指定请求天气预报的城市,第二个参数key是应用开发者的钥匙,术语称为密钥,是笔者为了开发这个应用,向信息提供方申请的一个API使用权,凡是用这个密钥发出的数据请求,都被视为笔者的行为,因此为了安全起见,这里隐去了其中的部分字符。
密钥的申请非常简单,只要登陆http://heweather.com ,注册一个账号,就可以获得一个密钥,如图4-5所示。建议读者现在就去申请一个密钥,以便在后续的开发中,可以获取到必要的数据。
现在你该明白。为什么表4-1中的第二个网址没有获得响应,因为API规定了数据请求的格式,在URL中必须包含两项信息:请求的城市以及请求者的密钥,这两个条件缺一不可。当然,API中还规定了可选的其他参数,如城市代码、城市ip等,有兴趣的读者可以访问http://heweather.com/documents/api 查看具体的参数设置要求。
通过上述例子,我们了解了向Web API请求数据的方法。HTTP协议仅仅为通信规定了一个框架,即,规定了对话的参与者、对话中使用的语言要素及语法规则,但是不涉及对话的具体内容,比如资源的名称、参数的个数及名称等,这些具体的内容由API提供方设定。因此,如果我们想从某个API提供方处获得资源,就要仔细阅读API提供方的开发文档,并依照文档中的说明设置自己的数据请求指令。
三、Web客户端组件
在App Inventor中有一个Web客户端组件,在组件面板的通信连接分组中,将该组件拖入预览窗口,它将落在预览窗口下方的非可视组件区,自动命名为Web客户端1。切换到编程视图,点击web1客户端打开它的代码块抽屉,将出现一个长长的代码块列表。在天气预报应用中,我们将用到其中的五个代码块,如图4-6所示。
从功能上讲,Web客户端组件与浏览器相类似,遵从HTTP协议与Web服务器进行通信,只是它不提供用户界面。在App Inventor中,需要在编程视图中设置Web客户端组件的网址等属性,然后向服务器发出请求,并处理服务器返回的数据。下面简述图4-6中几个代码块的功能:
- 设Web客户端的网址为:用于设置将要访问的API的网址(包括资源名称及参数),在本应用中,将使用图4-4中的网址请求数据。
- 让Web客户端编码指定文本:将非ASCII码转换为ASCII码。前面说过,浏览器可以将网址中的非ASCII码自动转换为ASCII码,但Web客户端组件目前尚不具备这一功能,因此需要用该块进行编码的转换。
- 让Web客户端执行GET请求;以GET方式向服务器发出请求;
- 当Web客户端收到文本时:当服务器对请求做出响应,并将数据返回给请求者时,触发该事件;
- 让Web客户端解析JSON文本:对返回的JSON格式的数据进行处理;
对于初学者来说,使用Web客户端组件访问Web API有两个难点,首先是对请求指令的设置,有些API只要求正确地设置网址,有些API还需要设置请求消息首部(也称请求头),要仔细阅读API提供方编制的开发文档,才能正确设置请求信息,保证请求的成功。其次,是对返回数据的处理。Web API通常以JSON或XML的格式返回数据,因此Web客户端组件提供了解析这两类数据的功能,但依然要小心地分析返回数据的结构,才能将信息合理地呈现出来。此外,通常利用Web API开发应用,开发者需要向API提供方申请开发权限,也就是申请密钥,具体的申请方法都可以在API提供方的文档中找到。
四、JSON数据简介
JSON是英文JavaScript Object Notation的缩写,译为“JavaScript对象符号”,用于表述一种机构化的数据,被描述的事物被称为“对象”,被描述事物的特征被称为对象的“属性”。举例说明,描述一个人的社会属性的数据可以这样写:
{“姓名”:”张三” , ”工作单位”:”百度” , ”职务”:”客户经理” }
如果同时还要描述这个人的自然属性,数据可能会写成这样:
{“社会属性”:{ “姓名”:”张三” , ”工作单位”:”百度” , ”职务”:”客户经理” }, ”自然属性”:[ ”性别”:”男” , “身高”:175 , ”年龄”: 30]}
注意观察这些数据的特征,可以归结为下面几点:
- 这些数据的最外层被一对大括号包围,而且外层大括号中还可以嵌套内层的大括号;
- 最基本的数据单元由两部分组成:数据标记及数据本身,也称为属性及属性值,或者简称为键与值,以下将称其为键与值。例如“姓名”就是键,而“张三”就是值,键与值之间用英文的冒号(:)分隔;
- 各项数据之间以英文的逗号(,)分隔;
- 值也可以是一对大括号包围的多项数据,即,值也可以是对象;
- 值也可以是一对方括号包围的多项数据,与大括号相比,用方括号表示的数据被称为数组,对应于App Inventor中的列表,这种表示方法强调数据项之间的共性:结构相同或者等级相同;
- 键用引号包围,值如果是字符,需要用引号包围,如果是数字,则无需用引号包围。
下面来看一段天气预报的真实数据——9月28日下午四点钟之后的3小时天气预报:
{"hourly_forecast":[
{"date":"2015-09-28 16:00",
"hum":"77",
"pop":"18",
"pres":"1024",
"tmp":"18",
"wind":{"deg":"99","dir":"东风","sc":"微风","spd":"6"}},
{"date":"2015-09-28 19:00",
"hum":"82",
"pop":"6",
"pres":"1025",
"tmp":"16",
"wind":{"deg":"107","dir":"东南风","sc":"微风","spd":"3"}},
{"date":"2015-09-28 22:00",
"hum":"84",
"pop":"1",
"pres":"1026",
"tmp":"16",
"wind":{"deg":"79","dir":"东风","sc":"微风","spd":"3"}}]}
这是整个天气预报数据中的一项,键为hourly_forecast(小时预报),值为列表,包含了3个列表项,即,三个对象,分别是16点、19点及22点的天气预报,由3对大括号包围,中间以逗号分隔;三项数据中的每一项数据中都包含了六项数据,即,每个对象包含六个属性,分别是日期(date)、湿度(hum)、降水概率(hum)、气压(pres)、温度(tem)以及风势(wind),其中风势的值又是一个对象,包含了四个属性,分别是角度风向(deg)、风向(dir)、风力(sc)以及风速(spd)。
了解JSON数据中各种符号的意义,是为了帮助我们理解数据之间的关系,理清数据的结构,这样我们才能正确地呈现这些数据。
五、将JSON数据转为列表数据
App Inventor并不能直接对JSON数据进行处理,但是Web客户端组件提供了一个内置的过程,可以将JSON数据转换为列表,这样,我们就可以利用列表的数据处理能力,通过程序来实现数据的呈现。如前一个标题中介绍,“让Web客户端解析JSON数据”块就是用于这个目的,解析之后的数据如下:
数据中紧邻括号的上标数字是笔者标注的,以显示列表的层级。为了形象地说明以上数据之间的关系,我们利用App Inventor的列表将这些数据表示出来,如图4-7所示,其中最内层的列表采用了内嵌式的显示方式,为了使截图可以短一些。
在图4-7中,共有六级列表。最外层列表(一级列表)有两个列表项,第一项为键“hourly_forecast”,第二项为值——一个二级列表;二级列表中包含3项,每一项为一个三级列表;每个三级列表包含6项,每一项为一个四级列表;每个四级列表中各包含2项,分别为键与值;第6个四级列表中的第2项为五级列表,其中包含4个列表项,每一项为一个六级列表;每个六级列表中包含2项,分别为键与值。
可以将上面列表分为三类:键值对、项列表以及键值对列表。键值对指的是列表中只有两个列表项,其中第一项为键,第二项为值,图4-7中的一级、四级、六级均为键值对;项列表指的是列表中的各个列表项地位相同,均为数据(单个值或列表),图4-7中的二级、三级、五级均为项列表;键值对列表指的是列表中包含的列表项为键值对,图4-7中的三级、五级均为键值对列表。
对列表进行分类,目的在于在后续的讲解中,针对不同类型的列表,采取不同的处理方式。
这个列表只是为了说明天气预报数据中的数据结构,完整的天气预报数据共有10级列表,结构还要复杂,这非常考验我们对数据结构的把握能力,我们将在应用的开发过程中逐一讲解针对每一类列表的处理方式。
六、App Inventor处理键值对列表
如上所述,天气预报数据中包含键值对列表,App Inventor提供了一个针对键值对列表的查询语句,在本应用将使用该语句,从复杂的数据中提取所需信息。为了更好地理解键值对列表及其查询方法,我们先来创建一个简单的键值对列表——查询项键值对列表,如图4-8所示。
注意这个列表的内层列表采用了内嵌式的显示方式,以便更好地利用页面宽度,并节省页面高度。内层列表的第一项为键,分别与天气预报数据中的键相对应,内层列表中的第二项为值,是与键相对应的中文含义。对于这样的列表,我们可以通过对键的查询来获取值,如图4-9所示。
我们创建一个名为“天气预报”的项目,来测试一下查询结果,如图4-10所示。
图中利用屏幕的标题属性来显示查询结果,与键“daily_forecast”相对应的值为“七日天气预报”,测试结果与我们预想的一样。
第三节 请求数据
一、用户界面设计
比起前几章的应用,这个应用的复杂程度稍稍高一些,这里说的复杂指的是编写程序的难度较大,相比之下,应用中使用的组件却很简单,本应用中共使用7个组件,其中5个可视组件5个,2个非可视组件,如表4-2所示,组件的排列如图4-11所示。
表4-2 应用中的组件
暂时不添加列表选择框组件,后面用到时再添加。
组件属性设置:
- Screen1的标题属性设为“天气预报”;
- 勾选Screen1的“允许滚动”属性,以便在手机上查看完整的显示信息;
- 水平布局组件宽度为“充满”;
- 城市输入框的宽度为“充满”;
- 请求数据按钮组件的显示文本设为“查询”;
- 天气预报标签的显示文本属性设为“天气预报…”
由于Web API返回的数据格式相当复杂,即便是允许屏幕滚动,在手机屏幕上也很难查看数据的全貌,因此我们需要借助于文件管理器组件,将返回的数据保存成文本文件,并在电脑中打开文件,进行数据结构的分析。
二、请求数据
对于一个实战的开发者来说,第一步要登陆和风天气网,从网站的API文档中复制相关的网址以及开发者密钥。如果你尚未申请密钥,现在请立即申请,否则,后续得操作都无法实现。如图4-12所示。
第二步,切换到编程视图,定义两个全局变量,来保存Web API的网址片段,如图4-13所示。
第三步,在按钮点击事件中,拼出完整的API访问网址,并发出数据请求;在Web客户端的收到文本事件中,用天气预报标签来显示收到的数据。代码如图4-14所示。
第四步,测试:在城市输入框中输入汉字“北京”,点击按钮,很快就有数据返回,如图4-15所示。
返回的是JSON格式的数据,包含一个键值对,键为“HeWeather data service 3.0”,值为一个只有一项的列表,由方括号包围,项的值为一个键值对,键为“status”,值为“unknown city”。这是API返回的查询结果,告知调用者,请求的城市名称不存在。
出现这一问题的原因是在请求数据的网址中出现了非ASCII字符——汉字“北京”。在介绍HTTP协议时曾提到,网址中只允许使用ASCII码,如果需要使用汉字,必须先将汉字进行编码。Web客户端组件提供了对字符进行编码的功能,修改后的代码如图4-16所示。
三、将数据保存为文件
为了使用App Inventor的列表来处理收到的数据,我们使用Web客户端的解析JSON文本功能,将收到的JSON格式的数据转为列表格式。为了能够在电脑屏幕上查看数据,我们将列表数据保存到手机上,取名为weather.txt,代码如图4-17所示。
测试:这次我们在城市输入框中输入“天津”,手机屏幕上出现一整屏都容不下的数据,如图4-18所示。
此时,说明我们请求数据的操作是成功的,并且数据已经以文本文件的形式保存在手机中,下面来分析这些数据的结构及内容。
第四节 数据结构分析
设法打开手机上的文件夹,在AppInventor\data文件夹中,找到weather.txt文件,复制到电脑中,并用记事本或其他文本编辑软件打开该文件。前面提到过,完整数据是一个10级的列表,我们必须对列表的结构有清醒的认识,才能正确地从列表中提取数据,并合理地将数据呈现出来。就像剥洋葱一样,我们自外而内一层一层地将列表剥开,看看这些数据究竟是如何组织起来的。下面是最外面五层的结构,依然在紧邻括号的位置用上标数字表示列表的层级。
在以上的五层结构中,第一层列表中只有1项,是第二层列表;第二层列表中有2项,分别为键和值,其中的键为“HeWeather data service 3.0”,值为第三层列表;第三层列表中也只有1项,是第四层列表;第四层列表中有7项,每一项为第五层列表。直到第五层才开始出现我们需要的数据,下面我们用表格的形式将上述结构加以呈现,如表4-3所示。
表4-3 最外面五层的列表结构
附表4-3-1 键的含义
可以看到,在第四层列表中包含了七个项,每一项都是键值对,这正是我们要分别加以呈现的分类数据。下面我们将针对第五层的七个列表分别进行剖析,来理清每个列表的内部结构。
第一项:空气质量指数
数据内容如下:
我们依然用表格来显示数据的结构,如表4-4所示。
附表4-4-1:键的含义
观察表4-4中数据的结构,我们将列表划分为以下四种类型:
- 值为文本的键值对:列表中只包含两项,第一项为键,数据类型为文本,第二项为值,数据类型也为文本,如第九层中的列表;
- 值为列表的键值对:列表中只包含两项,第一项为键,数据类型为文本,第二项为值,数据类型为列表,如表中第五层及第七层中的列表;
- 单项列表:列表中仅有1项,且该项也为列表,如表中的第六层;
- 多项列表:列表中包含多个列表项,且每一项均为列表,如表中的第八层。在本应用中,所有的多项列表均为键值对列表——每个列表项都是一个键值对,这个键值对的值可能是文本(如表4-4中的第9层),也可能是列表(如表4-3中的第5层)。
对列表进行如此分类的目的,是为了下一步针对不同类型的列表采用不同的处理方式。
第二项:城市基本信息
数据内容如下:
表4-5 城市基本信息的数据结构
附表4-5-1 键的含义
你可以分析一下表4-5中的列表类型,看是否超出了上面分析过的四种。是的,第6层有些复杂,6个列表项中,有5个值为文本的键值对,1个值为列表的键值对,但基本类型不超过之前定义的4种。
第三项:七日预报
数据内容如下:
表4-6 七日预报的数据结构
附表4-6-1 键的含义
在表4-6中,第七层共包含10个项,它们是第八层列表,全部是键值对,其中6项的值为文本,另外4项的值为列表。第九层列表中包含的项数不等,以wind为例,以wind为键的值是列表,其中包含了4个列表项,而且都是值为文本的键值对。
第四项:小时预报
数据内容如下:
表4-7 小时天气预报的数据结构
第五项:天气实况
数据内容如下:
表4-8 天气实况的数据结构
附:键fl的含义是体感温度。
第六项:数据状态
数据内容:。数据状态是一个键值对,表示向API请求数据的返回结果,如果为OK, 表示API工作正常,此前我们还接收到“invalid key(错误的用户密钥)”以及“unknown city”(未知城市),这些都是数据状态。
第七项:生活指数
数据内容如下:
表4-9 生活指数信息的数据结构
附表4-9-1 键的含义
支持上述表格的完整数据见附表。
第五节 呈现一组简单的数据——空气质量指数
一、提取分类信息
我们要做的第一件事,就是将第五层列表中的数据提取出来,放在一个列表中,并按照某种固定的顺序排列,比如,我们希望六项数据的顺序为:天气实况、小时预报、七日预报、空气质量、生活指数及城市信息。在正式开始编写代码之前,我们先来做一次“不插电”的编程,即,在纸上写出程序的思路。
我们要处理的数据如下:
假设我们用列表变量“第一层”来保存经过解析之后的json数据:
我们通过以下步骤来提取所需信息,并将最终解析出来的数据放在全局变量“分类信息列表”中:
- 设列表变量 第二层 = 第一层列表中的第1项(第一层共1项)——第二层列表包含2项;
- 设列表变量 第三层 = 第二层列表中的第2项(第二层共2项)——第三层列表包含1项;
- 设列表变量 第四层 = 第三层列表中的第1项(第三层共1项) ——第四层列表包含7项,为键值对列表(列表中的项均为键值对);
- 向分类信息列表中添加列表项:
- 在第四层列表中查找键为now的值,并将其添加到分类信息列表中;
- 在第四层列表中查找键为hourly_forecast的值,并将其添加到分类信息列表中;
- 在第四层列表中查找键为daily_forecast的值,并将其添加到分类信息列表中;
- 在第四层列表中查找键为aqi的值,并将其添加到分类信息列表中;
- 在第四层列表中查找键为suggestion的值,并将其添加到分类信息列表中; * 在第四层列表中查找键为basic的值,并将其添加到分类信息列表中;
完成以上操作后,分类信息列表中就有了我们将要呈现的数据。现在,让我们回到App Inventor的编程视图中,来实现以上的操作。首先声明一个全局变量“分类信息列表”,然后创建一个过程,取名为“提取分类信息”,参数为“第一层”,具体代码如图4-19所示。
以上代码是采用最直接的方式从原始数据中提取信息,这个方法显得有些笨拙,但是非常可靠且有效。
二、显示单项信息
我们将在Web客户端1组件的收到文本事件中,调用“提取分类信息”过程,在该过程执行完成之后,分类信息列表中就有了我们需要的数据,我们就利用这个列表来显示空气质量指数,代码如图4-20所示。
注意,因为我们已经获得了想要的数据,并完成了对数据结构的分析,因此在后续的开发中不会再用到文件管理器组件,此时,可以将该组件从项目中删除,与其相关的代码也将被自动删除。经测试,程序的运行结果如图4-21所示。
图中直接将列表数据显示在标签中,目的是为了验证我们是否正确地提取了数据。
三、规范信息的显示格式
普通人很难理解图4-19中的数据,即便可以猜出某些键的含义,但毕竟我们开发的是一款信息类应用,而不是一款解谜游戏。我们要用普通人看得懂的方式来显示这些信息,为此要做到以下几点:
- 将键值对中的键替换为对应的汉字(如co替换为一氧化碳);
- 为数据提供简要说明,如,污染物指标的含义(1小时的平均值,单位为ug/m³);
数据分行显示,每行只显示一项指标,具体格式如下;
城市数据: 空气质量指数:46 一氧化碳:0 … 二氧化硫:2
为此我们创建一个过程,名为“显示列表数据”。不过首先需要创建一个静态列表,将数据中包含的键与中文的含义对应起来,我们用键值对列表来存放它们,如图4-22所示。
然后再声明一个全局变量“显示文本”,来保存我们想要显示的内容,最后来创建过程,如图4-23所示。
首先要强调的是,上述过程使用了递归调用,即,如果列表项本身也是列表,则以列表项为参数,再次调用该过程。这个方法对于解析这种复杂结构的数据是非常有效的。
在整个过程中,先对列表项进行遍历,如果列表项是列表,则递归调用本过程;如果列表项不是列表,则有两种可能:①列表项为键值对的“键”,②列表项为键值对的“值”;如果是“键”,则查询空气质量字段列表,将键对应为相应的汉字,与“显示文本”进行合并,如果是“值”,则直接将“值”合并到“显示文本”中。
最后,在Web客户端1的收到文本事件中调用该过程,并用标签显示结果,如图4-24所示。
顺便提一句,今天是2015年10月7日,十一黄金周的最后一天,北京已经是连续第三个雾霾天了。
四、显示格式的改进
对于这样的显示结果(指的是格式而非内容),你满意吗?好像有一点问题,如果能够让标题(城市数据)与内容在层次上加以区分,就更好了,比如,内容缩进1个字符。我们来试试看,能否实现这一点。
先来创建一个有返回值的过程“字符缩进”,过程的参数为“缩进数”,返回值为缩进的空格字符,这里假定每一级的缩进量是四个空格。代码如图4-26所示。
然后对“显示列表数据”过程进行三项改造:①添加一个参数“缩进数”;②当过程处理的列表为键值对,且键值对中的值为列表时,让缩进数增加1;③在每一行的行首(键所对应的汉字之前)添加缩进字符,具体代码如图4-27所示。
最后,在Web客户端1的收到文本事件中,为“调用显示列表数据”块提供参数“缩进数”的值,设值为0,如图4-28所示。
测试结果如图4-29所示。
第六节 选择显示各类信息
我们已经实现了对数据的分类提取,并能够显示其中一组简单的数据,现在我们将数据显示功能扩展到所有类别的数据,使得用户可以根据需要选择自己想查看的数据,包括六个大类:天气实况、小时天气预报、7日天气预报、空气质量指数、生活指数以及城市基本信息。为了实现这一功能,我们需要准备一些基础的数据,目的是为了将数据中的“键”对应为汉字,像 “空气质量字段” 列表那样。
一、基础数据准备
(1)七日预报字段列表:
这些键所对应的汉字来自和风天气网的API开发文档,如果开发者觉得汉字的表述还可以更好,可以将这些汉字替换为自己认可的词。
(2)小时预报字段列表:
(3)天气实况字段列表
(4)生活指数字段列表
(5)城市信息字段列表
(6)字段总表
连同“空气质量字段”在内的六个键值对列表,将被放在另一个列表——字段总表中,作为其中的一个列表项。这项操作需要在屏幕初始化时完成,如图4-35所示。
注意:字段总表中各个字段列表的排列顺序不是随意安排的,而是与分类信息列表中项的排列顺序相一致(见图4-19),并且与下面的信息类别列表中项的顺序相一致,如图4-36所示,稍后你将看到这样排列的好处。
以上列表均为静态列表,也就是说,在程序的运行过程中,列表内容自始至终保持不变。像这样的静态信息,还有另一种设置方法,即,将列表内容输入到excel表格中,并导出为csv文件,再导入到项目中,解析为不同的列表。本章的学习目标是Web API的访问以及数据的处理,因此将数据直接填写在列表中,以简化叙述过程。
二、显示分类信息
为了让用户能够选择所要显示的信息种类,我们要在用户界面上添加一个列表选择框组件。列表选择框的备选项来自于列表“信息类别”,当用户从选择框中选中了某一项时,将触发列表选择框的完成选择事件,我们将在这个事件处理程序中,完成对显示信息的设置。
首先,添加列表选择框组件,并设置其相关属性:
在设计视图中,将列表选择框组件放在水平布局1下方,并设置以下属性: 设显示文本属性为“请选择要查看的信息”; 设宽度属性为充满; 在编程视图中,在屏幕初始化程序中,设置列表选择框的列表属性为信息类别列表,如图4-37所示。
此时,当用户点击列表选择框时,将看到一个可供选择的列表,当用户选中其中的某一项时,将触发列表选择框的完成选择事件。这里有一项非常重要的数据,即,列表选择框的选中项索引值,该属性值中保存了用户的选择结果,这个值将作为我们后续操作的唯一线索。为了正确地显示数据,我们需要做如下两件事情:
(1) 改造“显示列表数据”过程:此前该过程只是用于显示空气质量指数信息,因此数据中的“键”从空气质量字段列表中查找,现在我们需要根据用户的选择,从不同的字段列表中查询这些“键”,具体代码如图4-38所示。
(2) 编写列表选择框的完成选择事件处理程序,代码如图4-39所示。
下面我们来测试一下,点击列表选择框,并选择第一项——实况天气,显示的结果如图4-40所示。
三、纠错与改进
测试过程中发现若干问题,按照问题出现的顺序叙述如下:
(1)在城市输入框中输入城市名,点击查询按钮后,在App Inventor的编程视图中出现错误提示,如图4-41所示。
错误提示的内容是:在列表中选择了索引值为0的项。问题的原因在于列表选择框的选中项索引值的默认值为0。当Web客户端收到数据时,程序从分类信息列表中选取了索引值为0的列表项,这是一个非法的操作,因为列表项的索引值最小值为1。
解决这一问题的方法是为列表选择框设置一个不为零的默认选中项索引值,你可以假设用户希望每次打开应用都会显示某项他最关心的信息,例如实况天气信息,因此可以暂时将选中项索引值设为1,在屏幕初始化程序中完成此设定。如图4-42所示。
(2)测试手机上显示的信息中,与“键”相关的汉字全部显示为“找不到”。问题出在Web客户端收到文本事件中。我们在屏幕初始化时,将列表选择框的选中项索引值设为1,这意味着我们将要显示的数据为“分类信息列表”中的第一项,即天气实况信息,因此“显示列表数据”过程在处理天气实况信息时,将从字段总表的第一项,即天气实况字段列表中查询键所对应的汉字,但是在Web客户端收到文本事件中,默认要查看的数据依然是“分类信息列表”中的第4项,即,空气质量指数信息,因此,在天气实况字段中无法找到与空气质量指数对应的键,结果就是所有的键都显示汉字“找不到”。这里应该将“4”改为列表选择框的选中项索引值,这样才能保正为这些键找到对应的汉字说明。修改后的代码如图4-43所示。
(3)打开列表选择框后,列表上方的标题栏上显示的是“AI伴侣”,如图4-40所示,原因是我们没有设置列表选择框组件的标题属性,回到设计视图,将该组件的标题属性设置为“天气预报信息分类”。
(4)注意观察手机上显示的天气实况信息,信息的缩进是正确的,但是在段落的划分上,显得混乱,例如“风势”的标题与内容之间距离太远,这个可以通过将“\n\n”改为“\n”加以解决。
(5)当用户从列表选择框中选中某一项之后,列表选择框关闭,选择框按钮上的显示文本依然是“请选择要查看的信息”,我们希望此时按钮上显示用户选中的项,因此在选择完成事件中添加代码,设置文本选择框的显示文本属性为列表选择框的选中项;同时在屏幕初始化程序中也添加此代码。这可以让使用者心中明了当前显示的信息种类。代码如图4-44,运行效果如图4-45所示。
(6)从图4-43中可以看出另一个问题——数据的显示分不出段落,不同日期的数据之间应该空一行,以方便查看某一天的完整数据。修改显示列表数据过程可以实现这一点:在列表循环的最后一行添加一个条件判断,如果当前处理的列表为单项或多项列表、且缩进数为0、且当前正在处理的项为列表的最后一项时,添加空行。代码如图4-46所示。
这样的限定条件并不能实现生活指数数据的分段显示,读者可以自行设定更为具体的判断条件,来实现数据的显示。
除了上述的6个问题,还有一个更为棘手的问题,那就是在七日预报信息中,天气状况信息中出现了类似100、101这样的代码,这其实是需要用图片来显示的信息,如图4-47所示,我们将在下一章中实现这个版本的天气预报应用。
附表:天气预报的完整数据