admin管理员组

文章数量:1566992

摘要

随着智能手机的快速普及,智能手机操作系统市场风生水起。为了让智能手机用户能够随时随地查询互联网所提供的服务,一种高效的办法就是将应用系统的功能拓展到手机终端上,让手机能够通过移动网以及互联网访问Web网站并处理各种各样的业务。因此,智能手机的应用软件及其需要的服务将有广阔的发展前景。

在如今这个智能手机系统群雄纷争的时候,2008年Google推出了一款名为Android的开源智能手机操作系统,它采用Linux内核,开放手机联盟(OHA)成员可以任意使用和修改SDK包,系统的开源性使其具有良好的拓展性。这款软件包括了操作系统、用户界面和应用程序,即智能手机工作所需要的全部软件。Android的最大特点是其开放性体系架构,不仅具有非常好的开发、调试环境,而且还支持各种可扩展的用户体验,包括丰富的图形组件、多媒体支持功能以及强大的浏览器。因此,对于软件从业人员来说,Android平台具有无限的吸引力。

本文就在分析讨论Android手机软件开发技术原理的基础上,开发出能为用户提供更好的基于Android平台的3G手机气象软件。

关键字:android;气象软件;智能手机平台

Abstract

With the rapid proliferation of smart phones, smart phone operating system market, wind and water. In order for smart phone users to check the Internet anytime, anywhere services provided an efficient way to expand the function of the system will be applied to the mobile phone terminal, the mobile phone to Internet access via mobile networks, and variety of Web sites and process business. Therefore, the smart phone applications and the need for the service will have broad prospects for development.

In today's smart phone system when disputes warlords, 2008, Google introduced a smart phone called the Android open source operating system, which uses Linux kernel, Open Handset Alliance (OHA) members can be free to use and modify the SDK package, the system It has a good open-source nature of the expansion of. The software includes the operating system, user interface and applications that need to work smart phones all the software. Android's biggest feature is its open architecture, not only has a very good development and debugging environment, but also supports a variety of scalable user experience, including rich graphical components, multimedia support, and powerful browser. Therefore, for software professionals is, Android platform has unlimited appeal.

In this paper, the analysis and discussion Android mobile phone software development technology on the basis of the principle, developed to provide users with better platform to the 3G handsets based on Android meteorological software.

Keywordsandroid;weather software;smartphone platform

目 录

摘要         I

Abstract II

第一章   绪论    1

1.1 研究内容... 1

1.2 研究意义... 1

1.3 研究现状和发展趋势... 1

第二章  系统分析    3

2.1 研究目标... 3

2.2 需求分析... 3

2.3 性能分析... 3

第三章 系统开发环境及相关技术    4

3.1 Android开发环境介绍... 4

3.2  Android开发平台搭建... 4

第四章 程序设计    6

4.1  用户界面设计... 6

4.2  数据库设计... 6

4.3  程序模块设计... 7

第五章   程序开发    9

5.1  文件结构与用途... 9

5.2  数据提供者... 10

5.3  后台服务... 16

5.3.1  获取天气数据... 16

5.3.2  读取天气数据实例... 19

5.3.3  定时更新时间。... 19

5.4  用户界面... 19

5.4.1  程序入口类ForecastWidget 19

5.4.2  设置页面ConfigureActivity. 21

5.4.3 详细页面DetailForecastActivity. 22

5.5 动态特效... 23

第六章  系统测试    25

第七章   总结    29

参考文献    30

致  谢       31

第一章      绪论

1 研究内容

    Android平台3G手机气象软件是基于Android手机平台,运用Java语言,从Google上获取天气预报信息,开发出针对使用Android平台的手机天气预报系统。

2 研究意义

    Android平台3G手机气象软件的开发可以进一步扩大气象信息的覆盖面,让广大公众能够在第一时间方便且快捷地获取最新的气象预报(预警)信息,以便提前预防,把气象灾害造成的损失降到最低。同时也可以强化气象公共服务的职能,完善公共服务体系、改善公共服务手段、增加公共服务产品、提高公共服务质量,更好地发挥了气象事业对经济社会发展的现实性作用。

3研究现状和发展趋势

据Gartner最新数据显示,2010年第二、三季度全球智能手机销售继续大幅增长,其中第三季度安卓占全球智能手机市场的25.5%,仅次于塞班,成为第二大智能手机系统。2010第二、三季度与去年同期相比,增度分别是15.4%及22%,增势迅猛。而塞班系统较09年的市场份额,尽管终端销量有大幅度提升,但增势下滑10%,形势非常严峻。其他操作系统的表现则要平稳很多。

安卓是首款开源代码的操作系统,采用Linux内核,开放手机联盟(OHA)成员可以任意使用和修改安卓的SDK软件包。系统的开源性使其具有良好的拓展性,一方面,为众多移动应用开发者提供了良好的系统性平台,有利于移动应用的集合;另一方面,终端厂商可以针对自身的特殊需要“定制化开发”。与此相比,尽管2010年2月,塞班基本完成开源工作,但由于代码臃肿,用户界面设计缺失,塞班的开源并无实际意义,错失发展良机。

