Android实战:CoolWeather酷欧天气(加强版数据接口)代码详解(上)
来源:互联网 发布:淘宝海外代购是真的吗 编辑:程序博客网 时间:2024/05/19 11:48
拜读了郭霖大神的《第一行代码(第二版)》后,决定对其文末的酷欧天气实战项目进行数据扩充以及代码详解,完整文件请从我的GitHub中下载,想学习更多Android知识在看完本篇文章后请出门右转:京东、当当、亚马逊、天猫、PDF、Kindle、豆瓣、多看。
具体步骤还是按照郭霖大神的分析思路来,外加一点点个人的认知。
目录(上)
- 目录上
- 一功能需求及技术可行性分析
- 二创建数据库和表
- 三遍历全国省市县数据
一、功能需求及技术可行性分析
1、确定APP应该具有的功能
- 可以查询全国所有省、市、县的数据(列表)
- 可以查询全国任意城市的天气
- 可以切换城市查询天气
- 可以手动更新以及后台自动更新天气
2、考虑数据接口问题
- 如何得到全国省市县的数据信息
- 如何获取每个城市的天气信息
3、获取全国省市县数据信息
- 首推郭霖大神架设的供学习使用的服务器(返回JSON数据格式):http://guolin.tech/api/china
- 和风天气国内城市ID:https://cdn.heweather.com/china-city-list.txt
- 中国天气城市ID列表:http://cj.weather.com.cn/support/Detail.aspx?id=51837fba1b35fe0f8411b6df
注:除第一个外,其余都是可获取的原始数据,需要进行数据预处理。(数据预处理部分后期文章补上)
4、获取每个城市的天气信息
- 和风天气API:普通用户每天1000次访问量 https://www.heweather.com/documents
- 彩云天气API:个人开发者免费10000次访问量 http://wiki.swarma.net/index.php/彩云天气API/v2
- 聚合数据天气API:2元/1000次访问量 https://www.juhe.cn/docs/api/id/39
- YY天气API:免费会员30次/小时 http://www.yytianqi.com/api.html
- 心知天气API:个人免费版400次/小时 https://www.seniverse.com/doc
- 中国气象数据网API:气象大数据交易平台 http://data.cma.cn/market/marketList.html
- 气象大数据应用产创平台:http://www.weatherdt.com/api.html
5、解析数据
以和风天气为例(其他API接口的使用后期文章更新),获取和风天气返回的JSON格式的城市详细天气数据。取苏州的详细天气信息,如下图:
并对其进行分析:(选择你所需要的数据)
其中,aqi包含当前空气质量的情况。basic中包含城市的一些具体信息。daily_forecast中包含未来3天的天气信息。now表示当前的天气信息。status表示接口状态,“ok”表示数据正常,具体含义请参考接口状态码及错误码。suggestion中包含一些天气相关的生活建议。
二、创建数据库和表
1、建立新的项目结构
在Android Studio中新建一个Android项目,项目名叫CoolWeather,包名叫做com.coolweather.android,之后一路Next,所有选项都使用默认就可以完成项目的创建。
为了让项目能有更好的结构,在com.coolweather.android包下再新建四个包。其中,db包用于存放数据库模型相关代码。gson包用于存放GSON模型相关的代码,service包用于存放服务相关代码,util包用于存放工具相关的代码。
2、将项目中所需的各种依赖库进行声明,编辑app/build.gradle文件,在dependencies闭包中添加如下内容:
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha7' testCompile 'junit:junit:4.12' compile 'org.litepal.android:core:1.6.0' compile 'com.squareup.okhttp3:okhttp:3.9.0' compile 'com.google.code.gson:gson:2.8.2' compile 'com.github.bumptech.glide:glide:4.3.1'}
为了简化数据库的操作,我们使用LitePal来管理数据库。在dependencies闭包中,最后四行为新添加的声明,都更新为最新的版本号。其中,LitePal用于对数据库进行操作,OkHttp用于进行网络请求,GSON用于解析JSON数据,Glide用于加载和展示图片,以上四种声明都附有GitHub超链,可以点击进行深入了解。
3、设计数据库表结构
准备建立3张表:province、city、county,分别用于存放省、市、县的数据信息。对应到实体类中就是建立Province、City、County这三个类。由于LitePal要求所有的实体类都要继承自DataSupport这个类,所以三个类都要继承DataSupport类。
在db包下新建一个Province类,代码如下:
public class Province extends DataSupport{ private int id;//实体类的id private String provinceName;//省的名字 private int provinceCode;//省的代号 //getter和setter方法 public int getId(){ return id; } public void setId(int id){ this.id = id; } public String getProvinceName() { return provinceName; } public void setProvinceName(String provinceName) { this.provinceName = provinceName; } public int getProvinceCode() { return provinceCode; } public void setProvinceCode(int provinceCode) { this.provinceCode = provinceCode; }}
接着在db包下新建一个City类,代码如下:
public class City extends DataSupport{ private int id;//实体类的id private String cityName;//城市名 private int cityCode;//城市的代号 private int provinceId;//当前市所属省的id值 //getter和setter方法 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCityName() { return cityName; } public void setCityName(String cityName) { this.cityName = cityName; } public int getCityCode() { return cityCode; } public void setCityCode(int cityCode) { this.cityCode = cityCode; } public int getProvinceId() { return provinceId; } public void setProvinceId(int provinceId) { this.provinceId = provinceId; }}
然后在db包下新建一个County类,代码如下:
public class County extends DataSupport{ private int id;//实体类的id private String countyName;//县的名字 private String weatherId;//县所对应天气的id值 private int cityId;//当前县所属市的id值 //getter和setter方法 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCountyName() { return countyName; } public void setCountyName(String countyName) { this.countyName = countyName; } public String getWeatherId() { return weatherId; } public void setWeatherId(String weatherId) { this.weatherId = weatherId; } public int getCityId() { return cityId; } public void setCityId(int cityId) { this.cityId = cityId; }}
实体类内容很简单,就是声明一些用到的字段,并生成相应的getter和setter方法。接下来需要配置litepal.xml文件,切换左上角下拉菜单到project模式,右击app/src/main目录->New->Directory,创建一个assets目录,然后在assets目录下再创建一个litepal.xml文件(新建.xml时文件可能会跑到/app目录下,用鼠标托回到assets目录下即可),编辑litepal.xml文件中的内容,如下所示:
<litepal> <dbname value="cool_weather"/> <version value="1"/> <list> <mapping class="com.coolweather.android.db.Province"/> <mapping class="com.coolweather.android.db.City"/> <mapping class="com.coolweather.android.db.County"/> </list></litepal>
我们将数据库名指定为cool_weather,数据库版本指定为1(注:使用LitePal来升级数据库非常简单,只需要修改你想改的内容,然后将版本号加1即可),并将Province、City和County这3个实体类添加到映射列表当中。最后还需要配置一下LitePalApplication,修改AndroidManifest.xml中的代码,如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.coolweather.android" > <application android:name="org.litepal.LitePalApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>
这样我们就将所有的配置写完了,数据库和表会在首次执行任意数据库操作的时候自动创建。
三、遍历全国省市县数据
1、与服务器进行数据交互
全国省市县的数据都是从服务器端获取的,因此需要与服务器端进行数据的交互。我们在util包下增加一个HttpUtil类,代码如下:
public class HttpUtil { /** * 和服务器进行交互,获取从服务器返回的数据 */ public static void sendOkHttpRequest(String address, okhttp3.Callback callback){ //创建一个OkHttpClient的实例 OkHttpClient client = new OkHttpClient(); //创建一个Request对象,发起一条HTTP请求,通过url()方法来设置目标的网络地址 Request request = new Request.Builder().url(address).build(); //调用OkHttpClient的newCall()方法来创建一个Call对象, // 并调用它的enqueue()方法将call加入调度队列,然后等待任务执行完成 client.newCall(request).enqueue(callback); }}
由于OkHttp的出色封装,仅用3行代码即完成与服务器进行交互功能,有了该功能后我们发起一条HTTP请求只需要调用sendOkHttpRequest()方法,传入请求地址,并注册一个回调来处理服务器响应就可以了。
2、解析和处理JSON格式数据
由于服务器返回的省市县的数据都是JSON格式,所以我们再构建一个工具用于解析和处理JSON数据。在util包下新建一个Utility类,代码如下所示:
public class Utility { /** * 解析和处理服务器返回的省级数据 */ public static boolean handleProvinceResponse(String response){ if(!TextUtils.isEmpty(response)){ try{ //将服务器返回的数据传入到JSONArray对象allProvinces中 JSONArray allProvinces = new JSONArray(response); //循环遍历JSONAray for(int i=0;i<allProvinces.length();i++){ //从中取出的每一个元素都是一个JSONObject对象 JSONObject provinceObject = allProvinces.getJSONObject(i); //每个JSONObject对象包含name、code等信息,调用getString()方法将数据取出 // 将数据组装成实体类对象 Province province = new Province(); province.setProvinceName(provinceObject.getString("name")); province.setProvinceCode(provinceObject.getInt("id")); //调用save()方法将数据存储到数据库当中 province.save(); } return true; }catch(JSONException e){ e.printStackTrace(); } } return false; } /** * 解析和处理服务器返回的市级数据 */ public static boolean handleCityResponse(String response,int provinceId){ if(!TextUtils.isEmpty(response)){ try{ JSONArray allCities = new JSONArray(response); for(int i=0;i<allCities.length();i++){ JSONObject cityObject = allCities.getJSONObject(i); City city = new City(); city.setCityName(cityObject.getString("name")); city.setCityCode(cityObject.getInt("id")); city.setProvinceId(provinceId); city.save(); } return true; }catch (JSONException e){ e.printStackTrace(); } } return false; } /** * 解析和处理服务器返回的县级数据 */ public static boolean handleCountyResponse(String response,int cityId){ if(!TextUtils.isEmpty(response)){ try{ JSONArray allCounties = new JSONArray(response); for(int i=0;i<allCounties.length();i++){ JSONObject countyObject = allCounties.getJSONObject(i); County county = new County(); county.setCountyName(countyObject.getString("name")); county.setWeatherId(countyObject.getString("weather_id")); county.setCityId(cityId); county.save(); } return true; }catch (JSONException e){ e.printStackTrace(); } } return false; }}
在Utility类中,分别提供了handleProvinceResponse()、handleCityResponse()、handleCountyResponse()这三个方法,分别用于解析和处理从服务器返回的各级数据。
3、左边栏碎片布局
将左边栏的内容写在碎片里,使用的时候直接在布局里面引用碎片即可。在res/layout目录中新建choose_area.xml布局,代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#fff"> <RelativeLayout android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary"> <TextView android:id="@+id/title_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textColor="#fff" android:textSize="20sp"/> <Button android:id="@+id/back_button" android:layout_width="25dp" android:layout_height="25dp" android:layout_marginLeft="10dp" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:background="@drawable/ic_back"/> </RelativeLayout> <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent"/></LinearLayout>
以线性布局做为主体,里面嵌套了一个相对布局和ListView,其中相对布局作为头布局,其中的TextView用于显示标题内容,Button用于执行返回操作(注:需要提前准备好一张ic_back.png图片作为返回按钮的图片)。省市县的数据信息则会显示在ListView中,其中每个子项之间会有一条分割线。
4、遍历省市县数据的碎片
在com.coolweather.android包下新建ChooseAreaFragment类继承自Fragment(注:在引入Fragment包的时候,建议使用support-v4库中的Fragment,因为它可以让碎片在所有的Android版本中保持功能一致性),代码如下:
public class ChooseAreaFragment extends Fragment { public static final int LEVEL_PROVINCE = 0; public static final int LEVEL_CITY = 1; public static final int LEVEL_COUNTY = 2; private ProgressDialog progressDialog;//进度条(加载省市县信息时会出现) private TextView titleText;//标题 private Button backButton;//返回键 private ListView listView;//省市县列表 private ArrayAdapter<String> adapter;//适配器 private List<String> dataList = new ArrayList<>();//泛型 /** * 省列表 */ private List<Province> provinceList; /** * 市列表 */ private List<City> cityList; /** * 县列表 */ private List<County> countyList; /** * 选中的省份 */ private Province selectedProvince; /** * 选中的城市 */ private City selectedCity; /** * 当前选中的级别 */ private int currentLevel; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { //获取控件实例 View view = inflater.inflate(R.layout.choose_area, container, false); titleText = (TextView) view.findViewById(R.id.title_text); backButton = (Button) view.findViewById(R.id.back_button); listView = (ListView) view.findViewById(R.id.list_view); //初始化ArrayAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, dataList); //将adapter设置为ListView的适配器 listView.setAdapter(adapter); return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //ListView的点击事件 listView.setOnItemClickListener(new AdapterView.OnItemClickListener(){ @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if(currentLevel == LEVEL_PROVINCE){//在省级列表 selectedProvince = provinceList.get(position);//选择省 queryCities();//查找城市 }else if(currentLevel == LEVEL_CITY){ selectedCity = cityList.get(position); queryCounties(); } } }); //Button的点击事件 backButton.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { if(currentLevel == LEVEL_COUNTY){ queryCities(); }else if(currentLevel == LEVEL_CITY){ queryProvinces(); } } }); queryProvinces();//加载省级数据 } /** * 查询全国所有的省,优先从数据库查,如果没有查询到再去服务器上查询 */ private void queryProvinces(){ titleText.setText("中国");//头标题 backButton.setVisibility(View.GONE);//当处于省级列表时,返回按键隐藏 //从数据库中读取省级数据 provinceList = DataSupport.findAll(Province.class); //如果读到数据,则直接显示到界面上 if(provinceList.size() > 0){ dataList.clear(); for(Province province : provinceList){ dataList.add(province.getProvinceName()); } adapter.notifyDataSetChanged(); listView.setSelection(0); currentLevel = LEVEL_PROVINCE; }else{ //如果没有读到数据,则组装出一个请求地址,调用queryFromServer()方法从服务器上查询数据 String address = "http://guolin.tech/api/china";//郭霖地址服务器 queryFromServer(address, "province"); } } /** * 查询选中省内所有的市,优先从数据库查询,如果没有查到再去服务器上查询 */ private void queryCities(){ titleText.setText(selectedProvince.getProvinceName()); backButton.setVisibility(View.VISIBLE);//当处于市级列表时,返回按键显示 cityList = DataSupport.where("provinceid = ?",String.valueOf(selectedProvince.getId())).find(City.class); if(cityList.size() > 0){ dataList.clear(); for(City city : cityList){ dataList.add(city.getCityName()); } adapter.notifyDataSetChanged(); listView.setSelection(0); currentLevel = LEVEL_CITY; }else{ int provinceCode = selectedProvince.getProvinceCode(); String address = "http://guolin.tech/api/china/"+provinceCode; queryFromServer(address, "city"); } } /** * 查询选中市内所有的县,优先从数据库查询,如果没有查询到再去服务器上查询 */ private void queryCounties(){ titleText.setText(selectedCity.getCityName()); backButton.setVisibility(View.VISIBLE);//当处于县级列表时,返回按键显示 countyList = DataSupport.where("cityid = ?",String.valueOf(selectedCity.getId())).find(County.class); if(countyList.size() > 0){ dataList.clear(); for(County county : countyList){ dataList.add(county.getCountyName()); } adapter.notifyDataSetChanged(); listView.setSelection(0); currentLevel = LEVEL_COUNTY; }else{ int provinceCode = selectedProvince.getProvinceCode(); int cityCode = selectedCity.getCityCode(); String address = "http://guolin.tech/api/china/"+provinceCode+"/"+cityCode; queryFromServer(address, "county"); } } /** * 根据传入的地址和类型从服务器上查询省市县数据 */ private void queryFromServer(String address, final String type){ showProgressDialog(); //向服务器发生请求,响应的数据会回调到onResponse()方法中 HttpUtil.sendOkHttpRequest(address, new Callback() { @Override public void onResponse(Call call, Response response) throws IOException { String responseText = response.body().string(); boolean result = false; if("province".equals(type)){ //解析和处理从服务器返回的数据,并存储到数据库中 result = Utility.handleProvinceResponse(responseText); }else if("city".equals(type)){ result = Utility.handleCityResponse(responseText,selectedProvince.getId()); }else if("county".equals(type)){ result = Utility.handleCountyResponse(responseText,selectedCity.getId()); } if(result){ //由于query方法用到UI操作,必须要在主线程中调用。 // 借助runOnUiThread()方法实现从子线程切换到主线程 getActivity().runOnUiThread(new Runnable() { @Override public void run() { closeProgressDialog(); if("province".equals(type)){ //数据库已经存在数据,调用queryProvinces直接将数据显示到界面上 queryProvinces(); }else if("city".equals(type)){ queryCities(); }else if("county".equals(type)){ queryCounties(); } } }); } } @Override public void onFailure(Call call, IOException e) { //通过runOnUiThread()方法回到主线程处理逻辑 getActivity().runOnUiThread( new Runnable() { @Override public void run() { closeProgressDialog(); Toast.makeText(getContext(),"加载失败",Toast.LENGTH_SHORT).show(); } }); } }); } /** * 显示进度对话框 */ private void showProgressDialog(){ if(progressDialog == null){ progressDialog = new ProgressDialog(getActivity()); progressDialog.setMessage("正在加载..."); progressDialog.setCanceledOnTouchOutside(false); } progressDialog.show(); } /** * 关闭进度对话框 */ private void closeProgressDialog(){ if(progressDialog != null){ progressDialog.dismiss(); } }}
在这个类中,具体代码的功能在代码里注释的很详细。其中,onCreateView()方法和onActivityCreated()方法进行初始化操作,queryProvinces()方法、queryCities()方法和queryCounties()方法分别提供省、市、县数据的查询功能。queryFromServer()方法根据传入的参数从服务器上读取省市县的数据。
5、将碎片添加在活动里
由于碎片不能直接显示,需要将其添加到活动里才能将其正常显示在界面上。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/choose_area_fragment" android:name="com.coolweather.android.ChooseAreaFragment" android:layout_width="match_parent" android:layout_height="match_parent"/></FrameLayout>
6、移除原生ActionBar
由于在碎片的布局里面已经自定义了一个RelativeLayout标题栏,因此就不需要原生的ActionBar了,修改res/values/styles.xml中的代码如下:
7、声明权限
因为需要从服务器中调用数据,则需要声明网络权限。
运行程序,就可以看到全国所有的省市县数据啦。如下图所示(右上角小人为截屏软件,请忽略):
上篇到此结束,剩余项目请关注下篇。完整代码文件:https://github.com/ambition-hb/CoolWeather
- Android实战:CoolWeather酷欧天气(加强版数据接口)代码详解(上)
- Android实战:CoolWeather酷欧天气(加强版数据接口)代码详解(下)
- CoolWeather天气个人学习笔记1(第一行代码实战)
- Android CoolWeather 天气
- Android:CoolWeather天气查看器
- Android调用天气接口(和风天气)
- CoolWeather(《第一行代码》实战)遍历全国各省市县总结
- 【实战】android获取天气情况(Json来返回数据)
- 用Android Studio写的查看天气的app(CoolWeather)
- 酷欧天气实战
- 参照《第一行代码》开发CoolWeather (一)
- 进入实战,开发酷欧天气(一)
- 进入实战,开发酷欧天气(二)
- 进入实战,开发酷欧天气(三)
- 进入实战,开发酷欧天气(四)
- 进入实战,开发酷欧天气(五)
- Android解析中国天气接口JSon数据,应用于天气查询!
- android模拟小米天气view(上)
- V4L2源代码之旅五:V4L2的起点和终点
- copy 数据库第三接口授权
- FCC算法:七、检查字符串结尾--Confirm the Ending
- 【DevExpress v17.2新功能预告】WinForms上的图表增强
- 通过scp上传文件到服务器
- Android实战:CoolWeather酷欧天气(加强版数据接口)代码详解(上)
- 从键盘输入字符串并输出该字符串(汇编语言)
- 微信公众平台给用户发红包+php
- Css有关于圣杯及双飞翼布局
- 字符串不输出某一指定字符!
- 数据结构与算法分析c++:栈的应用(1)
- 外部div宽度为100%,内部div宽度也为100%,内部div整体水平居中
- ABBYY FineReader 14新增功能
- WSingle站群系统,全网唯一支持【站群】的WordPress小说主题!