手机天气预报系统APP源码和设计报告

170 阅读17分钟

目录

摘要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 的移动设备上运行, 实现天气状况的实时动态更新与显示,方便人们的出行与生活。

二、项目需求分析

根据功能的需求,分析此项目的主要功能应具备以下几点:

  1. 精确查询定位全国各地城市未来几天内的实时天气状况

  2. 系统要具的实用性,符合用户查看信息习惯,界面设计优美

  3. 系统要具有稳定性,且在一定程度上节省流量的开销
    总体设计
    一、系统规划
    由上述的需求,现将系统分为三大模块:天气显示界面模块、预报城市设置模块与 Widget 桌面小部件模块。各系统模块功能如下:

     

    1. 天气显示界面模块
      显示指定城市三天内的天气状况,包括日期、城市名称、温度、风力与当日的建议,用户可通过按菜单键来显示菜单更新当前天气与设置天气显示的界面背景,以及跳转至设置预报城市界面来更换预报城市。
    2. 预报城市设置模块
      由自动设置预报城市与手动设置二部分组成,自动设置实现 GPS 定位功能,自动确定当前用户所在地;而手动设置则通过可伸展性下拉列表单击选择系统数据库中预存的城市来进行设置,同时为了方便用户查找,支持以输入框的形式来过滤查询预报城市。当单击选中城市时跳转至天气显示界面,来显示该城市当三天内的天气状况;第一次运行时自动跳到该界面。
    3. Widget 桌面小部件模块

为了方便用户实时了解天气状况,特别添加在 Android 系统桌面上显示当前天气与时间的天气小部件,使用户拿起手机的第一时刻就能了解天气,同时当用户单击小部件时,自动跳转至天气显示界面,显示三天内的详细天气。

二、系统功能界面

  1. 设置预报城市界面:

    1. 当第一次运行程序时,跳转至城市设置界面进行预报城市的选择:

 

安卓APP源码和设计报告——手机天气预报系统

计算机毕设——手机天气预报系统

计算机毕设——手机天气预报系统[/caption]

 

    1. 用户可以通过单击选择“定位当前城市”的方式调用系统 GPS 功能自动定位预报城市:

安卓APP源码和设计报告——手机天气预报系统

计算机毕设——手机天气预报系统

    1. 用户可通过输入框过滤查询当前系统中预存的城市:

安卓APP源码和设计报告——手机天气预报系统

计算机毕设——手机天气预报系统

  1. 天气显示界面:

    1. 选择了预报城市后,系统跳转至天气显示界面,显示该城市三天内的实时天气:

安卓APP源码和设计报告——手机天气预报系统

计算机毕设——手机天气预报系统

    1. 在天气界面中用户可通过按菜单键来调出菜单,选择城市,更新天气与更换背景:

安卓APP源码和设计报告——手机天气预报系统

计算机毕设——手机天气预报系统

  1. Widget 桌面小部件界面:

方便用户第一时间了解天气动态,添加 widget 显示功能界面:

安卓APP源码和设计报告——手机天气预报系统

计算机毕设——手机天气预报系统

三.设计目标

设计完成一个实用稳定的天气预报系统,同时要廉价使其能滿足大部分用户的需求,因此针对上述要求,本设计应滿足:

  1. 系统能及时的返反馈指定预报城市的天气情况
  2. 自动定位用户所在城市,支持 GPS 定位
  3. 节省流量开销,规定在指定的时间间隔内才更新天气,其它时段显示缓存的天气
  4. 操作方便快捷,使用简单,界面设计美观大方,支持 widget

系统设计

一、开发及运行环境

JDK1.6.10

Eclipse3.5

Android Development Toolkit (ADT) 15.0.0 Android 2.2 及以上

Windows XP 及以上

二、数据库设计

由于在本系统中是通过中央气象台的WebService 提供的API 访问得到的天气预报,在查询指定城市的天气时,需要用到它提供的城市码,而城市码相对稳定不变,所以在构建系统时将其事先通过 Android 的网络访问技术将其缓冲到本地