据Informa预测,2012年,安卓手机销量将超过诺基亚塞班手机,届时安卓将成为全球最大的智能手机平台。谷歌表示目前安卓手机每日平均激活量为20万部,销售速度加速上涨。

  我们认为,安卓在全球市场的成长壮大,塞班市场份额不断下跌已成不争事实。安卓操作系统发展迅猛主要得益于免费、开源和良好产业运作策略,这三点为安卓在智能手机市场赢得更多的发展机遇。系统的免费让更多的厂商加入,推动了份额的上升;代码的开源让更多的开发者进入应用开发;应用的丰富吸引了更多用户选择,这也是谷歌值得众多厂商学习的地方。此外,谷歌通过OHA联合众多厂商进行系统研发,不断更新版本,将系统应用推广到更多的终端产品中,确保用户体验持续改善。

   

第二章  系统分析

2.1  研究目标

(1)  了解Android应用程序的设计和开发过程;

(2) 使用多种组件进行Android 平台3G手机气象软件的开发。

本软件是基于Eclipse的开发环境,依托Google气象数据建立气象信息数据库,开发出了针对Android平台的手机天气预报系统。

2.2 需求分析

本软件是一个App Widget应用程序,启动程序后可以进行城市、更新频率的设置,可以通过图片和文字显示当前和未来的天气状况,包括温度、湿度、风向和雨雪情况等。这些天气数据是通过后台服务获取的,这个后台服务可以按照一定时间间隔,从Google上获取天气预报信息,并将天气信息保存在数据库中。

从上面的描述中可以基本了解软件的功能需求:

(1) 启动App Widget应用程序;

(2) 设置界面:对要显示天气预报的城市及更新频率进行设置;

(3) 显示界面:通过文字和图片显示当前的天气情况,包括日期、时间、城市、最高温度、最低温度、当前温度等。

(4) 详细界面:通过文字图片动画显示当前的天气情况,包括城市当前天气、温度、风向、风速、湿度;同时显示今后四天的天气情况。

2.3 性能分析

    程序响应速度快、安全性高、信息处理速度快。

第三章 系统开发环境及相关技术

3.1 Android开发环境介绍

    Android的上层应用程序是用Java语言开发,同时还需要基于Dalvik虚拟机,所以,Google公司推荐使用主流的Java继承开发环境Eclipse。只有Eclipse还不够,因为是使用Java语言进行开发,还应该有由SUN公司提供的Java SDK(其中包括JRE:Java Runtime Environment)。此外,Android的应用程序开发和Java开发有较大区别的,所以还需要有Google提供的Android SDK。同时,还需要在Eclipse安装ADT,为Android开发提供开发工具的升级或者变更,是Eclipse下开发工具的升级下载的工具。

    简言之,需要以下软件,才能搭建Android开发环境,从而进行Android应用程序的开发。

(1) Java SDK

(2) Eclipse

(3) Android SDK

(4) ADT

3.2  Android开发平台搭建

Android开发平台搭建步骤:

①首先到http://www.eclipse/downloads/下载Eclipse集成开发环境并且解压,这里推荐下载Java EE 集成版本,可以为平台的搭建省下不少工作。接着再去Java Downloads | Oracle 站点下载SDK后安装,下载http://dl.google/android/android-sdk-windows-1.5_r1.zip,Android SDK1.5后解压。第一步下载工序就结束了。

②双击Eclipse解压后目录中的eclipse.exe然后启动,选择Eclipse菜单中的Help-> Install New Software-> 选项卡上的Available Software,点击右侧的“Add”输入http://dl-ssl.google/android/eclipse/后确定,然后在“Work with”下拉菜单中选择刚才输入的网址。过一会就会出现一个Developer Tools选项,勾上以后点击Next以后Eclipse会自动网上查找Android 开发工具插件,然后找到Android DDMS和Android Development Tools,选中这两个点击Finish,Eclipse就会自动下载并安装Android插件了,最后会提示重启Eclipse。

③重启后选择Eclipse菜单中的Windows-> Preferences 在左侧的Android项目中SDK Location中填入Android SDK解压后的目录,然后点击Apply。

④在WindowsXP的系统变量中的path变量中添加一个值,该值指向解压后的Android SDK目录下的tools文件夹。

第四章 程序设计

4.1  用户界面设计

根据需求分析可以知道,应用程序应包含三个主要的用户界面,这里需要进一步分析每个用户界面中应该包括哪些显示内容。

在“设置页面”中,需要对要显示天气预报的城市及更新频率进行设置。在“显示页面”中,显示当前的天气状况,包括城市名称、温度、湿度、风向、雨雪情况和获取数据时间等信息。在“详细页面”动态显示当前天气情况,还要显示未来四天的天气情况。

