从今天开始呢,打算来介绍如何开发一款Android的天气软件,其中运用到的基础知识点也比较多,比较适合初学或者刚接触Android开发不久的同学一起来学习交流和切磋!
那么我先对这个天气软件进行需求分析,我们主要会实现什么功能呢?
1、查询全国省市县的任意城市的天气信息并可以自由切换
2、手动刷新天气信息
3、后台自动刷新天气
4、自动定位所在地的天气信息
根据以上需求分析,我们将使用Sqlite来存储从网络获取的全国省市县的列表信息,用SharedPreference来存储当天的天气情况,方便多次查看。当然还要使用Http协议来获取天气信息,我们这边将调用中国天气气象局的API接口。此外,我们还将运用Service和Recevier来实现后台自动刷新界面的功能,实现如下所示的界面.
此外,我们还将运用到一些Git上流行的开源框架,比如LitePal,这个框架大家可以看一下郭霖大神写的博文,Gson解析Json数据等等,今天的需求分析与功能规格就到此结束,下一篇博文一起来开发Android的天气软件(二)
下面是该应用的Git开源地址,https://github.com/melhc/SimpleWeather
一起来开发Android的天气软件(二)——使用LitePal搭建数据库
谢谢大家对该系列博文的支持与关注,我们现在趁热打铁正式开始我们的Android天气软件的开发吧!没有阅读过之前关于该软件的功能需求的同学可以先看一下 一起来开发Android的天气软件(一),可以先去快速浏览一下,清楚我们的概要体系。今天我们要做的是搭建Sqlite数据库,那这个数据库要存储哪些信息呢!该数据库是用来存储全国的省市县的一些信息,全国共有34个省比如浙江旗下又有好多的市如杭州、温州、湖州,杭州下面又有很多区县,我们要做的就是把这些结构化的数据存储到我们的数据库中。
那么我们现在就开始吧!我们这次使用的是LitePal的开源框架,它采用了对象关系映射(ORM)的模式,并将我们平时开发时最常用到的一些数据库功能进行了封装,使得不用编写一行SQL语句就可以完成各种建表、増删改查的操作。用起来总之非常的方便,从郭霖大神的博文中学会了解该框架后,我就快速学以致用用到这个天气软件的编程中来,Pratiece makes preferct,我们下面就来见识下LitePal的威力!
首先我们先要建立Province,City,County三张表。
1、Province
2、City
3、County
好的,以上这是我们表的结构的庐山真面目,我们会发现每一张表都会有一个id,然后会有相应的province_code,province_name属性,除此之外呢,city还有一个province_id列,这是一个外键列,是为了实现省份表与城市表的一个关联关系,一个city表里面存放一个具体的省份id,并且允许多个城市都存放同一个省份id,这样一个城市就只能对应一个省份,但一个省份却可以有多个城市,也就实现多对一的关系了!以此类推,county也有一个city_id来实现市与县的一对多的关系,不知道大家看到这里都理清楚了没有我们三张表的关系呢!
我们知道了以上的表结构,我们可以正式进入建表操作了,这边我在重新简述一下使用LitePal的使用流程,如果还没有下载这个Jar包的同学,LitePal开源项目地址:https://github.com/LitePalFramework/LitePal下载一下,首先在自己的程序导入Jar包,在在该项目的assets目录下面新建一个litepal.xml文件,第三步再在AndroidManifest.xml中配置一下LitePalApplication了,三步就完成了该包的导入过程!
使用LitePal后呢,我们就不用"create table province (" + "id integer primary key autoincrement, " + "province_name varchar, " + "province_code varchar " )";这样的建表语句了,一切变得很简单。根据对象关系映射模式的理念,每一张表都应该对应一个模型(Model),也就是说,如果我们需要先建一张Provinces表,就应该有一个对应的Province模型类。需要什么表建立一个相应的类,里头有什么列就在模型类里对应怎样的字段,让我们看一下!Provinces类!
- import java.util.ArrayList;
- import java.util.List;
- import org.litepal.crud.DataSupport;
- public class Province extends DataSupport {
- private int id;
- private String province_name;
- private String province_code;
- private List<City> cities = new ArrayList<City>();
- public String getProvince_code() {
- return province_code;
- }
- public void setProvince_code(String province_code) {
- this.province_code = province_code;
- }
- public List<City> getCities() {
- return cities;
- }
- public void setCities(List<City> cities) {
- this.cities = cities;
- }
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getProvince_name() {
- return province_name;
- }
- public void setProvince_name(String province_name) {
- this.province_name = province_name;
- }
- }
以上就是我们的Provinces类,我们可以看到该模型类中有id,province_name,provice_code以及cities字段,并且都实现了其中的get set方法,这边要注意一定要实现getset方法的,要不然建表会失败的!此外,id属性可写可不写,LitePal都会和人性化的自动生成的,重点在我们这边有一个CITY的集合,这个是干什么的呢,就是用来描述一个province对应着多个city,所以建立了一个list集合,那么在city表又怎么表示多对一的关系呢!
- import java.util.ArrayList;
- import java.util.List;
- import org.litepal.crud.DataSupport;
- public class City extends DataSupport {
- private int id;
- private String city_name;
- private String city_code;
- private Province province;
- private List<County> counties = new ArrayList<County>();
- public List<County> getCounties() {
- return counties;
- }
- public void setCounties(List<County> counties) {
- this.counties = counties;
- }
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getCity_name() {
- return city_name;
- }
- public void setCity_name(String city_name) {
- this.city_name = city_name;
- }
- public String getCity_code() {
- return city_code;
- }
- public void setCity_code(String city_code) {
- this.city_code = city_code;
- }
- public Province getProvince() {
- return province;
- }
- public void setProvince(Province province) {
- this.province = province;
- }
- }
从以上看出,我们只要在city表建立一个Province类就好了,表示每一个city都对应着一个Province归属,好的说到这里大家都应该明白怎么实现了吧!细心的朋友还会发现我们每一个类还继承了一个DateSupport,这个类是为了实现LitePal数据库的增删改查操作的哈!
同上County表如下就好了!
- package com.melhc.model;
- import org.litepal.crud.DataSupport;
- public class County extends DataSupport {
- private int id;
- private String county_name;
- private String county_code;
- private City city;
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getCounty_name() {
- return county_name;
- }
- public void setCounty_name(String county_name) {
- this.county_name = county_name;
- }
- public String getCounty_code() {
- return county_code;
- }
- public void setCounty_code(String county_code) {
- this.county_code = county_code;
- }
- public City getCity() {
- return city;
- }
- public void setCity(City city) {
- this.city = city;
- }
- }
- <?xml version="1.0" encoding="utf-8"?>
- <litepal>
- <dbname value="weather" >
- </dbname>
- <version value="2" >
- </version>
- <list>
- <mapping class="com.melhc.model.Province" >
- </mapping>
- <mapping class="com.melhc.model.City" >
- </mapping>
- <mapping class="com.melhc.model.County" >
- </mapping>
- </list>
- </litepal>
OK,下面为了方便我们的数据库操作,我们在封装一个 WeatherDB类,实现对数据库的正式生成和增删改查操作!
- <span style="font-family:Microsoft YaHei;font-size:14px;"> /**
- * 将provice实例存储到数据库
- */
- public void saveProvice(Province province) {
- if (province != null) {
- province.save();
- }
- }</span>
我们可以看到在存储province的信息时,该方法只要传入一个province类,然后调用province.save方法就可以实现数据库的插入,那么怎么插入数据库这些字段的呢,我们只要先新建一个province类,然后再把网络读取的信息通过province.setProvice_name()等set方法传入即可了,是不是非常简单呢!Province province = new Province();province.setProvince_code(array[0]); province.setProvince_name(array[1]);
那么下面问题又来了我们怎么实现city与Province的表与表之间的关联关系呢,其实也非常简单,只要将city对应的province得到,并用set方法构造进入就好了!我们来看一下
- City city = new City();
- city.setCity_code(array[0]);
- city.setCity_name(array[1]);
- city.setProvince(province);
- weatherDB.saveCity(city);</span>
- /**
- * 从数据库读取全国所有的省份信息
- */
- public List<Province> loadProvices() {
- List<Province> list = DataSupport.findAll(Province.class);
- return list;
- }
- /**
- * 从数据库读取某省下的所有的城市信息
- */
- public List<City> loadCities(int provinceId) {
- Province provice = DataSupport.find(Province.class, provinceId,true);
- List<City> list = provice.getCities();
- return list;
- }
- package com.melhc.db;
- import java.util.List;
- import org.litepal.crud.DataSupport;
- import org.litepal.tablemanager.Connector;
- import com.melhc.model.City;
- import com.melhc.model.County;
- import com.melhc.model.Province;
- import android.database.sqlite.SQLiteDatabase;
- public class WeatherDB {
- /**
- * 一些基本的数据库方法封装
- */
- private SQLiteDatabase db;
- private static WeatherDB weatherDB;
- public WeatherDB() {
- // TODO Auto-generated constructor stub
- db = Connector.getDatabase();//正式生成数据库
- }
- public synchronized static WeatherDB getInstance() {
- if (weatherDB == null) {
- weatherDB = new WeatherDB();
- }
- return weatherDB;
- }
- /**
- * 将provice实例存储到数据库
- */
- public void saveProvice(Province province) {
- if (province != null) {
- province.save();
- }
- }
- /**
- * 从数据库读取全国所有的省份信息
- */
- public List<Province> loadProvices() {
- List<Province> list = DataSupport.findAll(Province.class);
- return list;
- }
- /**
- * 将city实例存储到数据库
- */
- public void saveCity(City city) {
- if (city != null) {
- city.save();
- }
- }
- /**
- * 从数据库读取某省下的所有的城市信息
- */
- public List<City> loadCities(int provinceId) {
- Province provice = DataSupport.find(Province.class, provinceId,true);
- List<City> list = provice.getCities();
- return list;
- }
- /**
- * 将county实例存储到数据库
- */
- public void saveCounty(County county) {
- if (county != null) {
- county.save();
- }
- }
- /**
- * 从数据库读取某城市下的所有的县信息
- */
- public List<County> loadCounties(int cityId) {
- City city = DataSupport.find(City.class, cityId,true);
- List<County> list = city.getCounties();
- return list;
- }
- /**
- * 关闭数据库
- */
- public void destroyDB() {
- if (db != null) {
- db.close();
- }
- }
- }
好的,这一节课的内容就讲到这里,第一次博文码那么多字,感觉萌萌哒啊,希望大家通过这篇博文能对LitePaL框架有一个更好的认识,也希望大家能继续支持该系列的博文,你们的支持是我写下去的最大动力!今天的数据库设计就到此结束,下一篇博文
一起来开发Android的天气软件(三)
下面是该应用的Git开源地址,https://github.com/melhc/SimpleWeather
一起来开发Android的天气软件(三)——使用Volley实现网络通信
距离上一篇一起来开发Android天气软件二的时间又将近半个月了,之间一直因为有事而没有更新实在抱歉,最近会加快更新的步伐,争取在2015年到来前写完这系列的博文,上一章我们已经使用LitePal框架搭建好了我们所需的数据库,这一章的内容将主要完成关于从中国天气网获取数据的网络通信操作,之前有学习过Android开发的同学应该都知道,Android实现互联网通信主要有两种方法,一种使用HTTPURLCONNECTION,一种使用HttpClient的方式,而我们今天将使用不同于以上两种的方式,使用Volley框架完成我们的网络通信服务。
Volley框架呢是 2013年在Google I/O大会上推出了一个新的网络通信框架。Volley非常的简单易用,在通信性能方面也进行了大幅度的调整,它的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作,也比较适合我们这款软件吧。
一、如何获取数据
二、如何实现网络通信
- package com.melhc.util;
- import java.io.BufferedReader;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.net.HttpURLConnection;
- import java.net.URL;
- public class HttpUtil {
- /*
- * 从服务器端获取省县市的数据
- */
- public static void sendHttpRequest(final String address,
- final HttpCallbackListener listener) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- // TODO Auto-generated method stub
- HttpURLConnection connection = null;
- try {
- //创建一个url对象
- URL url = new URL(address);
- //通过url对象获取HTTPURLCONNCETION实例
- connection = (HttpURLConnection) url.openConnection();
- //设置http的请求所使用的方法为get方法
- connection.setRequestMethod("GET");
- //自由定制一些属性,比如设置连接超时,读取超时的毫秒数
- connection.setConnectTimeout(8000);
- connection.setReadTimeout(8000);
- //获得服务器返回的输入流
- InputStream in = connection.getInputStream();
- //将得到的输入流装换成String字符串
- BufferedReader reader = new BufferedReader(
- new InputStreamReader(in, "utf-8"));
- StringBuffer response = new StringBuffer();
- String line;
- while ((line = reader.readLine()) != null) {
- response.append(line);
- }
- LogUtil.i("HTTPUtil",
- "------------------>" + response.toString());
- if (listener != null) {
- listener.onFinish(response.toString());
- }
- } catch (Exception e) {
- // TODO: handle exception
- if (listener != null) {
- listener.onError(e);
- }
- } finally {
- if (connection != null) {
- connection.disconnect();
- }
- }
- }
- }).start();
- }
- }
- package com.melhc.util;
- /*
- * 网路连接的回掉接口
- */
- public interface HttpCallbackListener {
- void onFinish(String response);
- void onError(Exception e);
- }
- HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
- @Override
- public void onFinish(String response) {
- // TODO Auto-generated method stub
- boolean result = false;
- if ("province".equals(type)) {
- result = Utility
- .handleProvicesResponse(weatherDB, response);
- } else if ("city".equals(type)) {
- result = Utility.handleCitiesResponse(weatherDB, response,
- selectedProvince);
- } else if ("county".equals(type)) {
- result = Utility.handleCountiesResponse(weatherDB,
- response, selectedCity);
- }
三、Volley实现网络通信
接下来我们就开始正式使用Volley完成上面一摸一样的通信流程,只需要三步即可完成网络的发送与响应!首先还是得先下载一下Volley的jar文件,并且导入自己的程序中!volley.jar下载地址:http://download.csdn.net/detail/u013900875/8279223。接下来按以下三步走即可!1. 创建一个RequestQueue对象。
2. 创建一个StringRequest对象。
3. 将StringRequest对象添加到RequestQueue里面。
具体的代码实现如下所示
- RequestQueue mQueue = Volley.newRequestQueue(getApplicationContext());
- StringRequest stringRequest = new StringRequest(address,
- new Response.Listener<String>() {
- @Override
- public void onResponse(String response) {
- LogUtil.i("TAG", "---------------->"+response);
- boolean result = false;
- if ("province".equals(type)) {
- result = Utility.handleProvicesResponse(weatherDB,
- response);
- } else if ("city".equals(type)) {
- result = Utility.handleCitiesResponse(weatherDB,
- response, selectedProvince);
- } else if ("county".equals(type)) {
- result = Utility.handleCountiesResponse(weatherDB,
- response, selectedCity);
- }
- if (result) {
- // 通过runonUiMainThread方法返回主线程处理逻辑
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- // TODO Auto-generated method stub
- closeProgressDialog();
- if ("province".equals(type)) {
- queryProvinces();
- } else if ("city".equals(type)) {
- queryCities();
- } else if ("county".equals(type)) {
- queryCounties();
- }
- }
- });
- }
- }
- }, new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError error) {
- LogUtil.i("TAG", "-------------------->" + error);
- runOnUiThread(new Runnable() {
- @Override
- public void run() { // TODO Auto-generated method
- // stub //
- closeProgressDialog();
- Toast.makeText(getApplicationContext(),
- "加载数据失败!", Toast.LENGTH_SHORT).show();
- }
- });
- }
- });
- mQueue.add(stringRequest);
- RequestQueue mQueue = Volley.newRequestQueue(context);
- StringRequest stringRequest = new StringRequest(address,
- new Response.Listener<String>() {
- @Override
- public void onResponse(String response) {
- Log.d("TAG", response);
- }
- }, new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError error) {
- Log.e("TAG", error.getMessage(), error);
- }
- });
- mQueue.add(stringRequest);
离上一篇文章过去才4、5天,我们赶紧趁热打铁继续完成该系列的天气软件的开发。承接上一章的内容使用Volley实现网络的通信,返回给我们的是这一串Json数据{"weatherinfo":{"city":"杭州","cityid":"101210101","temp1":"1℃","temp2":"10℃","weather":"多云转晴","img1":"n1.gif","img2":"d0.gif","ptime":"18:00"}},不知有没有同学跟着我的步骤已经得到了以上的Json数据呢,接下来我们需要在我们的Android对以上数据解析!Lets go!
一、什么是Json?
Json是一种类似于XML的通用数据交换格式,具有比XML更高的传输效率,体积较小,在网络传输时也可以更节省流量。但缺点也有,相比于XML语义性更差,看起来远不如XML直观。
从结构上看,所有的数据(data)最终都可以分解成三种类型,但现在基本上常用的就是映射(mapping)这种类型,一个名/值对(Name/value),即数据有一个名称,还有一个与之相对应的值,这又称作散列(hash)或字典(dictionary),比如"首都:北京"。它的规格呢也是非常简单固定的。
(1) 并列的数据之间用逗号(",")分隔,如"city":"杭州","cityid":"101210101",city与cityid两个数据之间是用,隔开的
(2) 映射用冒号(":")表示。如"city":"杭州"
(3) 并列数据的集合(数组)用方括号("[]")表示。比如如果返回的数据是有好几天的,那么天气的数据就会有好几组,会返回类似以下的数据形式"weatherinfo":[{"city":"杭州","cityid":"101210101","temp1":"1℃","temp2":"10℃","weather":"多云转晴","img1":"n1.gif","img2":"d0.gif","ptime":"18:00"},{"city":"杭 州","cityid":"101210101","temp1":"1℃","temp2":"10℃","weather":"多云转晴","img1":"n1.gif","img2":"d0.gif","ptime":"18:00"}]
(4) 映射的集合(对象)用大括号("{}")表示。例如中国天气网给我们返回的首先是一整个是Weather对象,然后里头包含一个Weatherinfo对象。
二、如何解析Json数据?
我们先使用最简单的方法解析中国天气网返回的数据。
- /**
- * 解析服务器返回的JSON数据,并将解析出的数据存储到本地。
- */
- public static void handleWeatherResponse(Context context, String response) {
- try {
- JSONObject jsonObject = new JSONObject(response);
- JSONObject weatherInfo = jsonObject.getJSONObject("weatherinfo");
- String cityName = weatherInfo.getString("city");
- String weatherCode = weatherInfo.getString("cityid");
- String temp1 = weatherInfo.getString("temp1");
- String temp2 = weatherInfo.getString("temp2");
- String weatherDesp = weatherInfo.getString("weather");
- String publishTime = weatherInfo.getString("ptime");
- saveWeatherInfo(context, cityName, weatherCode, temp1, temp2,
- weatherDesp, publishTime);
- } catch (JSONException e) {
- e.printStackTrace();
- }
- }
将服务器返回的response通过创建一个JSONObject对象 jsonobject,再通过weatherinfo这个key值去获取它所对应weatherinfo所包括的属性,之后就通过getString()通过键值对映射的方法一个个获取其中的对象值啦!是不是觉得写起来好麻烦啊,有重复啊!无脑操作的代码要重复写好几遍了。
三、使用Gson解析数据?
如果你认为JSONObject解析JSON数据的方法已经足够简单,那你真的太容易满足了,Gson开源库可以让解析数据简单到难以置信的!不过万事的开头你还得先去下载一下GSONde jar包,导入自己的程序文件里。
然后呢我们再看一下我们要解析的Json数据格式{"weatherinfo":{"city":"杭州","cityid":"101210101","temp1":"1℃","temp2":"10℃","weather":"多云转晴","img1":"n1.gif","img2":"d0.gif","ptime":"18:00"}},根据其格式我们先定义一个weather类,
- package com.melhc.model;
- public class Weather {
- private Weatherinfo weatherinfo;
- public Weatherinfo getWeatherinfo() {
- return weatherinfo;
- }
- public void setWeatherInfo(Weatherinfo weatherinfo) {
- this.weatherinfo = weatherinfo;
- }
- }
然后这个weather类里有一个weatherinfo对象,这个对象呢又包含着city,cityid,temp1等等的对象,该怎么办呢!
- package com.melhc.model;
- public class Weatherinfo {
- private String city;
- private String cityid;
- private String temp1;
- private String temp2;
- private String weather;
- private String ptime;
- public String getCity() {
- return city;
- }
- public void setCity(String city) {
- this.city = city;
- }
- public String getCityid() {
- return cityid;
- }
- public void setCityid(String cityid) {
- this.cityid = cityid;
- }
- public String getTemp1() {
- return temp1;
- }
- public void setTemp1(String temp1) {
- this.temp1 = temp1;
- }
- public String getTemp2() {
- return temp2;
- }
- public void setTemp2(String temp2) {
- this.temp2 = temp2;
- }
- public String getWeather() {
- return weather;
- }
- public void setWeather(String weather) {
- this.weather = weather;
- }
- public String getPtime() {
- return ptime;
- }
- public void setPtime(String ptime) {
- this.ptime = ptime;
- }
- @Override
- public String toString() {
- return "WeatherInfo [city=" + city + ", cityid=" + cityid + ", temp1="
- + temp1 + ", temp2=" + temp2 + ", weather=" + weather
- + ", ptime=" + ptime + "]";
- }
- }
只要在定义一个weatherinfo类就好了,添加city等等字段到该类里头,并且注意属性的数据类型要一一对应的否则会解析失败的,万事具备,剩下来的就交给Gson吧
- public static void handleWeatherResponse(Context context, String response) {
- try {
- Gson gson = new Gson();
- Weather weather = gson.fromJson(response, Weather.class);
- Weatherinfo info = weather.getWeatherinfo();
- saveWeatherInfo(context, info);
- } catch (Exception e) {
- // TODO: handle exception
- }
- }
看到没有只需要三步就完成了数据的解析,有没有很简单呢!我们只需要通过gson,from()方法就可以把数据内容映射到指定类中!SO,easy! 大家可能注意到在这三步骤结束之后还有一个saveWeatherinfo方法,这个方法用来干嘛的呢!
- public static void saveWeatherInfo(Context context, Weatherinfo info) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy年M月d日", Locale.CHINA);
- SharedPreferences.Editor editor = PreferenceManager
- .getDefaultSharedPreferences(context).edit();
- editor.putBoolean("city_selected", true);
- editor.putString("city_name", info.getCity());
- editor.putString("weather_code", info.getCityid());
- editor.putString("temp1", info.getTemp1());
- editor.putString("temp2", info.getTemp2());
- editor.putString("weather_desp", info.getWeather());
- editor.putString("publish_time", info.getPtime());
- LogUtil.i("UTILITY", "----------------->" + sdf.format(new Date()));
- editor.putString("current_date", sdf.format(new Date()));
- editor.commit();
- }
这个方法就是我们使用Sharedpreference共享参数存储一下我们当天得到的天气数据,这样用户每次打开就可以直接读取之前得到的数据,在有需要的时候再通过网络获取即时的天气数据就好了,editor.put方法还不能直接存储类,只能存储基本的数据类型,我们这里就只能一个个put啦!
好的,这一节课的内容就讲到这里,也希望大家能继续支持该系列的博文,你们的支持是我写下去的最大动力!今天的GSON解析数据的内容就到此结束,下一篇博文也会很快跟大家见面的。