目录
摘要1需求分析2一、开发背景2二、项目需求分析2总体设计2一、系统规划2二、系统功能界面3设置预报城市界面:3天气显示界面:4Widget 桌面小部件界面:5三.设计目标6系统设计6一、开发及运行环境6二、数据库设计6三、主要方法及步骤7四、主要方法及技术7主要模块7一、项目框架7二、主要功能实现8获取城市码 db_weather.db 数据库文件8实现可伸缩性列表的的构建与过滤12GPS 定位功能的实现15Widget 窗体小部件的更新18功能测试19结论23
摘要
Window 操作系统的诞生成就了微软帝国,同时也造就了 PC 时代的繁荣, 然而如今,以 Android 和 iPhone 手机为代表的智能移动设备的发明与互联网云技术的兴起却敲响了 PC 时代的丧钟!这也预示着移动互联网时代(3G)已经来临。
在这个互联网繁荣的时代,有一颗超新星,以它独特性能优势与人性化的
UI 设计使它在短短的几年迅速的占领了智能移动设备的市场份额,它就是
Google 的 Android!这也意味着 Google 在移动互联网时代开始抢跑并领跑。
Android 是基于 Linux 平台完全开源的手机操作系统,同时开发语言为
Java,这对于 Java 开发的我们是何等的诱人,程序员的技术要与时代同行,因此我选择了以 Android 为平台的手机天气预报系统来作为我的毕业设计,选择手机天气预报系统不仅可以提升技术,同时也很实用,为人们时刻了解天气状况和出行带来了方便。
需求分析
一、开发背景
近几年来随着 3G 技术成熟和智能手机的不断普及,移动应用的需求与日俱增,移动应用开发成为当下最热门的技术之一。在 Google 和 Android 手机联盟的共同推动下,Android 在众多移动应用开发平台中脱颖而出。Android 是一个真正意义上的开源智能手机操作系统,该系统一经推出立即受到全球移动设备厂商和开发者的热捧。为顺应潮流,本设计旨在搭载 Android 的移动设备上运行, 实现天气状况的实时动态更新与显示,方便人们的出行与生活。
二、项目需求分析
根据功能的需求,分析此项目的主要功能应具备以下几点:
精确查询定位全国各地城市未来几天内的实时天气状况
系统要具的实用性,符合用户查看信息习惯,界面设计优美
系统要具有稳定性,且在一定程度上节省流量的开销
总体设计
一、系统规划
由上述的需求,现将系统分为三大模块:天气显示界面模块、预报城市设置模块与 Widget 桌面小部件模块。各系统模块功能如下:
天气显示界面模块
显示指定城市三天内的天气状况,包括日期、城市名称、温度、风力与当日的建议,用户可通过按菜单键来显示菜单更新当前天气与设置天气显示的界面背景,以及跳转至设置预报城市界面来更换预报城市。
预报城市设置模块
由自动设置预报城市与手动设置二部分组成,自动设置实现 GPS 定位功能,自动确定当前用户所在地;而手动设置则通过可伸展性下拉列表单击选择系统数据库中预存的城市来进行设置,同时为了方便用户查找,支持以输入框的形式来过滤查询预报城市。当单击选中城市时跳转至天气显示界面,来显示该城市当三天内的天气状况;第一次运行时自动跳到该界面。
Widget 桌面小部件模块
为了方便用户实时了解天气状况,特别添加在 Android 系统桌面上显示当前天气与时间的天气小部件,使用户拿起手机的第一时刻就能了解天气,同时当用户单击小部件时,自动跳转至天气显示界面,显示三天内的详细天气。
二、系统功能界面
设置预报城市界面:
当第一次运行程序时,跳转至城市设置界面进行预报城市的选择:
![](https://i-blog.csdnimg.cn/blog_migrate/8cf49995ee85a20727b932569ede70c4.png)
用户可以通过单击选择“定位当前城市”的方式调用系统 GPS 功能自动定位预报城市:
![](https://i-blog.csdnimg.cn/blog_migrate/cf1bd30c7b46501d7db3c7ce55b02007.png)
用户可通过输入框过滤查询当前系统中预存的城市:
![](https://i-blog.csdnimg.cn/blog_migrate/719550dcced2208bcfa701bbecb69825.png)
天气显示界面:
选择了预报城市后,系统跳转至天气显示界面,显示该城市三天内的实时天气:
![](https://i-blog.csdnimg.cn/blog_migrate/7aff25c3b8cf71173c6ae95c67b36ba4.jpeg)
在天气界面中用户可通过按菜单键来调出菜单,选择城市,更新天气与更换背景:
![](https://i-blog.csdnimg.cn/blog_migrate/2b074d4b8890dad370d143be54886213.jpeg)
Widget 桌面小部件界面:
方便用户第一时间了解天气动态,添加 widget 显示功能界面:
![](https://i-blog.csdnimg.cn/blog_migrate/93dafd2cfa0625c7c316ca5e11ef265c.jpeg)
三.设计目标
设计完成一个实用稳定的天气预报系统,同时要廉价使其能滿足大部分用户的需求,因此针对上述要求,本设计应滿足:
系统能及时的返反馈指定预报城市的天气情况
自动定位用户所在城市,支持 GPS 定位
节省流量开销,规定在指定的时间间隔内才更新天气,其它时段显示缓存的天气
操作方便快捷,使用简单,界面设计美观大方,支持 widget
系统设计
一、开发及运行环境
JDK1.6.10
Eclipse3.5
Android Development Toolkit (ADT) 15.0.0 Android 2.2 及以上
Windows XP 及以上
二、数据库设计
由于在本系统中是通过中央气象台的WebService 提供的API 访问得到的天气预报,在查询指定城市的天气时,需要用到它提供的城市码,而城市码相对稳定不变,所以在构建系统时将其事先通过 Android 的网络访问技术将其缓冲到本地
![](https://i-blog.csdnimg.cn/blog_migrate/dd2e14e853cbc343d3b63f970af6a006.png)
SQLite 数据库进行保存起来,方便以后的查询,同时节省了流量开销。综上所述在本地建立 db_weather.db 的数据库,其中的表结构如下:
其中只存在两个表: provices 和 citys
City 中存在 city_num 用天气的查询,同时还存在外键 province_id 与 provices 表形成 1 对 n 的关系。
三、主要方法及步骤
搭建 Android 开发环境,并建立一个 android2.2 版本名为 WeatherSystem
项目
首先编写网络访问代码,访问 http://m.weather.com.cn/data5/city.xml 中央气象站解析得到所有城市码并导出保存得到的 db_weather.db 数据文件
在程序第一次运行时,将 db_weatcher.db 数据库文件导入到应用程序数据库中
建立设置城市界面,读取数据库文件,获取省份,城市以及对应的城市码。
接收用户选择的城市码,访问:http://m.weather.com.cn/data/<城市码>.html 得到天气信息
解析天气信息,将城市码及天气信息缓冲下来,并为其设置有效时间,方便下次启动时直接得到天气信息,过期则从网上更新
定时由保存的城市码更新天气信息
四、主要方法及技术
Android 手机的界面 UI 设计
Android 的网络通信
Android 的广播
GPS 调用解析
Widget 小部件编程
XML 与 JSON 解析
SQLite 数据库操作
Android 文件操作
主要模块
一、项目框架
在装有 ADT 插件的 Eclipse 中新建一个名为 WeatherSystem 的 Android2.2 版本的项目, 项目主要文件结构如下:
WeatherSystem
| src
||_com.weather.app
|||_MainActivity.java
|||_SetCityActivity.java
|||_UpdateWidgetService.java
|||_WeatherWidget.java
||_com.weather.comp
|||_GPSListAdapter.java
|||_MyListAdpater.java
||_com.weather.dao
|||_DBHelper.java
||_com.weaher.utils
||_LocationXMParser.java
||_WeatherInfoParser.java
||_WebAccessTools.java
| res
||_ drawable
|||_(略)
||_layout
|||_gps.xml
|||_main.xml
|||_widget_layout.xml
|||_set_city.xml
||_menu
|||_configure_menu.xml
||_raw
|||_db_weather.db
||_values
|||_color.xml
|||_strings.xml
||_xml
||_weather_widget.xml
|
|_AndroidManifest.xml
二、主要功能实现
获取城市码 db_weather.db 数据库文件
获取全国各地的城市码,是通过访问中央气象局网从省份直辖市到城镇一级一级深入得到的,获得一个地区的城市码总共需要访问 4 次网络,分别如下:
01|北京,02|上海,03|天津,04|重庆,05|黑龙江,06|吉林,07|辽宁,08|内蒙古,…
访问 http://m.weather.com.cn/data5/city.xml 得到省份直辖市列表与它的编号:
访问 http://m.weather.com.cn/data5/city<省份编号>.xml 得到该省份直辖市的城市编号(如访问山东:http://m.weather.com.cn/data5/city12.xml)
1201|济南,1202|青岛,1203|淄博,1204|德州,1205|烟台,1206|潍坊,……
120101|济南,120102|长清,120103|商河,120104|章丘,120105|平阴,….
访问 http://m.weather.com.cn/data5/city<城市编号>.xml 得到该城市的县区编号(如访问济南:http://m.weather.com.cn/data5/city1201.xml)
访问 http://m.weather.com.cn/data5/city<县区编号>.xml 得到该县区的城市码(如访问长清:http://m.weather.com.cn/data5/city120102.xml)
120102|101120102
首先实现上述功能需使用 Android 的网络访问技术, 故编写工具类
WebAccessTools 类如下:
/**
根据给定的url地址访问网络,得到响应内容(这里为GET方式访问)
@param url 指定的url地址
@return web服务器响应的内容,为<code>String</code>类型,当访问失败时,返回为null
*/
publicString getWebContent(String url) {
//创建一个http请求对象
HttpGet request = new HttpGet(url);
//创建HttpParams以用来设置HTTP参数HttpParams params=new BasicHttpParams();
//设置连接超时或响应超时HttpConnectionParams.setConnectionTimeout(params, 3000);
HttpConnectionParams.setSoTimeout(params, 5000);
//创建一个网络访问处理对象
HttpClient httpClient = new DefaultHttpClient(params);
try{
//执行请求参数项
HttpResponse response = httpClient.execute(request);
//判断是否请求成功
if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
//获得响应信息
String content = EntityUtils.toString(response.getEntity());
return content;
} else {
//网连接失败,使用Toast显示提示信息
Toast.makeText(context, "网络访问失败,请检查您机器的联网设备!", Toast.LENGTH_LONG).show();
}
}catch(Exception e) { e.printStackTrace();
} finally {
//释放网络连接资源httpClient.getConnectionManager().shutdown();
}
return null;
}
由上面访问的可知,得到的编码与名称都是“编码|名称”的形式,因此在这也编写一个解析得到城市码的工具类 WeatherInfoParser,用于解析从服务器中得到的城市码:
/**
通过解析content,得到一个一维为城市编号,二维为城市名的二维数组
解析的字符串的形式为: <code>编号|城市名,编号|城市名,.</code>
@param content 需要解析的字符串
@return 封装有城市编码与名称的二维数组
*/
public static String[][] parseCity(String content) {
//判断content不为空
if(content!=null&&content.trim().length()!=0) { StringTokenizer st=new StringTokenizer(content, ","); int count = st.countTokens();
String[][] citys = new String[count][2]; int i=0, index=0; while(st.hasMoreTokens()) {
String city = st.nextToken(); index = city.indexOf('|');
citys[i][0] = city.substring(0, index); citys[i][1] = city.substring(index+1); i = i+1;
}
return citys;
}
return null;
}
WebAccessTools webTools = new WebAccessTools(this);
//得到访问网络的内容
String webContent=webTools.getWebContent("http://m.weather.com.cn/data5/city.xml");
//第一次解析得到的为省份或一级直辖市
String[][] provinces = WeaterInfoParser.parseCity(webContent); String[] groups = new String[provinces.length];
String[][] childs = new String[provinces.length][]; String[][] cityCode = new String[provinces.length][]; for(int i=0; i< provinces.length; i++) {
groups[i] = provinces[i][1];
//由省份码来得到城市码
StringBuffer urlBuilder= new StringBuffer("http://m.weather.com.cn/data5/city"); urlBuilder.append(provinces[i][0]);
urlBuilder.append(".xml");
webContent = webTools.getWebContent(urlBuilder.toString());
编写这两个类后现在就是编写从服务器端用程序遍历得到全国各地的城市名与城市码,并将它们分别的保存在 String[][] provinces 数组,String[][] childs 数组与String[][] cityCode 中:
String[][] citys = WeaterInfoParser.parseCity(webContent);
//用于保存所的有towns
String[][][] towns = new String[citys.length][][];
//计算总的城镇数
int sum=0;
for(int j=0; j<citys.length; j++) {
//由城市码来得到地方码
urlBuilder= new StringBuffer("http://m.weather.com.cn/data5/city"); urlBuilder.append(citys[j][0]);
urlBuilder.append(".xml");
webContent = webTools.getWebContent(urlBuilder.toString()); towns[j] = WeaterInfoParser.parseCity(webContent);
sum = sum + towns[j].length;
}
childs[i] = new String[sum]; cityCode[i] = new String[sum]; sum=0;
for(int j=0; j<citys.length; j++) {
for(int n=0; n<towns[j].length; n++) {
if(n==0)
childs[i][sum] = towns[j][n][1];
else
childs[i][sum] = towns[j][0][1] + "." + towns[j][n][1];
urlBuilder= new StringBuffer("http://m.weather.com.cn/data5/city"); urlBuilder.append(towns[j][n][0]);
urlBuilder.append(".xml");
webContent = webTools.getWebContent(urlBuilder.toString()); String[][] code=WeaterInfoParser.parseCity(webContent); cityCode[i][sum] = code[0][1];
sum = sum + 1;
}
}
urlBuilder=null;
}
接下来就是将得到的上面的三个数组建立数据库文件 db_weather.db 保存起来, 用到 android.database.sqlite.SQLiteDatabase 类的静态方法:
SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory)来创建一个数据库文件,其中的 path 表示数据库存放的路径,而 factory 中游标工厂,这里可将它设为空,从而得到 SQLiteDatabase 对象,则再调用它的 execSQL(String
sql)方法来执行保存数据库的操作,从而将上面的三个数组转换为数据库中的数
据,最后使用 ADT 插件中的 DDMS 工具将得到的数据库文件从 Android 模拟器
中导出,最终就得到了 db_weather.db 文件。以后上述的代码就可以不使用,直接将 db_weather.db 文件放入资源文件夹 res 目录中的 raw 目录中,则在程序第一次运行时导入到/data/data/com.weather.app/databases 目录中就行了,其中关于数据库的导入实际是 Java 中文件的复制。
实现可伸缩性列表的的构建与过滤
实现可伸缩性列表是通过继承 android.widget.BaseExpandableListAdapter 适配器实现的,其中主要实现它的 public View getGroupView()得列表的一级列表和public void getChildView()得到列表的二级子列表实现的,在这里由于只是实现文本显示功能,故用 TextView 组件来填充就行了,如果要构造这个自定义的适配器,则只需在提供存放省份直辖市的一级列表的数组 String[] groups 和存放对应的城镇的二级列表的 String[][] childs 就行了。
同时为了兼具过滤功能,还要需再实现 android.widget.Filterable 接口,这个接口有一个 getFilter()返回 Filter 过滤器的列表,故还要提供一个 Filter 过滤类,在本系统中,实现的是一个内部类 CityFilter,它继承 android.widget.Filter 类,覆盖实现了两个方法,一个是 performFiltering()得到 FilterResults 过滤结果对象方法,另一个是根据得到的 FilterResults 对象更新适配器的 publishResults()方法。其中的 performFiltering(CharSequence constraint)方法的实现是通过 constraint
这个关键字以省份直辖市为单位进行匹配,如果匹配成功,则添加该省份以下的所有城市,如果匹配不成功,则再逐一与这个省份的下的城市配匹,则只添加匹配的城市,其中匹配的结果放在 Map<Integer, ArrayList<Integer>> values 这样的向量中,再由新建的 FilterResults 封装返回,(具体实现如下):
首先是对关键字进行判断是否为空,如为空则由 values 添加所有省份与城市,其中的 allGroups 和 allChilds 保存的是所有的省份与对应的城市:
//当过滤条件为空时,返回所有的省份与城市
if(constraint == null || constraint.length() == 0) {
for(int i=0; i<allGroups.length; i++) { ArrayList<Integer> index = new ArrayList<Integer>();
//添加所有与之对应的城市
for(int j=0; j<allChilds[i].length; j++) { index.add(j);
}
values.put(i, index);
}
}
如果关键字 constraint 不为空,则以省份为单位进行匹配,省份匹配的添加下面的所在城镇,如果不匹配,则进行步深入匹配城镇,添加符合条件的城镇:
String filterStr = constraint.toString();
for(int i=0; i<allGroups.length; i++) {
//查找省名是否包含用户输入的字符串
if(allGroups[i].contains(filterStr)) { ArrayList<Integer> index = new ArrayList<Integer>();
//添加所有与之对应的城市
for(int j=0; j<allChilds[i].length; j++) { index.add(j);
}
values.put(i, index);
} else {
ArrayList<Integer> index = new ArrayList<Integer>();
//如果省份名没有,则查找它下面的城市名是否包含
for(int j=0; j<allChilds[i].length; j++) {
if(allChilds[i][j].contains(filterStr)) { index.add(j);
}
}
//如果添加进入了城市,说明存在,则它的省份也添加进去
if(index.size() > 0) { values.put(i, index);
} else {
index = null;
}
}
}
得到过滤的结果后将其用 FilterResource 封装后返回:
FilterResults results = new FilterResults(); results.values = values;
results.count = values.size();
另外的 publishResults(CharSequence constraint,FilterResults results)方法就是根据上面得到的results 对象来得到新的String[] groups 与String[][] Childs 数组, 再调用 BaseExpandableListAdapter 父类的 notifyDataSetChanged()方法来更新列表,从而实现过滤后结果的显示(具体实现如下):
首先将参数 FiltersResuls 对象转换为 Map<Integer, ArrayList<Integer>> filterResult,然后来判断过滤后的结果长度时否为 0,如果长度为 0 则说明过滤后的结果为空,则调用父类的 notifyDataSetInvalidated()方法来阻止列表的更新:
如果长度不为 0,则说明存在过滤结果,则将它转换为 groups 数组与childs
数组,并调用 notifyDataSetChanged()方法实再更新:
String[] newGroups = new String[count]; String[][] newChilds = new String[count][]; int index = 0;
int length = 0;
//得到新的groups和childs
for(int i=0; i<allGroups.length; i++) {
if(filterResult.containsKey(i)) { newGroups[index] = allGroups[i];
//符合条件的城市
ArrayList<Integer> citys = filterResult.get(i); length = citys.size();
newChilds[index] = new String[length];
for(int j = 0; j< length; j++) {
newChilds[index][j] = allChilds[i][citys.get(j)];
}
index = index + 1;
}
}
//设置groups和childs groups = newGroups; childs = newChilds;
//更新列表notifyDataSetChanged();
//判断是否展开列表
count = getGroupCount();
if(count < 34) {
//展开伸缩性列表
for(int i=0; i<count; i++) { provinceList.expandGroup(i);
}
} else {
//收缩伸缩性列表
for(int i=0; i<count; i++) { provinceList.collapseGroup(i);
}
}
如上所述则就实现了带有过滤性可伸展性列表适配性的实现,则在使用时在XML 组件配置文件中使用 ExpandableListView 列表,并调用它的 setAdapter()方法来,加载自定义的适配器。而在使用它的过滤功能时则调用自定义适配器的getFilter()得到过滤 Filter 对象,再调用 Filter 对象的 filter(String)方法实现的,在本系统中才用的时触发文本输入框EditText 的TextChangedListener 事件时调用从而实现手动选择预报城市的过滤查询。
GPS 定位功能的实现
![](https://i-blog.csdnimg.cn/blog_migrate/09b1dbd737c3fda063599fc606e9b2f7.png)
Android 中调用 GPS 功能,首先要获取 GPS 定位管理器 LocationManager, 获取 LocationManager 后就是获取 LocationProvider,可以通过 Criteria 对象设置过滤条件来获得最符合用户需求的 LocationProvider,得到 LocationProvider 后就可通过调用 LocationMananger 对象的 getLastKnownLocation() 方法来获取Location 地址封装对象,最后由实例化的 Geocoder 将 Location 中的经度和纬度反编译为地址信息集合 List 对象,从而由 List 对象来得到当前用户地址名。在开发过程中通过 Eclipse 中的 ADT 插件的 DDMS 可以为 Android 模拟器指定任意地址,如下:
当在模拟器控制面板中指定经纬度后,则会在模拟器中出现 GPS 的标志:
![](https://i-blog.csdnimg.cn/blog_migrate/df9ecdf7d5451daa26d0f09d1a1ce9be.png)
但在实际开发调用 GPS 功能过程时,只能获取经度与纬度,而在使用 Geocoder
![](https://i-blog.csdnimg.cn/blog_migrate/c58a1fd9d2a1909e9d513a443a20e7ea.jpeg)
反编译地址时报错:
使用的调用代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/90170ae4a461f798723fdf65e45f14b7.png)
后来通过网上搜索得知在 Android2.2 模拟器中调用 Geocoder 需要 backend 服务: ”The Geocoder class requires a backend service that is not included in the core android framework. The Geocoder query methods will return an empty list if there no backend service in the platform.“
![](https://i-blog.csdnimg.cn/blog_migrate/790898d46af03b43ea65cec91742b406.png)
但并有说此服务要怎么得到,这看起来像是 Android2.2 模拟器的一个 Bug,故而在本设计中采用访问[http://maps.google.cn/maps/geo?output=xml&q=经度,纬度] 的形式来得到详备的地址信息(来源于:http://www.iteye.com/problems/69517),如下访问 http://maps.google.cn/maps/geo?output=xml&q=30.659269,104.065762:
其中的 output 参数指定的是服务器响应的格式,除了 XML 格式还可以为 JSON, CSV 等格式。
由此可知,为了实现 GPS 的定位功能还需要实现一个解析 XML 的工具类,在本系统中由工具类 LocationXMParser 完成,它继承至 org.xml.sax.helpers.DefaultHandler 类,用于专门用于解析XML 文件。
上述的 GPS 功能具体实现过程如下:
得到 LocationManager 系统定位服务管理者:
LocationManager locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
设置 Geocoder 对象, 过滤得到符合条件的 LocationProvider, 再由
LocationProvider 得到封装经纬度信息的 Location 对象:
//设置一个Criteria标准用于过滤LocationProvider Criteria criteria = new Criteria();
//设置不需要高度信息criteria.setAltitudeRequired(false);
//设置不需要方位信息criteria.setBearingRequired(false);
//得到最好的可用的Provider
String provider = locationManager.getBestProvider(criteria, true);
//得到当前的位置对象
Location location = locationManager.getLastKnownLocation(provider);
double latitude = location.getLatitude();//得到经度
double longitude = location.getLongitude(); //得到纬度
得到经纬度后再访问 http://maps.google.cn/maps/geo 来得到含地址信息的XML 文本内容,并用自定义的 LocationXMParser 工具类解析封装得到此经纬度对应的城市名:
//根据经纬度得到详细的地址信息
//定义的一个网络访问工具类
WebAccessTools webTools = new WebAccessTools(this);
String addressContext = webTools.getWebContent
("http://maps.google.cn/maps/geo?output=xml&q="+ latitude+","+longitude);
//解析地址信息
SAXParserFactory spf = SAXParserFactory.newInstance();
try {
SAXParser parser = spf.newSAXParser(); XMLReader reader = parser.getXMLReader();
LocationXMLParser handler = new LocationXMLParser(); reader.setContentHandler(handler);
StringReader read = new StringReader(addressContext);
// 创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入
InputSource source = new InputSource(read);
//开始解析reader.parse(source);
//判断是否存在地址
if(handler.hasAddress())
return handler.getDetailAddress();
} catch (Exception e) { e.printStackTrace();
}
上面代码中的 getDetailAdress()方法返回的是一个 Map 对象,其中封装了从
XML 中解析得到的国家、省份、县区和城市四个信息,在得到这些信息后,系统将与数据库中预存的省份城市相比较,最终匹配得到该地区的城市码完成 GPS 自动定位功能的实现。
Widget 窗体小部件的更新
由于 widget 中的时钟关系,需要对 widget 显示进行时刻的更新用来保持与系统中时间的一致。实现这个功能需要用到 AlarmManager 类,这个类专门用来设定在某个指定的时间去完成指定的事件。设计思路是在 Widget 的 onUpdate 方法中启动一个自定义更新后台服务,更新 widget,并设定下一分钟再次调用此服务。具体实现过步骤如下:
首先自定义一个后台运行服务类继承至 Service 类,实现它的服务开始运行调用的 onStart()方法:
super.onStart(intent, startId);
//得到widget的布局对象
RemoteViews views = WeatherWidget.getWeatherView(this);
//得到AppWidgetManager widget管理器
AppWidgetManager appWidgetManager=AppWidgetManager.getInstance(this);
int[] appids=appWidgetManager.getAppWidgetIds(new ComponentName(this,
WeatherWidget.class));
//得到城市码,并更新天气
SharedPreferences sp=getSharedPreferences(
SetCityActivity.CITY_CODE_FILE, SetCityActivity.MODE_PRIVATE);
String cityCode= sp.getString("code", "");
if(cityCode!=null&&cityCode.trim().length() > 0) { WeatherWidget.updateAppWidget(views, this,
appWidgetManager, cityCode);
}
appWidgetManager.updateAppWidget(appids, views);
//获取当前时间设置警报服务 Date date = new Date(); long now =date.getTime(); long unit=60000;//间隔一分钟
int s=date.getSeconds();//得到秒数
unit=60000-s*1000;//将时间精确到秒pintent=PendingIntent.getService(this, 0, intent, 0);
//计时器alarm=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
//AlarmManager.RTC_WAKEUP设置服务在系统休眠时同样会运行
//第二个参数是下一次启动service时间alarm.set(AlarmManager.RTC_WAKEUP, now+unit, pintent);
然后在实现 AppWidgetProvider 的 widget 类的 onUpdate 方法中启动这个自定义的服务:
//启动一个自定义更新widget的后台服务
context.startService(new Intent(context,UpdateWidgetService.class));
除了启动这个服务是不够的,当用户删除 widget 部件时,后台服务也必须停止,这样就必须实现 Service 类中的另一个方法 onDestroy()方法,该方法在 Service 停止时调用,在这里用于取消 AlarmManager 设置的警报服务:
//当widget中通过调用context.stopService方法来指定销毁service时,被调用
public void onDestroy() {
//取消定时管理
if(alarm!=null) { alarm.cancel(pintent);
}
super.onDestroy();
}
public void onDisabled(Context context) {
super.onDisabled(context);
//关闭后台服务
context.stopService(new Intent(context,UpdateWidgetService.class));
}
则在widget 类的onDisabled()方法中调用stopService 方法来停止后台服务, 其中的 onDisabled 方法在 widget 被用户删除时由系统自动调用:
在自定了一个 Service 服务后,系统必须在清单文件 AndroidManifest.xml 中声明自定义的服务:
<!-- 自定义的后台更新widget服务 -->
<service android:name=".UpdateWidgetService"></service>
这样就实现了widget的 更 新 功 能 , 其 实 在 配 制widget的
AppWidgetProviderInfo 的配制文件中通过设置 android:updatePeriodMillis 属性来设置 widget 的自动更新,它的单位中毫秒,但由于自动更新对性能的损坏,故它有一个最低值,这最低值长于一分钟,故而与该系统的要求不符,故以调用后台 Service 的方式来解决。
功能测试
本系统的的测试,主要是通过手动操作该系统,查看是否存在异常或操作的结果是否符合设计初衷来完成的,现将系统的主要操作结果截图展示如下:
一. 当系统初次启动时,由于没有设置预报城市,则自动由 MianActivity 天气显示视图跳转到 SetCityActivity 城市设置视图,并要求用户选择预报城市,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/5a4f3e061742d57ccdd008983ed23bc0.jpeg)
二. 接下来点击确定选择城市,则系统确定用户单击城市的城市码跳转至天气显示界面,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/833e6c14ad048b66a83da907b68e06d0.png)
![](https://i-blog.csdnimg.cn/blog_migrate/00296c395fbcf703c27c42389cc232f7.png)
三. 跳转到天气显示界面后,可单击“菜单”按钮来显示天气的菜单:
![](https://i-blog.csdnimg.cn/blog_migrate/df67e4d76f001c98a5902491bb61d3f2.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/c8780687cdf0dc7f1ffdda667efed542.jpeg)
四. 选择“壁纸”菜单项可以更换程序背景图片如下:
![](https://i-blog.csdnimg.cn/blog_migrate/9ed511a4f171133204c6bdc2a4acc3b9.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/fe01d671ec50ded07ad6feaf0d0bd029.jpeg)
五. 选择“设置城市”菜单项,则跳转至设置城市的视图界面,在这里可以在输入城市的关键字在进行过滤查询,这样就及大的方便了用户的选择:
![](https://i-blog.csdnimg.cn/blog_migrate/14f2a79b91780c2cba9808caaaaca80c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ba89458169c3e6fc9219ed321ac7fe0c.png)
六. 如果要定位当前的城市,以单击“定位当前城市”自动设置城市,而在此之前必须,在 DDMS 控制面板中在指定一个经纬度,来开启 GPS 功能:
![](https://i-blog.csdnimg.cn/blog_migrate/02bf7161e70278fdf9a9f65f4b08444b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4cd5bc9d083f62750ff48e59bf42a769.png)
七. widget 窗体小部件的添加是通过长按桌面的空白处,出现一个选项菜单, 然后选择“窗体小部件”,接着在弹出的选项中选择“天气精灵”即本系统,则设计的小窗体部件将会出现在主屏幕的桌面上:
![](https://i-blog.csdnimg.cn/blog_migrate/5d7bce1f9b2e4b3ae3ca483744ba284b.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/139bb98b52ae340d11a79ff91be5a3e2.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/78b7c48804375b32ecf13c6696eef2d1.jpeg)
结论
本系统基本实现了需求的中的天气预报的功能,界面设计也较漂亮,具有一定的实用性。其中的天气数据全部来源于网络,故在运行时一定要确保系统所处环境的网络流畅性。由于开发过程是在 Android2.2 版本的模拟器上运行的,故测试的结果可能存在一定的局性性。同时在设计之初考虑问题的不全面,使得在无网状态下设计不完善,运行本系统在无网状态下时会异常停止。而且由于本人的技术有限,使得在 GPS 自动定位时功能不稳定。
通过本系统的开发使得我对于 Android 平台的应用开发有了一定的了解,特别是其中的 GPS 功能与 Widget 的更新功能的实现让我体会到了实践的意义,只有自己认真动手才能算真正的掌握,书本上的知识也不一定完全准确,在遇到问题时可以通过网上搜索的方式得到解决。同时在本系统中反映的问题也让我意识到了自己的一些不足,明解了以后学习时的基本方向。
参考文献:
《Android 应用开发揭秘》 杨丰盛 机械工业出版社
《Android 应用开发详解》 郭宏志 电子出版社
Google Android API