根据对用户界面显示内容的分析,绘制出用户界面的草图,如图4-1所示。

图4-1 用户界面草图

4.2  数据库设计

     本软件中有两部分数据需要存储,一个是显示页面的数据,另一个详细页面的数据。因此选择SQLite数据库作为存储数据的方法,建立数据库forcasts.db,并且建立两张表weather_forcastt和weather_widget,分别存储显示页面的数据和详细页面的数据。

表4-1 详细页面的数据库表weather_forcast的结构

属性

数据类型

说明

_id

integer

自动增加的主键

widgetId

integer

组件的ID号

dayOfWeek

text

周几

low

integer

最低温度

hight

integer

最高温度

icon

text

显示天气的图标

condition

text

未来天气情况

表4-2 显示页面的数据库表weather_widget的结构

属性

数据类型

说明

_id

integer

存储Widget组件id

city

text

进行天气信息查询的城市名

updateMilis

text

进行天气信息查询的频率,单位为分/次

isConfigured

text

是否设置

postalCode

text

城市标记

forecastDate

integer

预报日期

condition

text

当前天气情况

tempF

integer

最高温度

tempC

integer

最低温度

humidity

text

当前湿度

icon

text

天气图标

windCondition

text

风向风速情况

lastUpdateTime

integer

最后一次更新时间

4.3  程序模块设计

     从功能需求上分析可以看出,整个应用程序应划分为4个模块,分别是程序启动、用户界面、后台服务和数据库适配器,各模块之间的关系如图4-2所示。

     从模块结构图中不难看出,后台服务是整个应用程序的核心,主要是“数据获取模块”,负责周期性的从Google获取天气信息。后台服务在程序启动时就由AppWidgetProvider启动,也可由用户界面通过startService()进行启动,启动后的后台服务将一直保持运行状态。

     用户界面从数据库中获取天气信息,而没有直接通过网络访问Google的天气数据。之所以这么实际,一方面可以避免因网络通信不畅造成界面失去响应;另一方面,后台服务可以及时更新数据,以保证数据的准确性。

     数据库适配器封装了所有对SQLite数据库操作的方法,用户界面和后台服务会调用它来实现数据库操作。

     在完成用户界面设计、数据库设计和模块设计后,至此程序设计阶段基本完成。

第五章      程序开发

5.1  文件结构与用途

在程序开发阶段,首先确定“Android平台3G手机气象软件”的工程名为ty-weather,据程序模块设计的内容,建立ty-weather工程。Weather工程源代码的结构如图5.1所示。

图5.1 ty-weather工程的源代码文件

为了使源代码文件的结构更加清晰,Weather工程设置了多个命名空间,分别用来保存用户界面、数据库、后台服务和工具实体的源代码文件,源代码文件的名称以及说明如表5.1所示。

表5.1 ty-weather工程的文件用途说明

包 名 称

文 件 名

说  明

com.ty.weather

ConfigureActivity.java

“设置页面”的Activity

com.ty.weather

DetailForecastActivity.java

“详细页面”的Activity

com.ty.weather

ForecastProvider.java

天气预报相关数据提供者,操作数据库

com.ty.weather

ForecastService.java

后台进程内服务

com.ty.weather

ForecastTimeService.java

后台时间服务

com.ty.weather

ForecastWidget.java

实现AppWidget组件,并启动服务

com.ty.weather

WebServiceHelper.java

网络数据获取模块

com.ty.weather.util

ForecastEntity.java

未来天气信息的类

com.ty.weather.util

ForecastUtil.java

天气信息工具类

com.ty.weather.util

WidgetEntity.java

当前天气信息的类

Android的资源文件保存在/res的子目录中。其中、/res/anim目录中保存的是产生动画效果的XML文件,/res/drawable/目录中保存的是图像文件,/res/values目录中保存的是用来自定义字符串和颜色的文件,/res/xml目录中保存的是XML格式的数据文件。所有在程序开发阶段可以被调用的资源都保存在这些目录中,具体每个资源文件的用途可以参考表5.2:

表5.2  资源文件名称与用途

资源目录

文  件

说  明

anim

rotatecurrentweather.xml

渐变透明度动画效果

translatecloudleft.xml

画面转换位置移动动画效果(从左至右)

translatecloudright.xml

画面转换位置移动动画效果(从右至左)

translaterain01.xml

画面转换位置移动动画效果(从上至下)

translaterain02.xml

画面转换位置移动动画效果(从上至下)

translaterain03.xml

画面转换位置移动动画效果(从上至下)

translaterain04.xml

画面转换位置移动动画效果(从上至下)

translaterain05.xml

画面转换位置移动动画效果(从上至下)

drawable

cloudy.png

这个文件夹里是工程里所用的图片,如有表示天气状况、数字等,此处不赘述。

dots.png

number_7_tahoma.png

weather_sunny.png

layout

configure.xml

设置城市、天气更新频率的布局

weather.xml

“显示页面”的布局

detail.xml