安卓APP源码和设计报告——手机天气预报系统

计算机毕设——手机天气预报系统

SQLite 数据库进行保存起来,方便以后的查询,同时节省了流量开销。综上所述在本地建立 db_weather.db 的数据库,其中的表结构如下:

其中只存在两个表: provices 和 citys

City 中存在 city_num 用天气的查询,同时还存在外键 province_id 与 provices 表形成 1 对 n 的关系。

三、主要方法及步骤

  1. 搭建 Android 开发环境,并建立一个 android2.2 版本名为 WeatherSystem
    项目
  2. 首先编写网络访问代码,访问 m.weather.com.cn/data5/city.…中央气象站解析得到所有城市码并导出保存得到的 db_weather.db 数据文件
  3. 在程序第一次运行时,将 db_weatcher.db 数据库文件导入到应用程序数据库中
  4. 建立设置城市界面,读取数据库文件,获取省份,城市以及对应的城市码。
  5. 接收用户选择的城市码,访问:m.weather.com.cn/data/<城市码>.html 得到天气信息
  6. 解析天气信息,将城市码及天气信息缓冲下来,并为其设置有效时间,方便下次启动时直接得到天气信息,过期则从网上更新
  7. 定时由保存的城市码更新天气信息

四、主要方法及技术

  1. Android 手机的界面 UI 设计
  2. Android 的网络通信
  3. Android 的广播
  4. GPS 调用解析
  5. Widget 小部件编程
  6. XML 与 JSON 解析
  7. SQLite 数据库操作
  8. 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

