android应用开发-从设计到实现 4-10 解析天气预报数据

解析天气预报数据

界面布局完成以后,就需要设计代码来控制界面上各个元素的逻辑了。

  1. 从网络获取天气预报数据;
  2. 解析获取的数据;
  3. 根据解析的结果更新天气预报界面;

天气预报的数据依赖于网络端的服务器,不是手机端的应用开发者自己能决定的。假如应用和网络服务器是两拨人同时在开发,极有可能出现这样的情况:手机端需要获取数据的时候,服务器端还没有准备好。

遇到这种情况该怎么办呢?

我们可以自己构造一个假数据,模拟已经获取到真实数据的情形。当然,这样的假数据从格式到内容要尽量和网络端提供的真实数据一致。两者越是相同,后面切换到真实数据时,所需要做的代码修改就越简单。

所以在实际的开发项目中,应用开发者和网络开发者会事先拟定一个数据协议,应用开发者看到这个协议就知道假数据如何构造了,而不用等着网络端开发者的工作了。网络端开发者也会严格根据这个协议来开发,不然将来手机端同网络端的配合就是鸡同鸭讲。

这里我们先假设已经获取道了网络上的天气预报数据,看看如何来解析数据,并更新天气预报界面

数据格式

现在应用端(客户端)与网络端(服务器端)之间的数据交换,通常会使用两种格式的文本内容:XMLJSON

比如通过网络获取一个班级学生的信息(假设包含学生的姓名、年龄、性别等3个内容)。

姓名 年龄 性别
赵一 15
钱二 14
孙三 16
李四 12

* 使用XML来传递信息,可能的内容就是:

```xml

    
        赵一
        15
        
    

    
        钱二
        14
        
    

    
        孙三
        16
        
    

    
        李四
        12
        
    


```
每个节点`<>`代表一个数据。
  • 使用JSON来传递信息,可能的内容就是:

    {
        "students":
        [
            {
                "name":"赵一", 
                "age":"15", 
                "sex":"男"
            },
            {
                "name":"钱二", 
                "age":"14", 
                "sex":"女"
            },
            {
                "name":"孙三", 
                "age":"16", 
                "sex":"女"
            },
            {
                "name":"李四", 
                "age":"12", 
                "sex":"男"
            }
        ]
    }

XMLJSON相比,JSON格式的数据占用的空间更小,表达方式更简洁一些。所以JSON似乎更受开发者的欢迎。

我们这里采用的就是JSON格式的数据。

JSON初步

JSON格式的理解也很简单。

  • 数据由名称取值构成,例如"name":"李四"

    1. 它们由:分隔开,并且用"括了起来(对于数值型的取值可以不用引号扩起来,例如"age":12,但为了简化大家记忆的规则,都还是扩起来吧);
    2. :后面可以跟[],也可以跟{};不同的括号,代表数据的不同类型;
    "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解析器,直接拿过来用就好了。

  1. 添加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" +
                "}";
    ......
    }
  2. 删除之前为天气指数信息而创建的假数据,同时在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字符串遇到了问题,会通过异常报错,让我们做进一步的处理。

  3. 解析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类型。

解析天气详情数据

  1. 通过getJSONObject("data") 获取data字段的数据结构;
  2. 通过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");
}

解析天气预报数据

  1. 通过getJSONArray("forcast")获取forcast字段下所有的天气预报子项,一共有5个子项;

  2. 通过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调试

断点调试

断点调试让程序在运行到某一个状态的时候,冻结应用运行的状态,仿佛时间停止了一般。然后让我们有时间逐一观察此时程序的各个参数是否符合我们的预期。

这种调试方法适用于对时间不敏感的程序。也就是说被调试的程序线程不需要依赖别的线程,即使暂时停止工作也不会影响别的工作线程或者受别的工作线程影响。

  1. 在希望代码暂停运行的地方打断点——在代码前点击一下,出现一个红色的圆点,如果想取消,再点击一次即可。

  2. debug run的方式(ctrl+D)部署程序。当程序运行到设置了端点的位置时,程序将停止下来,切换到Debug窗口。这时,我们就可以观察各个参数了。

    例如下图右半区域就列出了停止时,各个变量的值;左边区域展示了当时函数到调用栈(谁调用的这个函数)情况。我们可以逐一分析,详细观察,看这些值是否符合我们的预期。

  3. 使用菜单栏中的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编程中购买相关硬件。同时也感谢大家对我们这些码农的支持。

*最后再次感谢各位读者对安豆的支持,谢谢:)
/*******************************************************************/

你可能感兴趣的:(从设计到实现)