“详细显示”页面上边的显示当前天气的布局

detailitems.xml

“详细显示”页面下边的显示未来天气的布局

values

color.xml

保存颜色的XML文件

strings.xml

保存字符串的XML的文件

xml

weatherwidget.xml

AppWidget的属性文件

5.2  数据提供者

    数据提供者ForecastProvider继承ContentProvider,是在应用程序间共享数据的一种接口机制。应用程序在不同的进程中运行,因此,数据和文件在不同的应用程序之间是不能够直接进行访问的。而CotentProvider为程序员提供了较高级的数据共享方法,应用程序可以指定需要共享的数据,而其他应用程序则可以在不知数据来源、路径的情况下,对共享数据进行查询、添加、删除和更新等操作。  

在介绍ForecastProvider的核心代码前,首先了解一下WidgetEntity.java(当前天气信息的类)和ForecastEntity.java(未来天气信息的类)。

(1)  WidgetEntity.java的部分代码:

public class WidgetEntity {

private ArrayList<ForecastEntity> details =

new ArrayList<ForecastEntity>();  //ForecastEntity对象

private Integer id;   //编号

    private Integer updateMilis//更新频率

    private String city//城市

    private String postalCode//城市标记

    private Long forecastDate//预报时间

    private String condition//天气情况

    private Integer tempF//华氏温度

    private Integer tempC//摄氏温度

    private String humidity//湿度

    private String icon//天气图标

    private String windCondition//风力风向

    private Long lastUpdateTime//最后更新时间

    private Integer isConfigured//是否已设置

    ......

}

从代码中可以看出,除ForecastEntity 的对象details, WidgetEntity类中有以上公有静态属性idupdateMilis 、updateMilispostalCodeforecastDateconditiontempFtempChumidityiconwindConditionlastUpdateTimeisConfigured完全对应数据库中weather_widget的表。

  1. ForecastEntity.java的部分代码:

public class ForecastEntity {

    private Integer id//编号

    private String dayOfWeek//星期

    private Integer low//最低温度

    private Integer hight//最高温度

    private String icon//天气图标

    private String condition//天气情况

    private Integer widgetId//组件id

从代码中可以看出,WidgetEntity类中有以上公有静态属性iddayOfWeek、lowhighticonconditionwidgetId完全对应数据库中weather_forcast的表。

     在创建ForecastProvider时,首先需要使用数据库、文件系统或网络实现底层存储功能,这里我们采用数据库建立了数据库设计中的两张表,从而实现了底层存储功能。然后在继承ContentProvider的类中实现基本数据操作的接口函数,包括添加、删除、查找和更新等功能。下面就核心代码进行分析。

  1. 创建数据库:DatabaseHelper类继承SQLiteOpenHelper,SQLiteOpenHelper这个类可以辅助建立、更新和打开数据库。DatabaseHelper在建立数据库时,同时建立了两个数据库表weather_widget和weather_forcast,并对保存配置信息的表进行了初始化。具体代码如下所示:

private static class DatabaseHelper extends SQLiteOpenHelper {

       //数据库名称

       private static final String DATABASE_NAME = "forecasts.db";

       //数据库版本

       private static final int DATABASE_VERSION = 2;

       //构造方法,重载父类构造

       public DatabaseHelper(Context context) {

           super(context, DATABASE_NAME, null, DATABASE_VERSION);

       }

       public void onCreate(SQLiteDatabase db) {

       //创建weather_widget

           public void onCreate(SQLiteDatabase db) {

       //创建weather_widget

           db.execSQL("CREATE TABLE " + TABLE_WIDGET + " ("

                  ......);

           //创建weather_forecast

           db.execSQL("CREATE TABLE " + TABLE_FORECAST + " ("

                  ......);

           }

       }

       //重写onUpgrade()方法

       @Override

       public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

           ......

           }

       }

    }

  1. URI设置:ContentProvider可以提供多个数据集,调用者使用URI(通用资源标识符)对不同的数据集的数据进行操作。URI用来地位任何远程或本地的可用资源。ContentProvider使用的URI语法结构content://<authority>/<data_path>/<id>。因此,我们就需要先来对URI进行设置。代码如下:

public static class WeatherWidgets implements BaseColumns {

       //继承BaseColumns可以提供自动生成的ID

       //声明CONTENT_URI

       public static final Uri CONTENT_URI = Uri.parse("content://"

              + AUTHORITY + "/widgets");        

       public static final String FORECAST_END = "forecasts";

       //指操作多条数据

           public static final String CONTENT_TYPE = "vnd.android.cursor.dir/awidget";

       //指操作单条数据

           public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/widget";

    }

  1. 查询功能:首先我们先了解一下SQLiteQueryBuilder的query()方法。query(SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit),参数说明如表5.3:

参  数

说  明

SQLiteDatabase db

要查询的数据库实例

String[] projectionIn

是一个字符串数组,里边的每一项代表了需要返回的列名

String selection

