解析天气预报数据
界面布局完成以后,就需要设计代码来控制界面上各个元素的逻辑了。
- 从网络获取天气预报数据;
- 解析获取的数据;
- 根据解析的结果更新
天气预报
界面;
天气预报的数据依赖于网络端的服务器,不是手机端的应用开发者自己能决定的。假如应用和网络服务器是两拨人同时在开发,极有可能出现这样的情况:手机端需要获取数据的时候,服务器端还没有准备好。
遇到这种情况该怎么办呢?
我们可以自己构造
一个假数据,模拟已经获取到真实数据的情形。当然,这样的假数据从格式到内容要尽量和网络端提供的真实数据一致。两者越是相同,后面切换到真实数据时,所需要做的代码修改就越简单。
所以在实际的开发项目中,应用开发者和网络开发者会事先拟定一个数据协议
,应用开发者看到这个协议
就知道假数据如何构造了,而不用等着网络端开发者的工作了。网络端开发者也会严格根据这个协议
来开发,不然将来手机端同网络端的配合就是鸡同鸭讲。
这里我们先假设已经获取道了网络上的天气预报数据,看看如何来解析数据,并更新天气预报界面
。
数据格式
现在应用端(客户端)与网络端(服务器端)之间的数据交换,通常会使用两种格式的文本内容:XML
与JSON
。
比如通过网络获取一个班级学生的信息(假设包含学生的姓名、年龄、性别等3个内容)。
姓名 | 年龄 | 性别 |
---|---|---|
赵一 | 15 | 男 |
钱二 | 14 | 女 |
孙三 | 16 | 女 |
李四 | 12 | 男 |
* 使用XML
来传递信息,可能的内容就是:
```xml
<students>
<student>
<name>赵一</name>
<age>15</age>
<sex>男</sex>
</student>
<student>
<name>钱二</name>
<age>14</age>
<sex>女</sex>
</student>
<student>
<name>孙三</name>
<age>16</age>
<sex>女</sex>
</student>
<student>
<name>李四</name>
<age>12</age>
<sex>男</sex>
</student>
</students>
```
每个节点`<>`代表一个数据。
使用
JSON
来传递信息,可能的内容就是:{ "students": [ { "name":"赵一", "age":"15", "sex":"男" }, { "name":"钱二", "age":"14", "sex":"女" }, { "name":"孙三", "age":"16", "sex":"女" }, { "name":"李四", "age":"12", "sex":"男" } ] }
XML
与JSON
相比,JSON
格式的数据占用的空间更小,表达方式更简洁一些。所以JSON
似乎更受开发者的欢迎。
我们这里采用的就是JSON
格式的数据。
JSON初步
JSON
格式的理解也很简单。
数据由
名称
和取值
构成,例如"name":"李四"
,- 它们由
:
分隔开,并且用"
括了起来(对于数值型的取值可以不用引号扩起来,例如"age":12
,但为了简化大家记忆的规则,都还是扩起来吧); :
后面可以跟[]
,也可以跟{}
;不同的括号,代表数据的不同类型;
"name":"李四" "age":"12" "sex":"男"
- 它们由
使用
{}
,里面包含的是同一个事物的不同项,{ "name":"李四", "age":"12", "sex":"男" }
使用
[]
,里面包含的是同一类事物,内部会有多个平级的数据项;"data": [ { "name":"李四", "age":"12", "sex":"男" }, { "name":"李三", "age":"11", "sex":"男" }, { "name":"李五", "age":"13", "sex":"女" } ]
天气数据
根据之前的功能规划,我们确定了网络数据的格式,拿出一个实实在在的例子感受一下吧:
{
"error_code": "0",
"data": {
"location": "成都",
"temperature": "23°",
"temperature_range": "18℃~23℃",
"weather_code": "5",
"wind_direction": "东南",
"wind_level": "1级",
"humidity_level": "30%",
"air_quality": "良",
"sport_level": "适宜",
"ultraviolet_ray": "弱",
"forcast": [
{
"date": "明天",
"temperature_range": "18℃~23℃",
"weather_code": "0"
},
{
"date": "星期六",
"temperature_range": "17℃~21℃",
"weather_code": "1"
},
{
"date": "星期日",
"temperature_range": "19℃~24℃",
"weather_code": "3"
},
{
"date": "星期一",
"temperature_range": "16℃~22℃",
"weather_code": "4"
},
{
"date": "星期二",
"temperature_range": "20℃~26℃",
"weather_code": "2"
}
]
}
}
可以看到整个数据分成了两个大的部分,
error_code
:网络端服务器返回的错误代码,假如服务器发现自身有问题,可以通过这个字段的数值告诉客户端。客户端收到返回值以后,首先要检查这个字段是否为0
。对于非0
值,我们就要警惕了,它说明data
字段的取值有可能是无效的。不过你也要记住,这个返回的
JSON
内容,都是应用开发者和网络开发者协商好的,你们也可以不设计error_code
这个字段。但是目前大家已经形成了一种默契,大都将这个字段作为JSON
数据的标配。data
:携带我们真正关心的实际数据,所有天气相关的数据都放在这个字段当中。
接下来的分析,我们将集中于data
字段。
天气详情数据
天气详情数据需要使用到如下内容,
"location": "成都",
"temperature": "23°",
"temperature_range": "18℃~23℃",
"weather_code": "5",
大部分数据的取值就是我们要显示到界面上的内容,这很简单。例如,
JSON字段名称 | JSON字段取值 | 界面显示 |
---|---|---|
location | 成都 | 成都 |
temperature | 23° | 23° |
temperature_range | 18℃~23℃ | 18℃~23℃ |
weather_code
字段,取值是数值,不同的数值,代表了不同的天气状态,
取值 | 天气状态 | 应用显示对应的图标 |
---|---|---|
0 | 晴 | R.mipmap.ic_sunny_l |
1 | 雨 | R.mipmap.ic_rainy_l |
2 | 多云 | R.mipmap.ic_cloudy_l |
3 | 雾 | R.mipmap.ic_fog_l |
4 | 雪 | R.mipmap.ic_snow_l |
5 | 晴间多云 | R.mipmap.ic_sunny_cloudy_l |
天气预报数据
天气预报数据包含5个子项-5天的天气预报
,
"forcast": [
{
"date": "明天",
"temperature_range": "18℃~23℃",
"weather_code": "0"
},
{
"date": "星期六",
"temperature_range": "17℃~21℃",
"weather_code": "1"
},
{
"date": "星期日",
"temperature_range": "19℃~24℃",
"weather_code": "3"
},
{
"date": "星期一",
"temperature_range": "16℃~22℃",
"weather_code": "4"
},
{
"date": "星期二",
"temperature_range": "20℃~26℃",
"weather_code": "2"
}
]
大部分数据的取值就是我们要显示到界面上的内容。例如,
JSON字段名称 | JSON字段取值 | 界面显示 |
---|---|---|
date | 星期一 | 星期一 |
temperature_range | 16℃~22℃ | 16℃~22℃ |
weather_code
字段,取值是数值,不同的数值,代表了不同的天气状态,对它的理解与天气预报数据中的weather_code
一样,只是图标变小了,
取值 | 天气状态 | 应用显示对应的图标 |
---|---|---|
0 | 晴 | R.mipmap.ic_sunny_s |
1 | 雨 | R.mipmap.ic_rainy_s |
2 | 多云 | R.mipmap.ic_cloudy_s |
3 | 雾 | R.mipmap.ic_fog_s |
4 | 雪 | R.mipmap.ic_snow_s |
5 | 晴间多云 | R.mipmap.ic_sunny_cloudy_s |
天气指数信息
天气指数信息需要使用到如下内容,
"wind_direction": "东南",
"wind_level": "1级",
"humidity_level": "30%",
"air_quality": "良",
"sport_level": "适宜",
"ultraviolet_ray": "弱"
数据的取值就是我们要显示到界面上的内容。例如,
JSON字段名称 | JSON字段取值 | 界面显示 | 使用的图标 |
---|---|---|---|
wind_direction | 东南 | 东南 | R.mipmap.ic_wind_direction |
wind_level | 1级 | 1级 | R.mipmap.ic_wind_level |
humidity_level | 30% | 30% | R.mipmap.ic_humidity_level |
air_quality | 良 | 良 | R.mipmap.ic_air_quality |
sport_level | 适宜 | 适宜 | R.mipmap.ic_sport_level |
ultraviolet_ray | 弱 | 弱 | R.mipmap.ic_ultraviolet_level |
解析JSON数据
Android SDK
给我们提供了非常好的JSON
解析支持,我们不需要重头去写一个JSON解析器,直接拿过来用就好了。
添加
JSON
假数据,public class MainActivity extends AppCompatActivity { private final String FAKE_DATA= "{\n" + " \"error_code\": \"0\",\n" + " \"data\": {\n" + " \"location\": \"成都\",\n" + " \"temperature\": \"23°\",\n" + " \"temperature_range\": \"18℃~23℃\",\n" + " \"weather_code\": \"5\",\n" + " \"wind_direction\": \"东南\",\n" + " \"wind_level\": \"1级\",\n" + " \"humidity_level\": \"30%\",\n" + " \"air_quality\": \"良\",\n" + " \"sport_level\": \"适宜\",\n" + " \"ultraviolet_ray\": \"弱\",\n" + " \"forcast\": [\n" + " {\n" + " \"date\": \"明天\",\n" + " \"temperature_range\": \"18℃~23℃\",\n" + " \"weather_code\": \"0\"\n" + " },\n" + " {\n" + " \"date\": \"星期六\",\n" + " \"temperature_range\": \"17℃~21℃\",\n" + " \"weather_code\": \"1\"\n" + " },\n" + " {\n" + " \"date\": \"星期日\",\n" + " \"temperature_range\": \"19℃~24℃\",\n" + " \"weather_code\": \"3\"\n" + " },\n" + " {\n" + " \"date\": \"星期一\",\n" + " \"temperature_range\": \"16℃~22℃\",\n" + " \"weather_code\": \"4\"\n" + " },\n" + " {\n" + " \"date\": \"星期二\",\n" + " \"temperature_range\": \"20℃~26℃\",\n" + " \"weather_code\": \"2\"\n" + " }\n" + " ]\n" + " }\n" + "}"; ...... }
删除之前为
天气指数信息
而创建的假数据,同时在onCreate()
中创建一个JSON
解析器,@Override protected void onCreate(Bundle savedInstanceState) { ...... try { JSONObject weatherResult = new JSONObject(FAKE_DATA); } catch (JSONException e) { e.printStackTrace(); } }
Android Studio
会提示你处理try catch
异常。假如程序运行时解析JSON
字符串遇到了问题,会通过异常报错,让我们做进一步的处理。解析
error_code
,判断数据是否可用,try { JSONObject weatherResult = new JSONObject(FAKE_DATA); int errorCode = weatherResult.getInt("error_code"); if(errorCode == 0) { } else { } } catch (JSONException e) { e.printStackTrace(); }
这里使用了
getInt("error_code")
来获取error
字段对应的值,并且把这个值解析成数值int
类型。
解析天气详情数据
- 通过
getJSONObject("data")
获取data
字段的数据结构; - 通过
getString(xxx)
、getInt(xxx)
获取location
temperature
temperature_range
weather_code
等字段的具体内容;
if(errorCode == 0) {
JSONObject data = weatherResult.getJSONObject("data");
String location = data.getString("location");
String temperature = data.getString("temperature");
String temperatureRange = data.getString("temperature_range");
int weatherCode = data.getInt("weather_code");
}
解析天气预报数据
通过
getJSONArray("forcast")
获取forcast
字段下所有的天气预报子项,一共有5个子项;通过
getString(xxx)
和getInt(xxx)
,获取date
temperature_range
weather_code
等字段的内容;
if(errorCode == 0) {
......
JSONArray forcast = data.getJSONArray("forcast");
for(int i = 0; i < forcast.length(); i++) {
JSONObject forcastItem = forcast.getJSONObject(i);
String date = forcastItem.getString("date");
String forcastTemperatureRange = forcastItem.getString("temperature_range");
int forcastWeatherCode = forcastItem.getInt("weather_code");
}
}
解析天气指数数据
通过getString(xxx)
获取wind_direction
wind_level
humidity_level
air_quality
sport_level
ultraviolet_ray
等字段的具体内容;
if(errorCode == 0) {
......
String windDirection = data.getString("wind_direction");
String windLevel = data.getString("wind_level");
String humidityLevel = data.getString("humidity_level");
String airQuality = data.getString("air_quality");
String sportLevel = data.getString("sport_level");
String ultravioletRay = data.getString("ultraviolet_ray");
}
完整的代码如下,
@Override
protected void onCreate(Bundle savedInstanceState) {
......
try {
JSONObject weatherResult = new JSONObject(FAKE_DATA);
int errorCode = weatherResult.getInt("error_code");
if(errorCode == 0) {
JSONObject data = weatherResult.getJSONObject("data");
String location = data.getString("location");
String temperature = data.getString("temperature");
String temperatureRange = data.getString("temperature_range");
int weatherCode = data.getInt("weather_code");
JSONArray forcast = data.getJSONArray("forcast");
for(int i = 0; i < forcast.length(); i++) {
JSONObject forcastItem = forcast.getJSONObject(i);
String date = forcastItem.getString("date");
String forcastTemperatureRange = forcastItem.getString("temperature_range");
int forcastWeatherCode = forcastItem.getInt("weather_code");
}
String windDirection = data.getString("wind_direction");
String windLevel = data.getString("wind_level");
String humidityLevel = data.getString("humidity_level");
String airQuality = data.getString("air_quality");
String sportLevel = data.getString("sport_level");
String ultravioletRay = data.getString("ultraviolet_ray");
}
else {
}
} catch (JSONException e) {
e.printStackTrace();
}
}
关于调试
解析的数据需要更新到界面上。但是在解析的过程中,我们希望能尽早看到解析的结果是否正确。
Android Studio
为我们提供了两种调试代码、看到代码运行到中间状态的方法:断点调试
和Log调试
。
断点调试
断点调试让程序在运行到某一个状态的时候,冻结应用运行的状态,仿佛时间停止了一般。然后让我们有时间逐一观察此时程序的各个参数是否符合我们的预期。
这种调试方法适用于对时间不敏感的程序。也就是说被调试的程序线程不需要依赖别的线程,即使暂时停止工作也不会影响别的工作线程或者受别的工作线程影响。
在希望代码暂停运行的地方打断点——在代码前点击一下,出现一个红色的圆点,如果想取消,再点击一次即可。
用
debug run
的方式(ctrl+D)部署程序。当程序运行到设置了端点的位置时,程序将停止下来,切换到Debug
窗口。这时,我们就可以观察各个参数了。例如下图右半区域就列出了停止时,各个变量的值;左边区域展示了当时函数到调用栈(谁调用的这个函数)情况。我们可以逐一分析,详细观察,看这些值是否符合我们的预期。
使用菜单栏中的
Run -> Step Over
(或者快捷键F8),能让程序往下执行一步。多按几次,就会依次往下执行几次。这里可以看到我们解析的天气预报数据都没有问题。
端点调试有很多的快捷按键,都是值得我们记住的,可以大大加快我们的开发效率。
Log调试
对于那些和时间相关的程序(不能让程序暂停,等你慢慢观察),我们就不能使用静态的设置断点的调试方法了,得采用动态调试,添加log的方式。
Log的中文名字叫做日志,在编程界表示程序运行过程中打印出的信息。根据log我们就知道现在程序运行到什么地方了,log还可以携带程序中某些变量的信息输出,让我们更精准的知道程序当前运行的状态。
代码中添加Log
在代码中添加一段函数,就能通过特别的工具输出这些log。我们在创建工程的时候就用过了,
在Android代码中添加log的方式如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("TEST", "Weather app launched");
}
这里面使用了Android提供的Log库,Log.d
代表信息属于Debug
类型。
我们给解析出的天气预报数据,都一一加上Log信息。
try {
Log.d("TEST","start to parse JSON content");
JSONObject weatherResult = new JSONObject(FAKE_DATA);
int errorCode = weatherResult.getInt("error_code");
Log.d("TEST", "error_code = " + errorCode);
if(errorCode == 0) {
JSONObject data = weatherResult.getJSONObject("data");
String location = data.getString("location");
String temperature = data.getString("temperature");
String temperatureRange = data.getString("temperature_range");
int weatherCode = data.getInt("weather_code");
Log.d("TEST","weather detail info: "+
" location=" + location +
" temperature=" + temperature +
" temperatureRange=" + temperatureRange +
" weatherCode=" + weatherCode);
JSONArray forcast = data.getJSONArray("forcast");
for(int i = 0; i < forcast.length(); i++) {
JSONObject forcastItem = forcast.getJSONObject(i);
String date = forcastItem.getString("date");
String forcastTemperatureRange = forcastItem.getString("temperature_range");
int forcastWeatherCode = forcastItem.getInt("weather_code");
Log.d("TEST","weather forcast info: "+
" date=" + date +
" forcastTemperatureRange=" + forcastTemperatureRange +
" forcastWeatherCode=" + forcastWeatherCode);
}
String windDirection = data.getString("wind_direction");
String windLevel = data.getString("wind_level");
String humidityLevel = data.getString("humidity_level");
String airQuality = data.getString("air_quality");
String sportLevel = data.getString("sport_level");
String ultravioletRay = data.getString("ultraviolet_ray");
Log.d("TEST","more weather info: "+
" windDirection=" + windDirection +
" windLevel=" + windLevel +
" humidityLevel=" + humidityLevel +
" airQuality=" + airQuality +
" sportLevel=" + sportLevel +
" ultravioletRay=" + ultravioletRay );
Log.d("TEST","finish to parse JSON content");
}
else {
Log.d("TEST","finish to parse JSON content without parse");
}
} catch (JSONException e) {
e.printStackTrace();
Log.d("TEST","fail to parse JSON content");
}
Log的查看
添加了log信息后,将程序通过debug app
部署到设备上,就能在Android Monitor
工具的logcat
窗口中看到对应的信息了。
可以看出,我们对JSON
的解析完全正确。
输出的调试信息,单条如下:
02-10 13:49:29.608 7948-7948/com.anddle.weatherapp D/TEST: error_code = 0
是不是可以猜出它所代表的含义呢?
字段内容 | 字段含义 |
---|---|
02-10 13:49:29.608 | log打印时的时间 |
7948-7948 | 这段代码执行时所在的线程编号 |
com.anddle.weatherapp | 这段代码所属的程序的包名 |
D | 该log是由Log.d()打印出来的,假如显示的是E,说明是由Log.e()打印出来的 |
TEST | Log.d()函数中第一个参数的内容 |
error_code = 0 | Log.d()函数中第二个参数的内容 |
Android应用开发的Log库提供了几种不同等级的log:Verbose
Debug
Info
Warning
Error
,我们可以根据自己log的需要加不同等级的log,使用的形式为:
Log.v(“TAG”,”content is verbose”);
Log.d(“TAG”,”content is debug”);
Log.i(“TAG”,”content is info”);
Log.w(“TAG”,”content is waring”);
Log.e(“TAG”,”content is error”);
/*******************************************************************/
* 版权声明
* 本教程只在CSDN和安豆网发布,其他网站出现本教程均属侵权。
*另外,我们还推出了Arduino智能硬件相关的教程,您可以在我们的网店跟我学Arduino编程中购买相关硬件。同时也感谢大家对我们这些码农的支持。
*最后再次感谢各位读者对安豆
的支持,谢谢:)
/*******************************************************************/