二、主要功能实现

  1. 获取城市码 db_weather.db 数据库文件
    获取全国各地的城市码,是通过访问中央气象局网从省份直辖市到城镇一级一级深入得到的,获得一个地区的城市码总共需要访问 4 次网络,分别如下:

     

    1. 01|北京,02|上海,03|天津,04|重庆,05|黑龙江,06|吉林,07|辽宁,08|内蒙古,…
      访问 m.weather.com.cn/data5/city.… 得到省份直辖市列表与它的编号:
    2. 访问 m.weather.com.cn/data5/city<省份编号>.xml 得到该省份直辖市的城市编号(如访问山东:m.weather.com.cn/data5/city1…
      1201|济南,1202|青岛,1203|淄博,1204|德州,1205|烟台,1206|潍坊,……
    3. 120101|济南,120102|长清,120103|商河,120104|章丘,120105|平阴,….
      访问 m.weather.com.cn/data5/city<城市编号>.xml 得到该城市的县区编号(如访问济南:m.weather.com.cn/data5/city1…
    4. 访问 m.weather.com.cn/data5/city<县区编号>.xml 得到该县区的城市码(如访问长清:m.weather.com.cn/data5/city1…)
      120102|101120102
      首先实现上述功能需使用 Android 的网络访问技术, 故编写工具类
      WebAccessTools 类如下:
      /**
      根据给定的url地址访问网络,得到响应内容(这里为GET方式访问)
      @param url 指定的url地址
      @return web服务器响应的内容,为String类型,当访问失败时,返回为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,得到一个一维为城市编号,二维为城市名的二维数组
      解析的字符串的形式为: 编号|城市名,编号|城市名,.
      @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//由城市码来得到地方码
      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; jfor(int n=0; nif(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 中文件的复制。
  1. 实现可伸缩性列表的的构建与过滤

    1. 实现可伸缩性列表是通过继承 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> values 这样的向量中,再由新建的 FilterResults 封装返回,(具体实现如下):

     

    1. 首先是对关键字进行判断是否为空,如为空则由 values 添加所有省份与城市,其中的 allGroups 和 allChilds 保存的是所有的省份与对应的城市:
      //当过滤条件为空时,返回所有的省份与城市
      if(constraint == null || constraint.length() == 0) {
      for(int i=0; i index = new ArrayList();
      //添加所有与之对应的城市
      for(int j=0; j}
      values.put(i, index);
      }
      }
    2. 如果关键字 constraint 不为空,则以省份为单位进行匹配,省份匹配的添加下面的所在城镇,如果不匹配,则进行步深入匹配城镇,添加符合条件的城镇:
      String filterStr = constraint.toString();
      for(int i=0; i//查找省名是否包含用户输入的字符串
      if(allGroups[i].contains(filterStr)) { ArrayList index = new ArrayList();
      //添加所有与之对应的城市
      for(int j=0; j}
      values.put(i, index);
      } else {
      ArrayList index = new ArrayList();
      //如果省份名没有,则查找它下面的城市名是否包含
      for(int j=0; jif(allChilds[i][j].contains(filterStr)) { index.add(j);
      }
      }
      //如果添加进入了城市,说明存在,则它的省份也添加进去
      if(index.size() > 0) { values.put(i, index);
      } else {
      index = null;
      }
      }
      }
    3. 得到过滤的结果后将其用 FilterResource 封装后返回:

FilterResults results = new FilterResults(); results.values = values;

results.count = values.size();

另外的 publishResults(CharSequence constraint,FilterResults results)方法就是根据上面得到的results 对象来得到新的String[] groups 与String[][] Childs 数组, 再调用 BaseExpandableListAdapter 父类的 notifyDataSetChanged()方法来更新列表,从而实现过滤后结果的显示(具体实现如下):

  1. 首先将参数 FiltersResuls 对象转换为 Map> filterResult,然后来判断过滤后的结果长度时否为 0,如果长度为 0 则说明过滤后的结果为空,则调用父类的 notifyDataSetInvalidated()方法来阻止列表的更新:
  2. 如果长度不为 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; iif(filterResult.containsKey(i)) { newGroups[index] = allGroups[i];
    //符合条件的城市
    ArrayList 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}
    } else {
    //收缩伸缩性列表
    for(int i=0; i}
    }
    如上所述则就实现了带有过滤性可伸展性列表适配性的实现,则在使用时在XML 组件配置文件中使用 ExpandableListView 列表,并调用它的 setAdapter()方法来,加载自定义的适配器。而在使用它的过滤功能时则调用自定义适配器的getFilter()得到过滤 Filter 对象,再调用 Filter 对象的 filter(String)方法实现的,在本系统中才用的时触发文本输入框EditText 的TextChangedListener 事件时调用从而实现手动选择预报城市的过滤查询。
  3. GPS 定位功能的实现

安卓APP源码和设计报告——手机天气预报系统

计算机毕设——手机天气预报系统

Android 中调用 GPS 功能,首先要获取 GPS 定位管理器 LocationManager, 获取 LocationManager 后就是获取 LocationProvider,可以通过 Criteria 对象设置过滤条件来获得最符合用户需求的 LocationProvider,得到 LocationProvider 后就可通过调用 LocationMananger 对象的 getLastKnownLocation() 方法来获取Location 地址封装对象,最后由实例化的 Geocoder 将 Location 中的经度和纬度反编译为地址信息集合 List 对象,从而由 List 对象来得到当前用户地址名。在开发过程中通过 Eclipse 中的 ADT 插件的 DDMS 可以为 Android 模拟器指定任意地址,如下:
当在模拟器控制面板中指定经纬度后,则会在模拟器中出现 GPS 的标志:

安卓APP源码和设计报告——手机天气预报系统

计算机毕设——手机天气预报系统

但在实际开发调用 GPS 功能过程时,只能获取经度与纬度,而在使用 Geocoder

安卓APP源码和设计报告——手机天气预报系统

计算机毕设——手机天气预报系统

反编译地址时报错:
使用的调用代码如下:

安卓APP源码和设计报告——手机天气预报系统

计算机毕设——手机天气预报系统

后来通过网上搜索得知在 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.“

安卓APP源码和设计报告——手机天气预报系统

计算机毕设——手机天气预报系统 由此可知,为了实现 GPS 的定位功能还需要实现一个解析 XML 的工具类,在本系统中由工具类 LocationXMParser 完成,它继承至 org.xml.sax.helpers.DefaultHandler 类,用于专门用于解析XML 文件。