相当于SQL语句中的where部分

String[] selectionArgs

是一个字符串数组,里边的每一项依次替代在第三个参数中出现的问号(?)

String groupBy

相当于SQL语句当中的groupby部分

String having

相当于SQL语句当中的having部分

String sortOrder

描述是怎么进行排序

String limit

相当于SQL当中的limit部分,控制返回的数据的个数

查询功能的核心代码如下:

public Cursor query(Uri uri, String[] projection, String selection,

           String[] selectionArgs, String sortOrder) {

       //getReadableDatabase()这个函数会数据库是否存在、版本号和是否可读等情况,

       //决定在返回数据库对象前,是否需要数据库。返回一个可读的数据库对象。

       SQLiteDatabase db = dbHelper.getReadableDatabase();

       //声明了一个SQLiteQueryBuilder对象,该对象利用传入的参数生成一个完整的

       //sqlite查询语句,还可以完成查询工作并返回结果集(Cursor)。

       SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

       String limit = null;

       //通过一个switch语句,完成了通过uri种类的辨别来生成不通的SQLiteQueryBuilder对象的工作。

       switch (uriMatcher.match(uri)) {

       case WIDGETS: {

            qb.setTables(TABLE_WIDGET);

            break;

        }

        case WIDGETS_ID: {

            String appWidgetId = uri.getPathSegments().get(1);

            qb.setTables(TABLE_WIDGET);

            qb.appendWhere(BaseColumns._ID + "=" + appWidgetId);

            break;

        }

        case WIDGETS_FORECASTS: {

            // Pick all the forecasts for given widget, sorted by date and

            // importance

        //获得appWidgetId

            String appWidgetId = uri.getPathSegments().get(1);

            //为该SQLiteQueryBuilder对象设置它查询工作将针对的表

            qb.setTables(TABLE_FORECAST);

            //qbwhere条件

            qb.appendWhere(ForecastEntity.WIDGET_ID + "=" + appWidgetId);

            //按照BaseColumns._ID进行排序

            sortOrder = BaseColumns._ID + " ASC";

            break;

        }case FORECASTS: {

           qb.setTables(TABLE_FORECAST);

           break;

       }

       case FORECASTS_ID: {

           String forecastId = uri.getPathSegments().get(1);

           qb.setTables(TABLE_FORECAST);

           qb.appendWhere(BaseColumns._ID + "=" + forecastId);

           break;

           }

       }

    return qb.query(db, projection, selection, selectionArgs, null, null,sortOrder, limit);

        }

  1. 添加、删除、更新功能由于核心代码类似,此处不一一赘述。

添加功能:

public Uri insert(Uri uri, ContentValues values) {}

删除功能:

public int delete(Uri uri, String selection, String[] selectionArgs) {}

更新功能:

public int update(Uri uri, ContentValues values, String selection,

                         String[] selectionArgs) {}

  1. 类型匹配:增删改查通过switch判断,进行不同的操作,因而还需要对关键字进行类型匹配。核心代码如下:

/*getType()函数用来返回指定URIMIME数据类型

     * CONTENT_TYPE表示URI是多条数据

     * CONTENT_ITEM_TYPE表示是单条数据

     */

    @Override

    public String getType(Uri uri) {

       // TODO Auto-generated method stub

       switch (uriMatcher.match(uri)) {

        case WIDGETS:

            return WeatherWidgets.CONTENT_TYPE;

        case WIDGETS_ID:

            return WeatherWidgets.CONTENT_ITEM_TYPE;

        case WIDGETS_FORECASTS:

            return WeatherDetails.CONTENT_TYPE;

       case FORECASTS:

           return WeatherDetails.CONTENT_TYPE;

       case FORECASTS_ID:

           return WeatherDetails.CONTENT_ITEM_TYPE;

       }

       throw new IllegalStateException();

    }

//构造无匹配方式的uriMatcher

    private static final UriMatcher uriMatcher = new UriMatcher(

           UriMatcher.NO_MATCH);

    private static final int WIDGETS = 101;

    private static final int WIDGETS_ID = 102;

    private static final int WIDGETS_FORECASTS = 103;

    private static final int FORECASTS = 201;

    private static final int FORECASTS_ID = 202;

    //声明了uriMatcher的匹配方式和返回代码

    static {

       uriMatcher.addURI(AUTHORITY, "widgets", WIDGETS);

       uriMatcher.addURI(AUTHORITY, "widgets/#", WIDGETS_ID);

       uriMatcher.addURI(AUTHORITY, "widgets/#/forecasts", WIDGETS_FORECASTS);

       uriMatcher.addURI(AUTHORITY, "forecasts", FORECASTS);

       uriMatcher.addURI(AUTHORITY, "forecasts/#", FORECASTS_ID);

    }

}

5.3  后台服务

后台服务是ty-weather工程的核心模块,在用户启动后持续在后台运行,直到用户停止服务。后台服务主要有三个功能,一是周期性的获取Google的天气数据并存储到SQLite,二是从SQLite读取出要显示的数据,三是定时更新“显示页面”的时间。

