利用OkHttp库来访问网络,获取和风天气的数据
一.声明网络权限和导入OkHttp库:
<uses-permission android:name="android.permission.INTERNET"/>
implementation 'com.squareup.okhttp3:okhttp:3.4.1'
二.新建一个客户端:
OkHttpClient client = new OkHttpClient();
三.获取访问和风天气接口URL:
String weatherURL = "https://free-api.heweather.com/s6/weather/forecast?location=CN101010100&key=XXXXXXXX";
注意:其中的Key=XXXXXXX,需要你自行注册和风天气,并填入获取到相应的密钥,而location=CN101010100中的CN101010100即是代表北京的代码(此处和风天气的接口也支持直接中文),所以此处我们查询到的数据就是北京的天气数据,具体用法见和风天气官方.
四.新建请求:
Request request = new Request.Builder()
.url(weatherURL)
.build();
五.发送请求,并捕获和风服务器返回的JSON格式的天气数据:
try {
Response response = client.newCall(request).execute();
String responseData = response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
获取到的JSON格式的数据是字符串形式的,所以我们利用responseData接收起来即可.
六.获取到的北京天气的JSON数据样本:
{
"HeWeather6": [{
"basic": {
"cid": "CN101010100",
"location": "北京",
"parent_city": "北京",
"admin_area": "北京",
"cnty": "中国",
"lat": "39.90498734",
"lon": "116.4052887",
"tz": "+8.00"
},
"update": {
"loc": "2018-08-29 09:45",
"utc": "2018-08-29 01:45"
},
"status": "ok",
"daily_forecast": [{
"cond_code_d": "100",
"cond_code_n": "100",
"cond_txt_d": "晴",
"cond_txt_n": "晴",
"date": "2018-08-29",
"hum": "45",
"mr": "20:30",
"ms": "07:57",
"pcpn": "0.0",
"pop": "3",
"pres": "1011",
"sr": "05:40",
"ss": "18:48",
"tmp_max": "31",
"tmp_min": "20",
"uv_index": "10",
"vis": "20",
"wind_deg": "351",
"wind_dir": "北风",
"wind_sc": "1-2",
"wind_spd": "3"
}, {
"cond_code_d": "101",
"cond_code_n": "101",
"cond_txt_d": "多云",
"cond_txt_n": "多云",
"date": "2018-08-30",
"hum": "63",
"mr": "20:59",
"ms": "08:57",
"pcpn": "0.0",
"pop": "20",
"pres": "1012",
"sr": "05:41",
"ss": "18:47",
"tmp_max": "30",
"tmp_min": "20",
"uv_index": "5",
"vis": "18",
"wind_deg": "217",
"wind_dir": "西南风",
"wind_sc": "1-2",
"wind_spd": "4"
}, {
"cond_code_d": "104",
"cond_code_n": "101",
"cond_txt_d": "阴",
"cond_txt_n": "多云",
"date": "2018-08-31",
"hum": "61",
"mr": "21:30",
"ms": "09:59",
"pcpn": "0.0",
"pop": "0",
"pres": "1014",
"sr": "05:42",
"ss": "18:45",
"tmp_max": "29",
"tmp_min": "20",
"uv_index": "3",
"vis": "20",
"wind_deg": "126",
"wind_dir": "东南风",
"wind_sc": "1-2",
"wind_spd": "1"
}]
}]
}
将上面简化一下的,容易看一下是这样的:
{
"HeWeather6": [{
"basic": {...},
"update": {...},
"status": "ok",
"daily_forecast": [{...},{...},{...}]
}]
}
我们可能会发现JSON数据{}或者[]前有类似 "HeWeather6": 这样的东西:
1.数组的名称,称它为数据头,防止跟里面的字段有歧义.
2.如果没有数据头,那就叫它纯数据,或者纯数组数据.
3.有数据头和没数据头解析时有区别.
解析和风天气返回的的JSON数据
一.将json字符串转化为json对象:
JSONObject jsonObject = new JSONObject(responseData);
二.获取json对象中数据头为HeWeather6(或者名称为HeWeather6)的数组:
JSONArray jsonArray = jsonObject.getJSONArray("HeWeather6");
三.获取HeWeather6数组中位置为0( 即"HeWeather6": [{...}]中的{...}之间的内容 )的json对象的字符串形式:
String weatherContent = jsonArray.getJSONObject(0).toString();
其内容即是:
[{
"basic": {...},
"update": {...},
"status": "ok",
"daily_forecast": [{...},{...},{...}]
}]
四.解析JSON数据常用的有两种(JSONObject,GSON),在此处我们使用GSON:
1.使用前导入GSON库依赖:
implementation 'com.google.code.gson:gson:2.7'
2.GSON主要可以将一段JSON格式的字符串自动映射成一个对象,所以我们需要参照JSON数据,建立JSON数据所对应的所有对象:Basic,Update,Status,Forecast,然后用一个总的类(Weather)持有它们.
public class Basic {
@SerializedName("loaction")
public String cityName;
@SerializedName("cid")
public String code;
}
-----------------------
public class Update {
@SerializedName("loc")
public String updateTime;
}
-----------------------
public class Forecast {
@SerializedName("tmp_max")
public String MaxT;
@SerializedName("tmp_min")
public String MinT;
@SerializedName("cond_txt_d")
public String dayTime;
}
public class Weather {
public Basic basic;
public Update update;
public String status;
@SerializedName("daily_forecast")
public List<Forecast> forecastList;
}
研究以上代码,可以发现这些类里面的写的字段也是一一对应JSON数据格式里面的字段的,并且变量名也要一一对应,不过,JSON格式中的字段名阅读性很差,如cond_code_d我们一般看出来是什么,那我们如何自定义变量名并且让其与JSON格式中的数据对应起来呢?这就用到了注解功能了,如被@SerializedName("cond_code_d")注解的字符串就可以对应json格式中的cond_code_d字段了.
和风天气返回的天气数据很多,我们并不需要这么多,所以我们在建立这些类时,里面的字段并不用参照JSON格式里的字段全部建立,只需建立我们需要的天气数据的字段即可.
3.开启解析(映射):
Weather weather = new Gson().fromJson(weatherContent , Weather.class);
4.然后便可以利用Weather对象获取相应的数据了,比如获取JSON数据中字段为status的值:
String value = weather.status;
5.我们可以观察到JSON数据的daily_forecast字段有点特殊,里面有三个一样的大括号,那我们怎么拿出里面的数据呢?用List即可:
List<Forecast> forecastList = weather.forecastList;
不难猜出forecastList的长度为3,其对应的就是三个大括号,比如拿出第一个括号里的字段为tmp_max的值:
Forecast forecast = forecastList.get(0);
String value = forecast.MaxT;
扩展
一.有时候服务器放回的JSON数据并不一定跟和风天气返回的JSON数据一样,但其实都大同小异:
1.没有数据头的JSON数据:
[
{"id":"5","version":"5.5","name":"Clash of Clans"},
{"id":"6","version":"7.0","name":"Boom Beach"},
{"id":"7","version":"3.5","name":"Clash Royale"}
]
解析时,我们就可以这样:
JSONArray jsonArray = new JSONArray(responseData);
//可以看到json数据有三个括号,所以此处的0指的就是第一个括号
JSONObject jsonObject = jsonArray.getJSONObject(0);
String id = jsonObject.getString("id");
这样的解析方式属于JSONObject,而不是GSON了,用GSON:
public class App {
public String id;
public String name;
public String version;
}
----------------------------------
Gson gson = new Gson();
List<App> appList = gson.fromJson(responseData , new TypeToken<List<App>>(){}.getType());
String id = appList.get(0).id;
或者:
public class App {
public String id;
public String name;
public String version;
}
-----------------------------
JsonParser parser = new JsonParser();
JsonArray jsonArray = parser.parse(responseData).getAsJsonArray();
Gson gson = new Gson();
ArrayList<App> appList = new ArrayList<>();
for (JsonElement user : jsonArray) {
App app = gson.fromJson(user, App.class);
appList.add(app);
}
注意
由于连接网络连接耗时,稳定性问题,为了避免堵塞主线程,以上的代码需要在子线程中进行,而且要注意子线程不可更新Ui问题.
JSON知识补充
一.JSON即对象表示法(JavaScript Object Notation),JSON文本格式在语法上与创建 JavaScript 对象的代码相同,如一个JSON文本和一个JavaScript 对象对比:
{
"employees": [
{ "firstName":"John" , "lastName":"Doe" },
{ "firstName":"Anna" , "lastName":"Smith" },
{ "firstName":"Peter" , "lastName":"Jones" }
]
}
var employees = [
{ "firstName":"Bill" , "lastName":"Gates" },
{ "firstName":"George" , "lastName":"Bush" },
{ "firstName":"Thomas" , "lastName": "Carter" }
];
所以在上面例子中,不难看出JSON中的employees是一个数组的变量名.
二.数组,对象,数据
1.在JSON中,方括号[ ]保存数组,花括号{ }保存对象.
2.数组可包含多个对象:{"employees": [ {...} , {...} , {...} ]} , 对象 "employees" 是包含三个对象的数组.
3.JSON对象在花括号中书写,即一个{ }代表一个JSON对象,JSON对象可以包含多个名称/值对:
{ "firstName":"John" , "lastName":"Doe" }
4.类似 "firstName":"John" 这样的 名称:值对 称为一条JSON 数据.
三.JavaScript 语法
var employees = [
{ "firstName":"Bill" , "lastName":"Gates" },
{ "firstName":"George" , "lastName":"Bush" },
{ "firstName":"Thomas" , "lastName": "Carter" }
];
1.访问 JavaScript 对象数组中的第一项:
第一种: employees[0].lastName;
第二种: employees[0]["lastName"];
2.修改数据: employees[0].lastName = "Jobs";
四.有了以上知识不妨回头看看和风天气返回的数据:
{
"HeWeather6": [{
"basic": {...},
"update": {...},
"status": "ok",
"daily_forecast": [{...},{...},{...}]
}]
}
我们知道{}代表一个保存的是对象,而和风天气返回的JSON数据最外一层的也是一个{},代表这段JSON是一个JSON对象,所以我们利用系统提供的JSONObject来保存这个JSON对象:
JSONObject jsonObject = new JSONObject(responseData);
再看看这个JSON对象里面包含了一个HeWeather6数组,因此,同样的我们利用系统提供的JSONArray来保存这个HeWeather6数组:
JSONArray jsonArray = jsonObject.getJSONArray("HeWeather6");
再接下去,我们又发现了HeWeather6数组里面含有一个{ }(即对象),并且这个对象里面又含有了两个对象(basic,update),一条数据(status),一个数组(daily_forecast),所以根据以往,首先,我们需要获取HeWeather6数组里含有的对象:
JSONObject jsonObject1 = jsonArray.getJSONObject(0);
然后再去获取这个对象中的对象:
JSONObject jsonObject2 = jsonArray.getJSONObject("basic");
然后最终得到JSON数据变量名为basic的数据:
Log.d(TAG, "onCreate: " + jsonObject1.getString("cid"));
所以你会发现用纯JSONObject的方法来解析JSON数据挺繁琐的,不如使用上面GSON的方式直接将JSON数据映射到实体类,然后通过实体类来获得相应的数据来的方便.
四.实际开发遇到的错误
1.以下代码粗看是一个JSONObject包含了两个JSONArray,但是这段JSON字符串转为JSONObject会报错
{
"xx" : "[{"aaa":"axx","b":"b"},{"a":"a","b":"b"}]",
"yy" : "[{"a":"a","b":"b"},{"a":"a","b":"b"}]"
}
正确写法,要把JSONArray字符串左右两边的 “ 去掉:
{
"xx" : [{"aaa":"axx","b":"b"},{"a":"a","b":"b"}],
"yy" : [{"a":"a","b":"b"},{"a":"a","b":"b"}]
}
2.假如我们在数据库直接用一个字段存储一段JSON字符串数据:
[{"职业规划":"25","计算机与科学":"85"},{"体育":"25","计算机与科学":"85"}]
客户端发起对php(或者其他类型的后台)的请求,用字符串data接收返回到的数据:
String data = response.body().string;
打印出来,可以看到多出来了些转移符 \ , 还有左右两边也多出了 “
如果这些我们没处理掉的话,直接把这个原始字符串转换为JSONArray会报错,怎么处理掉看下面java代码:
String data = response.body().string().replace("\\", ""); //接受并处理掉转义符\
String data2 = data.substring(1,data.length() - 1); //处理掉前后"符号