5.3.1  获取天气数据

天气数据的获取天气数据分为以下三个步骤:

  1. 从Google提供的Web Service中获取的天气数据,数据的获取地址是: http://www.google/ig/api?weather=%s&hl=zh-cn。核心代码如下:

public static WidgetEntity queryWebservice(String postalCode)

           throws ForecastParseException {

       //编码出错

       if (postalCode == null) {

           throw new ForecastParseException("can not covert to entity");

       }      Reader responseReader;

       WidgetEntity widgetEntity = null;

       //通过HttpClient创建Http连接

       HttpClient client = new DefaultHttpClient();

       //创建Http Get请求

       HttpGet request = new HttpGet(String.format(WEBSERVICE_URL, postalCode));

       try {

           Log.d(TAG, "get google's weather infomation");

           //发出请求

           HttpResponse response = client.execute(request);

           StatusLine status = response.getStatusLine();

           Log.d(TAG, "Request returned status " + status);

           //取出回复信息

           HttpEntity entity = response.getEntity();

           responseReader = new InputStreamReader(entity.getContent(), "GB2312");

       } catch (IOException e) {

           throw new ForecastParseException("Problem calling forecast API", e);

       }

       if (responseReader != null) {

           widgetEntity = parseResponse(responseReader);

       }

       return widgetEntity;

    }

  1. 调用轻量级XML解析器XmlPullParser对从网络上获取的字节流数据进行解析,并且将解析结果保存在WidgetEntity对象中。核心代码如下:

//返回类型为WidgetEntity

private static WidgetEntity parseResponse(Reader responseReader)

           throws ForecastParseException {

......

       try {

           //使用工厂类XmlPullParserFactory来创建解析器XmlPullParser

           XmlPullParserFactory factory = XmlPullParserFactory.newInstance();

           XmlPullParser xpp = factory.newPullParser();

           String tagName = null;

           xpp.setInput(responseReader);

           int eventType = xpp.getEventType();

           while (eventType != XmlPullParser.END_DOCUMENT) {

              if (eventType == XmlPullParser.START_TAG) {

                  tagName = xpp.getName();

                  //根据不同的标签做不同的解析

                  if (PROBLEM_CAUSE.equals(tagName)) {

                     throw new ForecastParseException(

                            "the city is non correct!");

                  } else if (FORECAST_INFORMATION.equals(tagName)) {

                     dealWithInfomation(tagName, widgetEntity, xpp);

                  } else if (CURRENT_CONDITIONS.equals(tagName)) {

                     dealWithCurrentConditions(tagName, widgetEntity, xpp);

                  } else if (FORECAST_CONDITIONS.equals(tagName)) {

                     dealWithForecastConditions(tagName, widgetEntity, xpp);

                  }

              }

              eventType = xpp.next();

           }

       } catch (IOException e) {

           ......

       }

       return widgetEntity;

    }

  1. 将解析好的数据存储到SQLite数据库中。其过程是:使用ContentResolver对象,通过URI间接调用ContentProvider,使用ContentResolver对象与ContentProvider进行交互,而ContentResolver则通过URI确定需要访问的ContentProvider的数据集。核心代码如下,调用关系如图5.1所示。

图5.1  ContentProvider调用关系

ContentResolver resolver = context.getContentResolver();

resolver.delete(forecastUri, null, null);

ContentValues values = new ContentValues();

for (ForecastEntity forecast : widgetEntity.getDetails()) {

           values.clear();

       values.put(ForecastEntity.DAYOFWEEK, forecast.getDayOfWeek());

       ......

       resolver.insert(forecastUri, values);

       }

注意:

for (ForecastEntity forecast : widgetEntity.getDetails())相当于foreach语句,在widgetEntity.getDetails()集合里打印出所有类型为ForecastEntity的forecast变量。

5.3.2  读取天气数据实例

        通过WebServiceHelper.getWidgetEntity()方法操作数据库取出所需要的数据,这一部分与上一节的“将解析好的数据存储到SQLite数据库中”的关键点相同,为节约篇幅,此处不赘述。

5.3.3  定时更新时间。

 AppWidget一启动就会启动ForcastTimeService这个后台服务,此服务设置了每隔20秒刷新一次时间,“显示页面”通过这个后台服务获取系统时间从而显示。核心代码如下:

AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);

alarmManager.set(AlarmManager.RTC_WAKEUP, now + updateMilis, pendingIntent);

至此,后台服务介绍完毕,最后还需要在AndroidManifest.xml文件中注册后台服务。

<service android:name="ForecastService"></service>

<service android:name="ForecastTimeService"></service>

 5.4  用户界面

在用户界面设计上,采用了AppWidget框架结构,提供直观的交互操作。三个用户界面风格简约、操作简便,用户体验将非常好。

5.4.1  程序入口类ForecastWidget

ForecastWidget在设计上采用了AppWidget框架结构,AppWidget就是HomeScreen上显示的小部件,通过在HomeScreen空白处长按,在弹出的对话框中选择Widget部件来进行创建。此外,长按部件后并拖动到垃圾箱里进行删除。创建AppWidget需要以下四个步骤:

  1. 定义Widget布局文件,此文件是res/layout/weather.xml,采用AbsoluteLayout方式进行布局需要注意的是在这个文件中所使用的组件必须是RemoteViews所支持的。
  2. 定义Widget的基本属性文件,此文件是/res/xml/weatherwidget.xml。代码如下:

<?xml version="1.0" encoding="utf-8"?>

<appwidget-provider

  xmlns:android=http://schemas.android/apk/res/android android:initialLayout="@layout/weather"  //Widget的布局文件

  //在启动前首先要启动ConfigureActivity进行设置

android:configure="com.ty.weather.ConfigureActivity"   android:minWidth="292dip"   //定义Widget组件的宽度

android:minHeight="144dip"  //定义Widget组件的高度

 android:updatePeriodMillis="0">  //更新的时间周期

</appwidget-provider>

  1. 创建ForecastWidget.java类,此类继承自AppWidgetProvider,主要的功能有:获取需要更新的桌面小控件;启动获取天气预报信息的服务;启动时间信息的服务;更新桌面小控件显示内容;更新时间信息。核心代码如下:

public class ForecastWidget extends AppWidgetProvider {

    @Override

public void onUpdate(Context context, AppWidgetManager appWidgetManager,int[] appWidgetIds) {

    super.onUpdate(context, appWidgetManager, appWidgetIds);

       // 获取需要更新的桌面小控件

    ForecastService.addWidgetIDs(appWidgetIds);

       // 启动获取天气预报信息的服务

context.startService(new Intent(context, ForecastService.class));

       // 启动时间信息的服务

context.startService(new Intent(context,

ForecastTimeService.class));

    }

       //更新桌面小空间显示内容

public static RemoteViews updateViews(Context context, Uri uri) {

       }

       //更新时间信息

   public static RemoteViews updateTime(Context context) {

       }

  1.  AppWidgetProvider对应一个receiver属性,需要更新AndroidManifest.xml。代码如下:

<!--receiver字段定义的是AppWidgetProvider—->

<receiver android:icon="@drawable/weather" android:label="@string/app_name"

           android:name="ForecastWidget">

<!--intent-filter说明appWidget可以接受下面的广播事件—->

    <intent-filter>

        <action android:name="android.appwidget.action.APPWIDGET_UPDATE">

</action>

    </intent-filter>

    <!--meta-data指明属性文件—->

    <meta-data android:name="android.appwidget.provider"

               android:resource="@xml/weatherwidget"></meta-data>

       </receiver>

5.4.2  设置页面ConfigureActivity

ConfigureActivity继承Activity并且实现了View.OnClickListener接口,AppWidget第一次启动之前会先运行ConfigureActivity,会出现设置页面用户进行设置,点击保存按钮就会将设置好的参数会存储到SQLite中核心代码如下:

//OnClick()事件

public void onClick(View v) {

       switch (v.getId()) {

       case R.id.btnSave: {

           city = editCity.getText().toString();

           updatetime = Integer.parseInt(editUpdatetime.getText().toString());

          

           ContentValues values = new ContentValues();

           values.put(BaseColumns._ID, widgetId);

           values.put(WidgetEntity.POSTALCODE, city);

           values.put(WidgetEntity.UPDATE_MILIS, updatetime);

           values.put(WidgetEntity.LAST_UPDATE_TIME, -1);

           values.put(WidgetEntity.IS_CONFIGURED, 1);

          

           ContentResolver resolver = getContentResolver();

           // 通过ContentResolver对象存储数据

           resolver.insert(WeatherWidgets.CONTENT_URI, values);

           ForecastService.addWidgetIDs(new int[]{widgetId});

           // 启动获取天气预报信息的后台服务

           startService(new Intent(this, ForecastService.class));

setConfigureResult(Activity.RESULT_OK);

            finish();

           break;

       }

      }

ConfigureActivity的布局文件是res/layout/configure.xml,采用了LinearLayout的布局方式。代码如下:

<LinearLayout xmlns:android="http://schemas.android/apk/res/android"

android:layout_height="fill_parent" android:layout_width="fill_parent" android:orientation="vertical">

<TextView android:id="@+id/TextView01" android:layout_height="wrap_content"

        android:text="@string/labelCity" android:layout_width="fill_parent"></TextView>

<!—编辑框“城市”—->

<EditText android:layout_height="wrap_content" android:text="Shenzhen" android:editable="true" android:layout_width="fill_parent" android:id="@+id/editCity"></EditText>

     <TextView android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/lableUpdateTime" android:text="@string/lableUpdateTime"></TextView>

<!—编辑框“更新频率”—->

<EditText android:layout_height="wrap_content" android:id="@+id/editUpdatetime" android:text="2" android:layout_width="fill_parent"></EditText>

<!—保存按钮—->

<Button android:layout_height="wrap_content"

android:id="@+id/btnSave"

android:text="@string/btnSave" android:layout_width="fill_parent"></Button>

</LinearLayout>

5.4.3 详细页面DetailForecastActivity

DetailForecastActivity主要用来显示详细的天气信息,包括当天和未来四天的天气信息。为了能够以列表的形势显示多行数据,并制定每行数据的布局,使用了ListActivity。

       ListActivity可以不通过setContentView( )设置布局,也不必重载onCreate( )方法,而直接将显示列表加载到ListActivity,增加了使用的便利性。在此工程中,仍然使用setContentView( )设置布局,这样做的好处是可以在界面中设置更为复杂的显示元素。核心代码如下:

//继承ListActivity

public class DetailForecastActivity extends ListActivity {

    private ListAdapter listAdapter;

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       //设置布局文件detail.xml

       setContentView(R.layout.detail);

       ......

       //获取游标,访问数据库

       Cursor forecastCursor = managedQuery(forecastUri,

              ForecastEntity.forecastProjection, null, null, null);

       listAdapter = new ForecastAdapter(this, forecastCursor);

}

列表适配器代码:

private class ForecastAdapter extends ResourceCursorAdapter {

       public ForecastAdapter(Context context, Cursor c) {

           //设置布局文件detailitems.xml

           super(context, R.layout.detailitems, c);

       }

       //绑定View

       @Override

       public void bindView(View view, Context context, Cursor c) {

ImageView icon = (ImageView) view.findViewById(R.id.dDetailImage);

           TextView day = (TextView) view.findViewById(R.id.ddDayText);

           TextView condition = (TextView) view.findViewById(R.id.ddConditionText);

           TextView temp = (TextView)

view.findViewById(R.id.ddTempCText);

           //ForecastUtil.getDetailForecastIcon()判断使用哪个图标

icon.setImageResource(ForecastUtil.getDetailForecastIcon(c.getString(4)));

           day.setText(c.getString(1));

           condition.setText(c.getString(5));

           temp.setText(c.getInt(2) + "¡ã/" + c.getInt(3) + "¡ã");

       }

    }

在上述代码中涉及布局文件detail.xml和detailitem.xml,为节约篇幅,此处不赘述。

5.5 动态特效

动态特效是指如果天气为多云或雨天时,就会在detail.xml布局中出现漂浮的云或下落的雨滴;如果天气为晴时,就会在detail.xml布局中出现透明度逐渐变化的太阳图标等。通过DetailForecastActivity.updateAnimtation( )方法判断具体显示哪个动画。动画效果是通过XML文件来实现的。按照XML文档的结构来介绍Animtation,其有以下四种类型:

由于篇幅限制,选实现“下落雨滴”的translaterain03.xml进行分析。代码如下所示:

<!—下落雨滴”动态效果—->

<?xml version="1.0" encoding="utf-8"?>

<set xmlns:android="http://schemas.android/apk/res/android">

<!—Y不变,X-100420,重复无限次—->

    <translate android:repeatCount="-1"  android:fromXDelta="-100" android:toXDelta="420"

       android:fromYDelta="50" android:toYDelta="50" android:duration="15000" />

</set>

第六章  系统测试

至此已经完成了ty-weather工程的代码编写工作,在这一章节对ty-weather工程进行测试,步骤及结果如下:

  1. 启动Android虚拟机

在工程名ty-weather上点右键,然后选择“Run As”,然后选择“Android Application”。 Android虚拟机启动较慢。如下图所示:

  1. 启动App Widget应用程序

长按屏幕空白处,选择“Widget”,然后选择“天气预报”。 如下图所示:

  

(2)设置界面:对要显示天气预报的城市及更新频率进行设置。如下图所示:

(3)设置好后就进入显示页面,显示页面通过文字和图片显示当前的天气情况,包括日期、时间、城市、最高温度、最低温度、当前温度等。如下图所示:

(4)点击显示页面就会进入详细界面,详细页面通过文字图片动画显示当前的天气情况,包括城市当前天气、温度、风向、风速、湿度;同时显示今后四天的天气情况。如下图所示:

参考文献

[1] 靳岩,姚尚朗. Google Android 开发入门与实战.人民邮电出版社

[2] 景保玉.2010中国移动应用开发现状与趋势大调查,20110112

[3] Michael J.Young. 轻松搞定XML 林嘉胜译 20010108

[4](英)Herbert Schildt Java参考大全(J2SE 5 Edition),清华大学出版社

[5](美)鲍格斯坦BergstenH.)著,林琪朱涛江 .JSP设计(第三版)/ OReilly Java系列[M].北京:中国电力出版社,200412月.

[6] 王向辉,张国印,沈洁.Android应用

本文标签: 气象精品平台软件android