android手机计步软件的设计与实现

发布时间:2014-05-28 16:58:07   来源:文档文库   
字号:

随着智能手机市场的不断扩大,智能手机应用开发己经展现出其强大的市场价值。Android最具创新的特点是其开放性体系架构,这使得Android平台具体无限的吸引力。近来越来越多的人对自身健康问题感到关注,运动健身类软件也逐渐成为了移动互联网业务的一个主要应用方向。

本文以运动健身作为研究方向,设计并实现一款基于Android智能手机的计步应用软件。该应用软件通过手机GPS功能来获取用户实时的经纬度信息,借助地球球面两点间的距离公式计算用户的运动距离,从而测得用户所走的步数、速度以及消耗的热量。此外,该软件包括使用SQLite数据库查询用户的历史运动信息以及设置运动者的身高、体重和运动目标等参数的功能。

本系统界面友好、操作便捷,具有良好的可扩展性和可维护性;系统经过测试,可以稳定运行,对于日常走路上班或者锻炼的人来说非常合适,方便人们及时掌握自己锻炼的情况,达到高效锻炼的效果。

关键词Android GPS 计步器; SQLite 运动健身



ABSTRACT

With the continuous expansion of the smart Phone market,the smartPhone applications development has demonstrated its strong market value.Android's most innovative feature is its open architecture, which makes the Android platform unlimited appeal.Recently, more and more people are concerned about their own health problems, sports and fitness class software is becoming a major application direction of the mobile Internet business.Therefore, the context make use of the sports and fitness as its research direction to design and implement the pedometer application software which based on Android-based smartphone.

The core functionality is that through the GPS function of mobile phones ,it can get the latitude and longitude information in real time about the user, use the distance between two points of the Earth's spherical formula to calculate the user's movement distance, which used to measure the number of steps to go, speed and calories burned. Besides,this software’s function includs that using the SQLite database to query the history information about user and set the height,weight and target of step sports about the user.

The system’s interface is quite friendly and easy to handle the system,.it has good scalability and maintainability; the system has been tested and operated very well, the software is very suitable for the people who walks to work daily or likes exercising themselves, it is convenient for people to know their exercise information at any time and in any place ,so that we can achieve our purpose to hava a high efficiency exercise.

Keywords Android; GPS; pedometer; SQLite; fitness

绪论

研究背景

自上世纪80年代第一部手机诞生以来,随着计算机技术的进步,手机也经历了从模拟器到GSM2.5G再到今天的3G手机如此一个发展历程[1]。特别是进入21世纪后手机的硬件性能得到很大的提高,当前主流手机普遍配有主频500MHZ的微处理器和500MB容量的存储器。与之同步发展的还有支持手机运行的手机操作系统,从当初的单片机系统到后来的专用嵌入式系统,到后来开始为智能手机设计开发出可以更高效管理手机软硬件资源的操作系统,其中有不少属于开源项目的手机操作系统。目前全球的智能手机操作系统主要以Google公司开发的Android系统、苹果公司开发的iPhone系统、诺基亚公司开发的Symbian系统、微软公司开发的WindowsMobile系统和Linux系统为代表。

Android平台是一个面向大众的系统,分布在低端,中端和高端市场中,在各个阶段的是市场中都有很受欢迎的机型,并且Android是开源的,开发者随时都可以查看系统源代码,并且使用时下最流行的java作为其主要的开发语言。由于这些特点,使得Android成为现在市场占有量最大的软件平台。近来越来越多的人对自身健康问题感到关注,运动健身类软件也逐渐成为了移动互联网业务的一个主要应用方向。因此,本文以运动健身作为研究方向,设计并实现一款基于Android智能手机的计步应用软件。

研究目的和意义

随着移动互联网的发展和社会文明程度的不断提高,人们的生活观念也在不断转变。运动休闲作为一种新时代的休闲理念,己逐渐成为现代人生活的一种时尚,显示出旺盛的社会需求,它所强调的正是在运动中放松身心,在休闲中锻炼体质,随时随地随心,而步行无疑是最好的、简单实用的方法。对于步行这种简单易行的运动,要是能有一个优秀的搭档与步行构成完美组合,那么我们的步行运行便会立竿见影,显现在健康活动中的重要作用,这个优秀的搭档便是计步器。

  本文针对现在市面上现有的计步器产品进行了广泛的调查。目前为止,此类产品可分为两大类:一类是电子计步器,是独立的硬件产品,与火柴盒体积相仿,内部设有加速度传感器,外部设有显示屏和操作按键,价格在几百元;另一类是运行于智能手机平台上的运动类应用软件,使用手机自带的硬件模块,包括GPS和加速度传感器,使用手机屏幕作为统计结果输出,手机按键或触屏作为操作输入。本文从两类中分别挑选一个有代表性的产品,西铁城TW700电子计步器和SPortyPal运动软件进行对比,结果显示,运动类应用软件具有以下几个明显的优势:1.不需要另外携带,减少了用户的携带成本;2.价格相对便宜,其中很大部分为免费安装使用;3.智能手机功能强大,且易于应用软件的扩展。另外,Android操作系统凭借着自己开放的平台允许任何移动终端厂商加入到Android联盟中,从而使得越来越多的用户倾向于选择操作系统为Android的手机品牌。选择设计基于Android平台的计步器软件,毫无疑问能覆盖更多的用户,让用户的步行运动显现效果。

研究内容

本文围绕android手机计步软件的设计与实现展开研究,设计了基于GPS的研究实现方案。通过获取GPS数据来对用户的运动情况进行统计。本文的工作主要集中在以下几个方面:

1 学习AndroidGPS相关知识和技术。本文将通过Android手机GPS功能来获取用户实时的经纬度信息,借助地球球面两点间的距离公式计算用户的运动距离,从而测得用户所走的步数、速度以及消耗的热量。

2、设计实现辅助功能:查询用户的运动信息,通过柱状图来直观的显示用户以往的运动情况,激励用户挑战自我,增强体质;设置软件的配置参数:身高、体重、运动目标,以便更准确的实现计步功能,为用户所青睐。

3、根据以上功能设计来实现用户交互界面,让计步器软件不仅是逻辑功能,在视觉和交互上能够提供让用户满意的使用体验。

本文结构

本论文一共有6章,每章内容介绍如下:

1 绪论。介绍Android手机计步软件研究的背景、目的、意义以及内容。

2 相关技术综述。介绍Android平台开发所设计到的开发环境和实现本软件开发的GPS技术。

3 需求分析。主要介绍系统的需求分析。

4 系统设计。介绍本系统的设计。

5 系统实现。介绍本系统的实现。

6 结束语。对自己工作的总结,指出工作的不足,并且对系统的一些后续的发展提供一些建议。

相关技术综述

Android

Android简介

Android一词的本义指“机器人”,同时也是Google2007115日宣布的基于Linux平台的开源手机操作系统的名称,该平台由操作系统、中间件、用户界面和应用软件组成,号称是首个为移动终端打造的真正开放和完整的移动软件。

Android是基于Linux内核的软件平台和操作系统,早期由Google开发(在华注册商标名为“安致”),后由开放手机联盟(Open Handset Alliance)开发。它采用了软件堆层(software stack,又名以软件叠层)的架构,主要分为三部分。低层以Linux内核工作为基础,只提供基本功能;其他的应用软件则由各公司自行开发,以Java作为编写程序的一部分。另外,为了推广此技术,Google和其它几十个手机公司建立了开放手机联盟。Android在未公开之前常被传闻为Google电话或gPhone。大多传闻认为Google开发的是自己的手机电话产品,而不是一套软件平台。到了20101月,Google开始发表自家品牌手机电话的Nexus One,目前最新SDK版本为Android 4.2.2对于一个Android应用程序来说,是由ActivityIntentReceiverServiceContent Provider四部分组成。在应用程序中使用时,需要在配置文件AndroidManifest.xml中进行配置。

Android特性

Android系统有如下的几大特性[2]

1 应用程序框架 支持组件的重用与替换

  2 Dalvik虚拟机 专门为移动设备做了优化

  3 内部集成浏览器 该浏览器基于开源的WebKit引擎

  4 优化的图形库 包括2D3D图形库,3D图形库基于OpenGL ES

  5 SQLite 用作结构化的数据存储

  6 多媒体支持 包括常见的音频、视频和静态印象文件格式

  7 GSM电话(依赖于硬件)

  8 蓝牙Bluetooth, EDGE, 3G, and WiFi (依赖于硬件)

  9 照相机,GPS,指南针,和加速度计 (依赖于硬件)

  10丰富的开发环境 包括设备模拟器,调试工具,内存及性能分析图表,和Eclipse集成开发环境插件。

Android 基本框架

21 Android 体系结构

Android 的软件层次结构包括一个操作系统,中间件,应用程序。其软件层次结构自上而下可分为:

1)应用程序:以java语言编写,设计用户界面交互设计。Android本身提供了桌面,联系人,电话,浏览器等很多核心的应用。

2)应用程序框架:为应用程序提供系统的API。通过Android应用程序框架,开发人员可重用各种组件和服务[3]。应用程序的框架组成部分:

UI组件:包括列表,文本框,按钮等UI组件,是用户可视的部分。

Content Providers:提供了一种应用程序可实现数据的访问和共享的机制。

Notification Manager:能让应用程序将自己的警告信息显示在状态栏上。

Activity Manager:管理应用程序的生命周期,并提供应用程序页面退出的机制。

3)各种库和Android的运行时环境:Android包含了一些C/C++库,这些库能通过JNI技术供系统的各种组件使用。下面是部分核心库:

界面管理(Surface Manager):管理访问显示子系统[4]和无缝组合多个应用程序的二维和三维图形层。

媒体库(Media Framework):包括多种常用的音频,视频格式回放和录制。同时支持静态图像文件,支持MPEG4MP3AAC,JPG,PNG,H.264ARM等多种编码格式。

SQLite:一个通用型很强的轻型关系型数据库引擎。

OpenGL|ES:提供了对3D的支持。

FreeType:位图和矢量字体渲染。

WebKit:一个最新的web浏览器引擎,支持android浏览器和一个可嵌入的web视图。

SGL:底层的2D图形引擎。

SSL:位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。

Android运行环境主要指的虚拟机技术-Dalvik,android包括了一个核心库的集合,该核心库提供了java编程语言核心库的大多数功能。每一个android应用程序是Dalvik虚拟机中的实例,运行在它们自己的进程中。

4Linux内核:Android使用Linux2.6作为操作系统,基于Linux2.6的内核提供了一系列的系统服务,如安全性,进程管理,内存管理,网络协议栈和驱动模型。Linux内核也同时作为硬件和软件栈之间的抽象层,这一层隐藏了具体的硬件特性为上一层提供统一的标准服务。Android对操作系统的使用还包括驱动抽象,主要驱动有:显示驱动,照相机驱动,wifi驱动,音频驱动。

Android系统的四大组件

Android系统中,为我们提供了四大基本组件,每个组件是一个视图,但是有些是可见的,有些是不可见的视图,像ActivityContentProvider是可见的视图,ServiceBroadcastReceiver是不可见的,它们只在系统的后台运行。下面分别介绍四大组件[5]

1Activity 简介

Android系统中Activity提供可视化的用户界面,一个Android应用通常由多个Activity组成。多个Activity组成了Activity栈(Stack),当前活动的Activity处于栈顶。Activity有自己的生命周期,由Android系统来控制。

2Service 简介

顾名思义Service就是运行在后台的一种服务程序,一般很少和用户交互,因此没有可视化界面定义一个Service类比较简单,只要继承Service类,实现其生命周期中的方法就可以了。一个定义好的Service必须在AndroidManifest.xml配置文件注册,通过元素声明才能使用.Service有自己的生命周期,我们可以调用startService()启动一个Service或者bindService()方法来绑定一个存在的Service

3Broadcast Receiver 简介

Broadcast Receiver 顾名思义广播接收器,它和事件处理机制类似,只不过事件处理机制是程序组件级别的(例如,某个按钮的单击事件),而广播事件处理机制是系统级别的。到目前为止我们可以使用Intent来启动一个程序组件,我们还可以通过使用sendBroadcast()方法来发起一个系统级别的事件广播来传递消息。我们可以在你的应用程序中实现Broadcast Receiver来监听和响应这些广播的Intent

4ContentProvider简介

Content Provider 用来保存和检索数据,并且使应用程序之间相互访问数据成为可能。它是跨应用程序共享数据的唯一方法。

Android 为常用的数据类型(如:音视频、图片和联系方式等)提供了大量的Content Provider。它们被定义在android.provider包下面。通过这样定义好的ContentProvider 我们可以方便的进行数据操作。当然我们必须拥有适当的权限。我们也可以自己来定ContentProvider共享我们的数据,方便用户的访问。

开发环境介绍

Eclipse简介

Eclipse是一个开放源代码的、基于Java的可扩展开发平台[6]。最初是由IBM公司开发的替代商业软件Visual Age for Java的下一代IDE开发环境。200111月贡献给开源社区,现在它是由非营利软件供应商联盟Eclipse基金会管理的。

Eclipse是著名的跨平台的自由集成开发环境(IDE)。最初用java语言开发,但是目前也可以通过插件使其作为其他计算机语言的开发工具。

Eclipse是一个开放源代码的软件开发项目,专注于为高度集成的工具开发提供一个全功能、具有商业品质的工业平台,主要有Eclipse项目、Eclipse工具项目和Eclipse技术项目三个项目,具体包括Eclipse PlatformJDTCDTPDE四个部分组成。JDT支持java开发、CDT支持C开发、PDE用来支持插件开发、Eclipse Platform就是一个开放的可扩展的IDE,提供一个通用的开发平台。

Android工程是在Eclipse工具上编写的,进行android工程的编写,首先需要在Eclipse上安装ADT插件,然后指定SDK所在的路径,便可以创建android虚拟机了。虚拟机模拟的是android智能手机的界面,通过操作虚拟机可以体验到android智能手机的部分应用。同时虚拟机是作为开发的android应用运行的场所,它提供基本的人机交互功能。

ADT

目前Android开发所用的开发工具是Eclipse,在Eclipse编译IDE环境中,安装ADT,为Android开发提供开发工具的升级或者变更,简单理解为在Eclipse下开发工具的升级下载工具。

Android SDK

SDK:(software development kit软件开发工具包。被软件开发工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。因此,Android SDK 指的既是Android专属的软件开发工具包。

SQLite数据库

SQLite是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 TclC#PHPJava等,还有ODBC接口,同样比起MysqlPostgreSQL这两款开源世界著名的数据库管理系统来讲,它的处理速度比他们都快。SQLite第一个Alpha版本诞生于20005, 最新版本为3.7.9

GPS相关理论技术知识

GPS定位原理

GPS,中文全称为全球定位系统,21颗使用中的卫星和3颗备用卫星组成,在2万高空以12小时为周期围绕地球旋转。GPS利用到达时间测距原理(TOA)来确定用户的位置。这种原理需要测量信号从位置己知的发射源(例如无线电信标或卫星)发出至到达用户接收机所经历的时间,将这个称为信号传播时间的时间段乘以信号的速度(光速),便得到从发射源到接收机的距离。接收机通过测量从多个位置己知的发射源(即导航台)所广播的信号的传播时间,便能确定自己的位置。大多数民用GPS的精度在15米以内,个别高端产品可以保持在5米左右。

GPS测量的原理是将卫星视为“动态”的控制点,在己知其瞬时坐标(可根据卫星轨道参数计算)的条件下,GPS卫星和用户接收天线之间的距离(或距离差)为观测量,采用空间距离后方交会方法[7](前方交会),从而确定用户接收机天线所处的位置。

通过以上方法,手机GPS模块定时向卫星发送请求,获取用户的实时经纬

度信息,提供给逻辑运算模块进行处理。

AndroidGPS类简介

Android支持地理定位服务的API.该地理位置可以用来获取当前设备的地理位置。应用程序可以定时请求更新设备当前的地理定位信息。Android关于定位[8]功能的android.location包中比较重要的类:

LocationManager:本类提供访问定位服务的功能,也提供获取最佳定位提供者的功能。另外,临近警报功能也可以借助该类来实现。

LocationProvider:定位提供者的抽象类。定位提供者具备周期性报告设备地理位置的功能。

LocationListener:提供定位信息发生改变时的回调函数。必须事先在定位管理器中注册监听器对象。

Criteria:该类使得应用能够通过在LocationProvider[9]中设置的属性来选择合适的定位提供者。

Android也提供了一组访问Google MAPAPI,借助Google MAP及定位API,我们就能在地图上显示用户当前的地理位置。Android中定义了一个名为com.google.android.maps的包,其中包含了一系列用于在Google MAP上显示,控制和层叠信息的功能类,以下是该包中最重要的几个类:

MapActivity:这个类是用于显示Google MAPActivity类,它需要连接底层网络。

MapView:用于显示地图的View组件,它必须和MapActivity配合使用。

MapController:用于控制地图的移动。

Overlay:一个可显示于地图之上的可绘制的对象。

GeoPoint:一个包含经纬度位置的对象。

需求分析

功能需求

本文主要功能模块包括以下几个:计步功能模块、查询运动信息功能模块、设置参数模块、退出系统模块。

1、计步功能:通过Android手机GPS功能来获取用户实时的经纬度信息,借助地球球面两点间的距离公式计算用户的运动距离,根据用户设置的身高可以估算出步长,从而测得用户所走的步数;计时功能根据用户运动的时间可以计算出用户步行的速度,然后可以根据用户设置的体重,估算用户步行运动所消耗的热量。当距离发生改变时在前台界面上刷新用户步行的步数、距离、每分钟的步数、每千米的距离和消耗的卡路里;该计步功能还实现了Notification后台运行的功能。

2、查询运动信息功能:通过输入日期后查询数据库里的每天运动信息表来显示每天的运动历史信息。该功能可以实现查询最近一个月的运动信息,运动信息的显示分为两部分:通过柱状图来直观的显示用户每天步行的步数所达到期望目标的百分比以及通过文本框来显示用户运动的详细信息包括步数、距离、总用时、消耗的热量。

3、设置参数功能:设置用户的身高、体重、运动目标3个参数信息。通过设置身高可以大致计算出不同身高的人的步长是多少,以便根据距离来计算用户所行走的步数;通过设置体重可以估算出不同体重的人运动所消耗的卡路里量;通过设置运动目标可以在用户查询最近运动信息时提示用户达到期望目标的百分比,更好的激励用户去运动。

4、退出系统功能:当用户想终止计步软件时,可以点击退出菜单后退出本系统。

系统的用例图如3-1所示:

31计步软件用例图

数据的需求分析

本文的计步软件的数据结构和功能如下:

(1)每次运动信息。存放用户一天当中所有运动次数的历史记录信息,E-R图见图3-2所示。

(2)每天运动信息。存放用户一天当中运动的累积总量信息E-R图见图3-3所示。

32 E-R

33 E-R

系统运行环境

本软件是在windows 7 操作系统中,利用Eclipse 开发工具搭载Android SDK工具包,结合Android手机自带的sqlite数据库进行开发。

系统设计

功能模块设计

计步功能

计步功能的核心为用户运动距离的统计,该模块包括三个部分:GPS定位、Notification管理,实时更新数据。完成该功能所涉及到的类有:计步界面类(StepActivity)主要负责渲染计步功能的步数、速度、距离、卡路里数据显示的界面,后台服务类(PedometerService)主要负责计步功能的实现,完成GPS定位的操作和计算需要在前台界面上更新的数据,接口类(IBinder)使用了onBind方法返回绑定服务的对象,IBinder接口的实现类(StepBinder)主要负责提供后台服务的service对象,及获取计步中的步数、速度、距离、卡路里的值的方法以供绑定的service对象调用,通信类(Handler)主要负责将后台服务的数据发送到前台界面上显示出来。按钮的监听类(onClickListener)主要负责监听不同的按钮操作,从而执行不同的操作。这些类图之间的关系如图4-1所示:

41 计步功能的类图

完成计步功能的时序图描述为:当用户点击底部菜单的“首页”选项时进入到计步的视图界面,点击开始按钮后与后台服务进行绑定,创建后台服务的service对象,从而能够获取service中的方法。后台服务调用创建(onCreate)方法后显示notification 后台提示。当用户想关闭该计步软件的界面使用手机中的其它软件而又不想停止计步软件运行的情况下,在计步功能与后台服务绑定时,会在界面的顶部菜单栏提示该软件的运行通知,关闭计步软件的界面后,菜单栏的提示任然存在,这时可以通过点击提示再次进入到上次关闭的界面上,当用户退出系统后,顶部的菜单栏提示就会关闭。调用开始(onStart)方法后获取前台传递的时间参数信息,接着向Activity中返回Service的绑定对象,从而实现界面信息与后台服务的绑定。在后台服务中创建GPS定位的监听器,当经纬度发生变化时调用位置变化函数获取不同位置的经纬度信息,然后可以根据两点间的不同经纬度来求得距离,通过计算求得步数、速度、消耗的卡路里,将发生变化的数据通过开启一个新的线程发生消息发生到UI线程进行界面的更新操作。

完成该功能的时序图如图4-2所示:

42 计步时序图

查询运动信息功能

作为人们日常工作生活的运动助手,除了能够对用户的运动情况进行实时监测,还应该能将之前的运动数据以历史记录的形式提供给用户进行查询,因此,本文设计了查询运动数据功能的模块,通过柱状图来显示步行的步数所达到期望目标的百分比,和文本框显示步行的详细信息:步数、总用时、距离和消耗的卡路里。完成该功能所涉及到的类有:柱状视图类(Configuration)主要负责绘制柱状图的操作,可以实现动态的绘画和静止的显示柱状图形,运动信息界面类(ChartActivity)主要负责查询最近一个月的运动信息情况并把这些信息通过文本框和柱状图的形式显示在界面上,分页类(ViewPager)主要负责实现分页的效果,适配器类(MyAdapter)主要负责完成界面的显示操作,页面监听类(MyPageListener)主要负责当页面发生改变时执行相应的操作。它们的类图关系如图4-3所示:

43 查询运动信息类图

完成该功能的时序图如图4-4所示:

44 查询运动信息时序图

完成查询运动信息功能的时序图描述为:当用户点击底部菜单的“统计”选项时进入到查询运动信息的视图界面,定义实现分页的布局容器后创建ViewPager的适配器,然后进行设置适配器。 创建ViewPager的页面监听器,然后进行设置页面监听器,当页面发生切换操作时调用翻页onPageScrolled()方法,并在切换时查询SQLite数据库中每天的运动信息表来显示步行的详细信息,根据查询到的步数来设置柱状图的大小直观的显示达到期望运动目标的百分比。

设置参数功能

为了实现计步功能,需要设置一些辅助信息,如用户的身高、体重,用户运动的目标步数,以便能更准确的统计步数和消耗的卡路里量。实现该功能所涉及到的类有:PedometerSettings类主要负责从配置文件中读取存储的数据值,设置参数界面类(SettingsActivity)主要负责渲染设置参数的界面,后台服务类(PedometerService)在设置参数的功能中主要负责获取从配置文件中读取的参数配置信息,以用来计算步数、消耗的卡路里量。这些类图之间的关系如图4-5所示:

45 设置参数类图

完成该功能的时序图如图4-6所示:

46 设置参数时序图

完成设置参数功能的时序图描述为:当用户点击底部菜单的“设置”选项时进入到设置参数的视图界面,当用户点击分别设置身高、体重、运动目标并保存后,修改的值会保存到配置文件中,后台服务类会通过调用获取身高、体重、运动目标的方法来对这些数据进行操作。

退出系统功能

当用户想停止步行运动时需要退出该软件系统。实现该功能的类有主界面类(MainActivity)主要负责显示本软件的四个功能。该类图如图4-7所示:

47 退出系统类图

完成该功能的时序图如图4-8所示:

48 退出系统时序图

完成退出系统功能的时序图描述为:当用户点击计步软件的图标通过欢迎界面进入到主界面进行操作后默认为计步界面,当用户想退出系统时,点击系统的菜单选项“退出”,系统会弹出一个警示框,询问是否要退出本软件,若选择确定,则退出系统,计步界面会调用销毁方法解除service绑定,后台服务就会调用销毁方法取消Notification后台运行;若选择取消,则继续计步软件的使用。

数据设计

1、表4-1为每次运动信息表HistoryByTimes,该表记录每次步行的历史信息。表中的字段有运动的日期、开始时间、运动的用时、步数、每分钟的步数、距离、每千米的距离、消耗的卡路里。每次的运动信息的获取是当用户点击了停止按钮之后,将此次的运动信息保存到该表中。

2、表4-2为每天运动信息表HistoryByDay,该表记录每天步行的历史信息。表中的字段有运动的日期、运动的用时总量、步数、距离、平均速度、消耗的卡路里。每天的运动信息的获取是当用户点击了开始按钮之后,先判断是否已保存前一天运动总量,若没有,则保存到该表之中。

41 每次运动信息表

列名

数据类型

允许空

说明

dateTime

varchar(20)

Y

日期

startTime

varchar(20)

Y

开始时间

totalTime

varchar(20)

Y

时长

steps

Integer

Y

步数

pace

Integer

Y

每分钟步数

distances

double

Y

距离

speed

float

Y

速度

calories

float

Y

卡路里

42 每天运动信息表

列名

数据类型

允许空

说明

dateTime

varchar(20)

Y

日期

totalTime

varchar(20)

Y

时长

steps

Integer

Y

步数

distances

double

Y

距离

avgSpeed

float

Y

平均速度

calories

float

Y

卡路里

系统实现

Android环境搭建

任何事物要运行,都要有它的环境,Android也有它的环境才能够运行,下面介绍Android的开发环境配置.

搭建开发环境需要的软件:

1、操作系统:Windows 7Linux

2、软件包:Android SDK(Software Development kit Java Development kit) ADT(Android Develoopment Tool)

3IDE环境:Eclipse IDE

4JDKJava Runtime Environment虚拟机 (JDK)Java Development kit

安装步骤如下:

第一步:安装Java虚拟机sun-java6-jdk版本

第二步:在官网http://developer.android.com/sdk/index.html,如图5-1所示下载软件包和开发环境[13]

51 开发环境下载图

下载完解压后在系统环境变量中设置platform-tool的路径即可进行开发了。由于本软件的GPS定位需要使用Google APIs,而该简化版开发环境中没有安装,点击SDK Manager.exe后勾选相应的Google APIs进行下载安装。如图5-2所示:

52 下载google api

这样就可以进行本软件的开发了。

软件结构设计

本软件程序结构如图5-3所示:

53 程序结构图

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

54 pedometer工程的文件用途说明

包名称

文件名

说明

com.pedometer.tabActivity

WelcomeActivity.java

软件的欢迎界面

com.pedometer.tabActivity

MainActivity.java

主界面

com.pedometer.tabActivity

StepActivity.java

计步界面

com.pedometer.tabActivity

SettingsActivity.java

设置参数界面

com.pedometer.tabActivity

ChartActivity.java

查询运动信息界面

com.pedometer.tabActivity

PedometerService.java

后台服务

com.pedometer.tabActivity

PedometerSettings.java

获取参数信息

com.pegometer.dao

PedometerDAO.java

持久层负责数据的存储访问

com.pedometer.model

HistoryByTimes.java

每次运动信息类

com.pedometer.model

HistoryByDay.java

每天运动信息类

com.pedometer.util

ConfigurationView.java

绘画柱形图的类

com.pedometer.util

MySQLiteHelper.java

操作数据库的类

本软件资源文件结构设计如图5-5所示:

55 资源结构图

Android的资源文件保存在/res的子目录中。其中/res/drawable目录中保存的是图片文件,/res/layout目录中保存的是界面的布局文件,/res/menu目录中保存的是菜单文件,/res/values目录中保存的是用来自定义字符串、自定义颜色、图片、样式的文件,/res/xml目录中保存的是XML格式的数据文件。所有在程序开发阶段可以被调用的资源都保存在这些目录中,具体每个资源文件的用途如图5-6所示。

56 pedometer工程的文件用途说明

包名称

文件名

说明

layout

main_welcome.xml

软件的欢迎界面的布局

maintabs.xml

主界面功能的布局

main_step.xml

计步界面的布局

main_chart.xml

设置参数界面的布局

chart_bar.xml

查询运动信息界面的布局

drawable

bg_box.9.png

工程中所用的图片

menu

menu_main.xml

设置菜单的布局

values

colors.xml

保存颜色的XML文件

dimens.xml

保存字体大小的XML文件

drawables.xml

保存图片资源的XML文件

strings.xml

保存字符串的XML文件

styles.xml

保存样式的XML文件

xml

preferences.xml

保存参数设置的XML文件

计步功能

一个好的软件界面既能使画面美观也能提高对用户的吸引力。因而本软件设置一个进入计步器界面之前的一个欢迎界面,如图5-7所示。

该欢迎界面通过动画的方式来显示,主要代码如下:

欢迎界面的详细代码请见附录1

57欢迎界面图

当欢迎界面的动画结束后就进入主程序,默认是进入计步器功能界面。该主界面是通过TabHost标签来实现底部菜单功能,有计步、设置、历史三个底部菜单项,如图5-8所示。

实现主界面中的底部菜单功能的核心代码如下:

实现底部菜单功能的代码请见附录2

58 tab标签底部菜单界面图

当用户点击开始按钮后开始统计步数,使用Chronometer完成计时功能,与后台服务进行绑定后,会在标题栏显示计步功能的通知信息,可以在后台通过GPS定位获取用户步行过程中的经纬度的变化,从而计算出用户所步行的距离,根据距离来统计用户的步数、每分钟的步数、每小时步行的距离、消耗的卡路里。通过Handler类来进行前台界面与后台服务的异步通信,将用户步行的情况实时地更新到界面上。也可以使用暂停按钮,若想继续步行,则按继续按钮,若想停止计步,则按下停止按钮。当停止计步时会将此次步行的信息保存到SQLite数据库中。在下一次运动点击开始按钮时,会先判断前一天的运动信息总量是否保存到每天运动信息表中,若没有保存,则将前一天的运动信息总量也保存到数据库的每天运动信息表之中。

本软件的计步功能是通过在模拟器上通过输入经纬度的值来测试的,在Emulator Control中更改经纬度信息后,前台的界面信息发生改变,如图5-9所示:

59计步界面图

在实现了基本的距离测算功能后,软件增加了步数、速度、能量的消耗的功能。资料显示,步长与用户的身高与速度成正比。所以不同的运动状态下需要使用不同的计算公式。为此软件提供了步行和跑步两种状态,本文中提供的步长的计算公式[12]分别如下所示:

1、走路:stepLength(walk)=height/3

2、跑步:stepLength(run)=1.2*height.

卡路里(calories)的消耗量与诸多因素有关,故对其进行近似计算,具体计算公式如下:

Calories=weight*distances(total)*1.036

距离和体重的单位分别为米和千克,能量的单位为cal.

在变化经纬度信息后获取到距离时,根据用户的身高算出步长,从而计算出步数,已知用户运动的时间,可以算出用户每分钟运动的步数和每千米运动的距离,根据用户的体重,可以计算出用户运动所消耗的热量。

计步功能的核心代码如下:

完成计步功能的详细代码请见附录3

查询运动信息功能

在应用主界面中点击历史tab则进入到查询运动信息界面,该界面通过分页的形式左右侧滑来显示最近一个月以内的所有运动信息。该界面分为2个部分:柱状图来显示用户每天运动的步数所达到期望的运动目标的百分比,文本框来显示用户每天运动的具体运动信息。如图5-10所示:

510查询运动信息界面图

完成用户运动信息查询的功能的分页效果是通过ViewPager来实现的,ViewPager提供了多界面切换的新效果。新效果有如下特性:

1、当前显示一组界面中的其中的一个界面。

2、当用户通过左右滑动界面时,当前的屏幕显示当前的界面和下一个界面的一部分。

3、滑动结束后,界面自动跳转到当前选择的界面中。

实现分页效果的核心代码如下:

分页功能的详细代码请见附录4

在界面中显示柱状图,需要传入参数-步数。柱状图会根据数值的大小来变换显示的颜色,如绿色、土黄色、红色。柱状图的升高采用了类似于动画效果,可以在创建时设置是否启动动画效果。柱状图的显示核心代码如下:

实现柱状图的代码请见附录5

设置参数功能

为了时计步软件更好的为用户所服务,设置用户的个人信息参数是很必要的。该功能主要设置用户的身高、体重、期望的运动目标。

设置用户身高的作用是为了计算出用户的步长,以便计步功能能够使用该数据算出步数。设置用户体重的作用是为了计算出用户运动中所消耗的卡路里量。设置用户期望的运动目标的作用是为了根据查询到的步数来设置柱状图的大小直观的显示用户运动的步数所达到期望运动目标的百分比,让用户更直接的感受到运动带来的效果,激励用户去锻炼。

实现该功能的界面如图5-11所示。

511设置界面图

参数设置的功能界面通过preferences.xml来进行设置,继承PreferenceActivity后将该界面显示出来。当用户选择不同的参数时,将修改后的信息保存到配置文件中,使用SharedPreferences类来使用配置文件中的数据,以便辅助完成计步的功能的函数来调用。

实现该功能的核心代码如下:

完成该功能的详细代码请见附录6.

退出系统功能

当用户停止运动想退出该软件时,就点击菜单选择退出系统选项,使用alert对话框询问是否确认退出,界面如图5-12所示。

512退出系统界面图

完成退出系统的详细代码请见附录7



结束语

经过2个多月的努力,我的毕业设计工作即将完成。在本次毕业设计之前我对Android的了解几乎为零,对图片处理也很不熟悉,使得我在毕业设计在开始阶段十分的困难。在花了大量的时间查阅资源,并且在导师的耐心指导和帮助下完成了此次的毕业设计。

由于对Android开发的不熟悉,我碰到了很多问题,走不了少的弯路,面对这些问题我也只能硬着头皮认真排查和上网求助,最终解决了问题。在解决问题的过程中,学会了自主学习的方法,熟悉了很多程序编写规范和经典编程理念。完成此次计步软件的课题,我学会了灵活运用基础理论知识,结合实际情况解决问题。最终实现了运用Android组件设计软件的界面,运用IntentService构建计步软件的控制中心,实用SharePreferenceSQLite数据库构建计步软件的数据中心。虽然我的论文作品不是很成熟,还有很多不足之处,但每一次解决困难的过程中,我积累了经验,学会了遇到困难不放弃和求真务实的学习态度。

尽管本次毕业设计的计步功能已经基本开发完成,但这是我第一次开发基于Android的软件,相对于成熟的计步软件,软件中还存在许多的不足,针对这些问题下一步的工作计划如下:

(1)完善计步的功能,添加步行时的轨迹显示界面;

(2)完善查询运动信息的功能,添加查询一天当中每次的运动信息的图表;

(3)优化代码,使代码更加简洁。



参考文献

[1] 贺飞.智能手机操作系统在全球各地区份额调查报告[OL]. http://mobile.yesky.com /262/11693762.shtml.2012-11-20

[2] 张孝祥著.java就业培训教程[M].北京:清华大学出版社,2007.

[3] 赵勇,杨红梅, 第三代移动通信业务服务规范研究[D],电信网技术,Vol10),20094.

[4] 开放手机联盟.Android[DB/OL].http://code.google.com/android/,2008-07-30.

[5] E2ECloud工作室著.深入浅出Google Android[M].北京:人民邮电出版社,2009-08-1.

[6] JavaEyehttp://www.javaeye.com/forums 2010-04-15.

[7] 盖玉婷、庄洪宇,GPS测量的误差及精度控制[N],黑龙江科技信息,201016):10~11.

[8] 李明峰、冯洪宝、刘三枝.GPS定位技术及其应用[M].国防工业出版社,2006,2.

[9] Android API.chmhttp://androidappdocs.appspot.com/index.html 2010-04-10.

[10] 王家林.大话企业级Android应用开发实战[M].电子工业出版社,2011,8

[11] James Steele NelsonThe Android Developer’s CookbookBuliding Application with the Android SDK20118

[12] 汲康.基于IOS的娱乐计步软件_HEALTHY_PIC的设计与实现[C]20123.

[13] Android官方网站.http://developer.android.com/sdk/index.html



附录1:欢迎界面的代码

public class WelcomeActivity extends Activity{

private static final int GOTO_MAIN_ACTIVITY=0;

private static final long SPLASH_DISPLAY_LENGHT = 3000;

private TextView msg;

private ImageView image;

@Override

protected void onCreate(Bundle savedInstanceState) {

// TODO Auto-generated method stub

super.onCreate(savedInstanceState);

//super.requestWindowFeature(Window.FEATURE_NO_TITLE);

setContentView(R.layout.main_welcome);

image=(ImageView)findViewById(R.id.image);

AnimationSet animationset=new AnimationSet(true);

AlphaAnimation alphaAnimation=new AlphaAnimation(1, 0);

alphaAnimation.setDuration(5500);

animationset.addAnimation(alphaAnimation);

image.startAnimation(animationset);

new Handler().postDelayed(new Runnable(){

@Override

public void run() {

Intent mainIntent = new Intent(WelcomeActivity.this,MainActivity.class);

WelcomeActivity.this.startActivity(mainIntent);

WelcomeActivity.this.finish();

}

}, SPLASH_DISPLAY_LENGHT);

}

}

附录2TabHost标签实现底部菜单功能

private void setupIntent()

{

this.mTabHost = getTabHost();

TabHost localTabHost = this.mTabHost;

localTabHost.addTab(buildTabSpec("Step_TAB", R.string.main_home,

R.drawable.ic_step, this.mAIntent));

localTabHost.addTab(buildTabSpec("Settings_TAB", R.string.main_settings,

R.drawable.ic_settings, this.mBIntent));

localTabHost.addTab(buildTabSpec("Chart_TAB", R.string.main_chart, R.drawable.ic_chart, this.mDIntent));

}

private TabHost.TabSpec buildTabSpec(String tag, int resLabel, int resIcon, final Intent content)

{

return this.mTabHost.newTabSpec(tag).setIndicator(getString(resLabel), getResources().getDrawable(resIcon)).setContent(content);

}

附录3:计步功能

public class StepActivity extends Activity implements OnClickListener{

private static final String TAG = "Pedometer";

private LinearLayout mBottomLayout;

private Button btn_Start;

private Button btn_pause;

private Button btn_GoOn;

private Button btn_Stop;

private Chronometer chronometer;

public static long time=0;

private PedometerService.StepBinder mService;

private boolean mIsRunning=false;//判断服务是否启动

private TextView mStepValueView;

private TextView mPaceValueView;

private TextView mDistanceValueView;

private TextView mSpeedValueView;

private TextView mCaloriesValueView;

private HistoryByTimes mHistory;

private int mStepValue;

private int mPaceValue;

private float mDistanceValue;

private float mSpeedValue;

private int mCaloriesValue;

private Intent intent;

String[] date=null;

int value1;

int value2;

float value3;

float value4;

float value5;

private static final int STEPS_MSG = 1;

private static final int PACE_MSG = 2;

private static final int DISTANCE_MSG = 3;

private static final int SPEED_MSG = 4;

private static final int CALORIES_MSG = 5;

private ServiceConnection mServiceConnection = new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

Log.i("stepActivity连接服务", "service is connected!!!");

mService = (PedometerService.StepBinder)service;

new Thread() {

@Override

public void run() {

while(mIsRunning)

{

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

}

value1 = mService.stepsChanged(); mHandler.sendMessage(mHandler.obtainMessage(STEPS_MSG,value1, 0));

value2 = mService.paceChanged(); mHandler.sendMessage(mHandler.obtainMessage(PACE_MSG,value2, 0));

value3 = mService.distanceChanged(); mHandler.sendMessage(mHandler.obtainMessage(DISTANCE_MSG,(int) (value3), 0));

value4 = mService.speedChanged();

System.out.println("value4 " + value4); mHandler.sendMessage(mHandler.obtainMessage(SPEED_MSG,(int) (value4), 0));

value5 = mService.caloriesChanged(); mHandler.sendMessage(mHandler.obtainMessage(CALORIES_MSG,(int) (value5), 0));

}

}

}.start();

}

@Override

public void onServiceDisconnected(ComponentName name) {

mService = null;

}

};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main_step);

chronometer =(Chronometer)findViewById(R.id.pedometer_last_time);

btn_Start=(Button)super.findViewById(R.id.btn_start);

btn_GoOn=(Button)super.findViewById(R.id.btn_go_on);

btn_Stop=(Button)super.findViewById(R.id.btn_stop);

mBottomLayout=(LinearLayout)super.findViewById(R.id.layout_bottom);

mStepValueView=(TextView)super.findViewById(R.id.step_value);

mPaceValueView=(TextView)super.findViewById(R.id.pace_value);

mDistanceValueView=(TextView)super.findViewById(R.id.distance_value); mSpeedValueView=(TextView)super.findViewById(R.id.speed_value); mCaloriesValueView=(TextView)super.findViewById(R.id.calories_value);

btn_Start.setOnClickListener(this);

btn_GoOn.setOnClickListener(this);

btn_Stop.setOnClickListener(this);

//设置计时信息的格式

chronometer.setFormat("计时:%s");

}

@Override

public void onClick(View view) {

switch(view.getId())

{

case R.id.btn_start:

chronometer.setBase(SystemClock.elapsedRealtime()); chronometer.start();//开始计时

startStepService();

bindStepService();

saveYestodayHistory();

btn_Start.setBackgroundResource(R.drawable.btn_pause);

btn_Start.setText("暂 停") ;

btn_Start.setOnClickListener(new OnClickListener()

{

@Override

public void onClick(View view) {

btn_Start.setVisibility(view.GONE);

mBottomLayout.setVisibility(view.VISIBLE);

chronometer.stop();//暂停计时

unbindStepService();

stopStepService();

long minutes=Integer.parseInt(chronometer.getText().toString().split(":")[1]);

long seconds=Integer.parseInt(chronometer.getText().toString().split(":")[2]);

time=minutes*60+seconds;

Log.e("时间", String.valueOf(time));

}

});

break;

case R.id.btn_go_on: chronometer.setBase(SystemClock.elapsedRealtime()-time*1000);

chronometer.start();//继续计时

startStepService();

bindStepService();

mBottomLayout.setVisibility(view.GONE);

btn_Start.setVisibility(view.VISIBLE);

btn_Start.setText("暂 停") ;

break;

case R.id.btn_stop:

chronometer.setBase(SystemClock.elapsedRealtime());/

mBottomLayout.setVisibility(view.GONE);

btn_Start.setVisibility(view.VISIBLE);

btn_Start.setText("开 始");

saveHistoryDataByTimes();

//showData();

resetValues();

startStepService();

bindStepService();

break;

}

}

private void startStepService() {

if (! mIsRunning) {

Log.i(TAG, "[step SERVICE] Start");

mIsRunning = true;

Intent intent=new Intent(StepActivity.this,PedometerService.class);

Bundle bundle=new Bundle();

bundle.putString("time",String.valueOf(Calendar.getInstance().getTimeInMillis()) );

intent.putExtras(bundle);

startService(intent);

}

}

private void bindStepService() {

Log.i(TAG, "[step SERVICE] Bind");

intent=new Intent(StepActivity.this, PedometerService.class);

this.getApplicationContext().bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);

}

private void unbindStepService() {

Log.i(TAG, "[step SERVICE] Unbind");

this.getApplicationContext().unbindService(mServiceConnection);

}

private void stopStepService() {

Log.i(TAG, "[step SERVICE] Stop");

if (mService != null) {

Log.i(TAG, "[step SERVICE] stopService");

stopService(new Intent(StepActivity.this, PedometerService.class));

}

mIsRunning = false;

}

private void resetValues()

{ mStepValueView.setText("0");

mPaceValueView.setText("0");

mDistanceValueView.setText("0");

mSpeedValueView.setText("0");

mCaloriesValueView.setText("0");

}

public void saveHistoryDataByTimes()

{

Date nowTime=new Date();

SimpleDateFormat matter=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss(EE)");

String formatTime=matter.format(nowTime);

date=formatTime.split(" ");

mHistory=new HistoryByTimes();

this.mHistory.setDate(date[0]);

this.mHistory.setStartTime(date[1]);

this.mHistory.setTotalTime(String.valueOf(time)); this.mHistory.setSteps(Integer.parseInt(this.mStepValueView.getText().toString())); this.mHistory.setDistance(Float.parseFloat(this.mDistanceValueView.getText().toString())); this.mHistory.setPace(Integer.parseInt(this.mPaceValueView.getText().toString())); this.mHistory.setSpeed(Float.parseFloat(this.mSpeedValueView.getText().toString())); this.mHistory.setCalories(Float.parseFloat(this.mCaloriesValueView.getText().toString()));

PedometerDAO dao=new PedometerDAO(this);

dao.open();

try {

dao.saveHistoryDataByTimes(mHistory);

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

dao.close();

}

public void showData()

{

PedometerDAO dao=new PedometerDAO(this);

dao.open();

try {

Cursor cursor=dao.queryDataByTimes(date[0]);

while(cursor.moveToNext())

{

String steps=cursor.getString(cursor.getColumnIndex("steps"));

String totalTime=cursor.getString(cursor.getColumnIndex("totalTime"));

String distances=cursor.getString(cursor.getColumnIndex("distances"));

String calories=cursor.getString(cursor.getColumnIndex("calories"));

}

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

private Handler mHandler = new Handler() {

@Override public void handleMessage(Message msg) {

switch (msg.what) {

case STEPS_MSG:

mStepValue = (int)msg.arg1;

mStepValueView.setText("" + mStepValue);

break;

case PACE_MSG:

mPaceValue = msg.arg1;

if (mPaceValue <= 0) {

mPaceValueView.setText("0");

}

else {

mPaceValueView.setText("" + (int)mPaceValue);

}

break;

case DISTANCE_MSG:

mDistanceValue = ((int)msg.arg1)/1000f;

if (mDistanceValue <= 0) {

mDistanceValueView.setText("0");

}

else {

mDistanceValueView.setText(

("" + (mDistanceValue + 0.000001f)).substring(0, 5)

);

}

break;

case SPEED_MSG:

mSpeedValue = msg.arg1;

if (mSpeedValue <= 0) {

mSpeedValueView.setText("0");

}

else {

mSpeedValueView.setText(

("" + (mSpeedValue + 0.000001f)).substring(0, 4)

);

}

break;

case CALORIES_MSG:

mCaloriesValue = msg.arg1;

if (mCaloriesValue <= 0) {

mCaloriesValueView.setText("0");

}

else {

mCaloriesValueView.setText("" + (int)mCaloriesValue);

}

break;

default:

super.handleMessage(msg);

}

}

};

public void saveYestodayHistory()

{

Calendar calendar=Calendar.getInstance();

calendar.add(Calendar.DATE, -1);

String yestedayDate= DateFormat.format("EE, MM dd", calendar).toString();

PedometerDAO dao=new PedometerDAO(this);

dao.open();

boolean flag = false;

try {

flag = dao.isYestodaySaved(yestedayDate);

} catch (SQLException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

}

if(flag==false)

{

try {

dao.saveYestodayData(yestedayDate);

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

dao.close();

Log.i(TAG, "保存前一天记录成功!");

}

}

public class PedometerService extends Service {

private static final String TAG = "com.pedometer.service.PedometerService";

private NotificationManager nm; //声明NotificationManager

private LocationManager locationManager;

public GeoPoint gp1;

public GeoPoint gp2;

private String mLocationProvider="";

private Location mLocation;

public float distance=0.0f;

private int step=0;

private int pace=0;

private float speed=0.0f;

private float calories=0.0f;

private PedometerSettings mSettings;

private SharedPreferences pre;

private long timeFromActivity;

private long timeNow;

private long targetTime;

private StepBinder mBinder = new StepBinder();

@Override

public IBinder onBind(Intent intent) {

Log.i(TAG, "return the binder");

return this.mBinder;

}

@Override

public void onCreate() {

Log.i(TAG, "[my SERVICE] onCreate");

super.onCreate();

showNotification();

this.locationManager=(LocationManager) super.getSystemService(LOCATION_SERVICE);

getLocationProvider(); // 取得provider和location

gp1 = this.getGeoByLocation(mLocation);

gp2 = gp1;

// 设置事件的listener

this.locationManager.requestLocationUpdates(mLocationProvider, 1000, 1, mLocationListener);

pre=PreferenceManager.getDefaultSharedPreferences(this);

mSettings=new PedometerSettings(pre);

}

@Override

public void onDestroy() {

Log.i(TAG, "[my SERVICE] onDestroy");

super.onDestroy();

this.nm.cancel(R.string.app_name);

} @Override

public void onStart(Intent intent, int startId) {

Log.i(TAG, "[my SERVICE] onStart");

Bundle bundle=intent.getExtras();

String time=bundle.getString("time");

timeFromActivity=Long.parseLong(time);

Log.i("传递到service的时间", time);

}

//显示Notification

private void showNotification()

{

this.nm=(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

Notification myNotification=new Notification(R.drawable.ic_step,null,

System.currentTimeMillis());//参数分别为图标,标题,知时间

myNotification.defaults=Notification.DEFAULT_SOUND;//发出默认声音 //当单击消息时就会向系统发送openintent意图

PendingIntent contentIntent=PendingIntent.getActivity(this, 0, new Intent(this,MainActivity.class), 0);

myNotification.setLatestEventInfo(this, "计步器", "记录您的脚步!", contentIntent);

this.nm.notify(R.string.app_name, myNotification);//发送通知

}

public class StepBinder extends Binder

{

public StepBinder(){Log.i("步伐绑定", "StepBinder");};

public PedometerService getService()

{

return PedometerService.this;

}

public int stepsChanged()

{

Log.i("步数", String.valueOf(step));

return step;

}

public int paceChanged(){

Log.i("每分钟步数", String.valueOf(pace));

return pace;

}

public float distanceChanged(){

Log.i("行走距离", String.valueOf(distance));

return distance;

}

public float speedChanged(){

Log.i("每千米距离", String.valueOf(speed));

return speed;

}

public float caloriesChanged(){

Log.i("消耗卡路里", String.valueOf(calories));

return calories;

}

}

public final LocationListener mLocationListener=new LocationListener()

{ @Override

public void onLocationChanged(Location location) {

// TODO Auto-generated method stub

//记下移动后的位置

gp2=getGeoByLocation(location);

//取得移动距离

distance+=GetDistance(gp1,gp2);

//更新界面上的数据

updateView(distance);

gp1=gp2;

Log.i("距离:",String.valueOf(distance));

}

@Override

public void onProviderDisabled(String provider) {

// TODO Auto-generated method stub

}

@Override

public void onProviderEnabled(String provider) {

// TODO Auto-generated method stub

}

@Override

public void onStatusChanged(String provider, int status, Bundle extras) {

// TODO Auto-generated method stub

}

};

//取得两点间的距离

public double GetDistance(GeoPoint gp1,GeoPoint gp2)

{

double lat1r=ConvertDegreeToRadians(gp1.getLatitudeE6()/1E6);

double lat2r=ConvertDegreeToRadians(gp2.getLatitudeE6()/1E6);

double Long1r=ConvertDegreeToRadians(gp1.getLongitudeE6()/1E6);

double Long2r=ConvertDegreeToRadians(gp2.getLongitudeE6()/1E6);

double R=6371;

double d=Math.acos(Math.sin(lat1r)*Math.sin(lat2r)+Math.cos(lat1r)*

Math.cos(lat2r)*Math.cos(Long2r-Long1r))*R;

return d*1000;

}

private double ConvertDegreeToRadians(double degrees)

{

return (Math.PI/180)*degrees;

}

public void getLocationProvider()

{

Criteria criteria=new Criteria();

criteria.setAccuracy(Criteria.ACCURACY_FINE);

criteria.setCostAllowed(false);

criteria.setPowerRequirement(Criteria.POWER_LOW);

criteria.setBearingRequired(false);

criteria.setAltitudeRequired(false); this.mLocationProvider=this.locationManager.getBestProvider(criteria, true); this.mLocation=this.locationManager.getLastKnownLocation(mLocationProvider);

}

public GeoPoint getGeoByLocation(Location location)

{

GeoPoint gp=null;

try {

if(location!=null)

{

double geoLatitude=location.getLatitude()*1E6;

double geoLongitude=location.getLongitude()*1E6;

gp=new GeoPoint((int)geoLatitude,(int)geoLongitude);

}

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

return gp;

}

public void updateView(double distance)

{

long timeNow=Calendar.getInstance().getTimeInMillis();

Log.i("service现在的时间", String.valueOf(timeNow));

targetTime=(timeNow-timeFromActivity);

Log.i("时长", String.valueOf(targetTime));

float stepLength=mSettings.getHeight()/3/100;

step=(int) (distance/stepLength);

pace=(int) ((distance/stepLength/targetTime)*(1000*60));//单位是步每分钟

speed=(float) ((distance/1000/targetTime)*(1000*60*60));//单位千米每小时

calories=(float) (mSettings.getBodyWeight()*distance*1.036);

}

}

附录4:分页实现左右侧滑

public class ChartActivity extends Activity

{

private ViewPager viewPager;

private PagerTitleStrip pagerTitleStrip;//表示滑动的每一页的标题

private List list=new List();;//表示装载滑动的布局

private List titleList=new List();;//表示滑动的每一页的标题

public static final int WIDTH = 280;

public static final int HEIGHT = 250;

private ConfigurationView bar,view;

private LinearLayout layout;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main_chart);

viewPager=(ViewPager)this.findViewById(R.id.pager); pagerTitleStrip=(PagerTitleStrip)this.findViewById(R.id.pagerTitle);

Calendar calendar=Calendar.getInstance();

today=DateFormat.format("EE, MM dd", calendar).toString();

titleList.add(today);

String mToday= DateFormat.format("dd", calendar).toString();

String month= DateFormat.format("MM", calendar).toString();

View view[]=new View[35];//定义动态加载布局的容量

for(int i=0;i<=30;i++)

{ view[i]=LayoutInflater.from(ChartActivity.this).inflate(R.layout.chart_bar, null);

list.add(view[i]);

calendar.add(Calendar.DATE, -1);

String yestedayDate= DateFormat.format("EE, MM dd", calendar).toString();

titleList.add(yestedayDate);

}

viewPager.setAdapter(new MyAdapter());

viewPager.setOnPageChangeListener(new MyPageListener());

}

class MyAdapter extends PagerAdapter

{ @Override

public int getCount() {

// TODO Auto-generated method stub

return list.size();

}

@Override

public boolean isViewFromObject(View arg0, Object arg1) {

// TODO Auto-generated method stub

return arg0==arg1;

}

@Override

public void destroyItem(ViewGroup container, int position,

Object object) {

//把每一个加载进来的布局销毁掉

((ViewPager)container).removeView(list.get(position));

}

@Override

public CharSequence getPageTitle(int position) {

// TODO Auto-generated method stub

return titleList.get(position);

}

@Override

public Object instantiateItem(ViewGroup container, int position) {

// TODO Auto-generated method stub

((ViewPager)container).addView(list.get(position));

return list.get(position);

}

@Override

public void setPrimaryItem(ViewGroup container, int position,

Object object) {

}

}

class MyPageListener implements OnPageChangeListener

{ @Override

public void onPageScrollStateChanged(int arg0) {

// TODO Auto-generated method stub

}

@Override

public void onPageScrolled(int position, float arg1, int arg2) {

// TODO Auto-generated method stub

Log.i("数据", "data by day");

//查询所选页的运动信息

queryDataByDay();

showChart(Integer.parseInt(steps));

TextView stepValue=(TextView) list.get(position).findViewById(R.id.step_value);

TextView distanceValue=(TextView) list.get(position).findViewById(R.id.distance_value);

TextView totalTimeValue=(TextView) list.get(position).findViewById(R.id.totalTime_value);

TextView caloriesValue=(TextView) list.get(position).findViewById(R.id.calories_value);

stepValue.setText(steps);

distanceValue.setText(distances);

totalTimeValue.setText(totalTime);

caloriesValue.setText(calories);

}

@Override

public void onPageSelected(int arg0) {

// TODO Auto-generated method stub

}

}

public void queryDataByDay()

{

Cursor cursor = null;

Date nowTime=new Date();

SimpleDateFormat matter=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss(EE)");

String formatTime=matter.format(nowTime);

String[] date=formatTime.split(" ");

try {

PedometerDAO dao=new PedometerDAO(this);

dao.open();

Log.i("今天", date[0]);

cursor = dao.queryDataByDay(date[0]);

while(cursor.moveToNext())

{

steps=cursor.getString(cursor.getColumnIndex("steps")); distances=cursor.getString(cursor.getColumnIndex("distances")); totalTime=cursor.getString(cursor.getColumnIndex("totalTime")); calories=cursor.getString(cursor.getColumnIndex("calories"));

dao.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

public void showChart(int steps)

{

layout = (LinearLayout) findViewById(R.id.configLayout);

bar = new ConfigurationView(this, steps*100/200, "步数/%", true);

layout.addView(bar, new LayoutParams(50, LayoutParams.FILL_PARENT));

}

}

附录5:柱状图的动画显示

public class ConfigurationView extends View {

private Paint paint;

private Paint font_Paint;

// 数值显示的偏移量

private int numWidth = 9;

// 起始高度为 最大高度减去 80 【注意这里的高度是反的,也就是说,y轴是逐渐减少的】

private float startHeight = ChartActivity.HEIGHT - 80;

private float endHeight = startHeight;

// 柱状图的宽度

private int viewWidth = 20;

// 组态图的高度 【显示的数值,100 为 100%】

private int maxSize = 43;

private int indexSize = 0;

// 要显示的模式 【类型,比如:℃和百分比等】

private String displayMode = "%";

// 模式

private boolean mode = false;

// 线程控制

private boolean display = true;

// 是否开启动画效果

private boolean animMode = true;

public ConfigurationView(Context context, int maxSize, String displayMode) {

super(context);

this.maxSize = maxSize;

this.displayMode = displayMode;

init();

}

public ConfigurationView(Context context, int maxSize, String displayMode,

boolean mode) {

super(context);

this.maxSize = maxSize;

this.displayMode = displayMode;

this.mode = mode;

init();

}

public ConfigurationView(Context context, int maxSize, String displayMode,

boolean mode, boolean animMode) {

super(context);

this.maxSize = maxSize;

this.displayMode = displayMode;

this.mode = mode;

this.animMode = animMode;

init();

}

// 绘制界面

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.drawRect(10, endHeight, 10 + viewWidth, startHeight, paint);

if (!display) {

// 这段if语句可以放在初始化中,这个就交给大家吧。

if (!mode && indexSize >= 50) {

paint.setARGB(255, 200, 200, 60);

if (!mode && indexSize >= 80) {

paint.setARGB(255,

(indexSize < 100) ? (110 + indexSize + 45) : 255,

(indexSize < 100) ? 210 - (indexSize + 45) : 0, 20);

}

} else if (mode && indexSize <= 50) {

paint.setARGB(255, 200, 200, 60);

if (mode && indexSize <= 30) {

paint.setARGB(255, 255 - indexSize, indexSize, 20);

}

}

canvas.drawRect(10, endHeight, 10 + viewWidth, startHeight, paint);

paint.setARGB(255, 99, 66, 0);

canvas.drawText("" + indexSize, numWidth, endHeight - 5, paint);

paint.setARGB(255, 110, 210, 60);

}

canvas.drawText(displayMode, 0, startHeight + 15, font_Paint);

}

// 初始化

private void init() {

// 数值初始化

paint = new Paint();

paint.setARGB(255, 110, 210, 20);

font_Paint = new Paint();

font_Paint.setARGB(255, 66, 66, 66);

// 设置顶端数值显示的偏移量,数值越小,偏移量越大

numWidth = 9;

if (maxSize < 10) {

numWidth = 15;

} else if (maxSize < 100) {

numWidth = 12;

}

if (animMode) {

// 启动一个线程来实现柱状图缓慢增高

thread.start();

} else {

// 不启用动画效果,则直接赋值进行绘制

display = false;

indexSize = maxSize;

endHeight = startHeight - (float) (maxSize * 1.5);

invalidate();

}

}

private Handler handler = new Handler() {

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

// 通过endHeight >= 20将柱状图的高度控制在150以内,这样刚好循环一百次到顶部

if (msg.what == 1 && indexSize < maxSize && endHeight >= 20) {

endHeight -= 1.5;

indexSize += 1;

} else {

display = false;

}

invalidate();

}

};

private Thread thread = new Thread() {

@Override

public void run() {

while (!Thread.currentThread().isInterrupted() && display) {

Message msg = new Message();

msg.what = 1;

handler.sendMessage(msg);

try {

// 每隔15毫秒触发,尽量使升高的效果看起来平滑

Thread.sleep(15);

} catch (InterruptedException e) {

System.err.println("InterruptedException!线程关闭");

this.interrupt();

}

}

}

};

}

附录6:设置参数配置信息

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

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

<PreferenceCategory

android:title="@string/steps_settings_title">

<EditTextPreference

android:key="height"

android:title="@string/height_setting"

android:summary="@string/height_setting_details"

android:dialogTitle="@string/height_setting_title"

android:defaultValue="160" />

<EditTextPreference

android:key="body_weight"

android:title="@string/body_weight_setting"

android:summary="@string/body_weight_setting_details"

android:dialogTitle="@string/body_weight_setting_title"

android:defaultValue="50" />

<EditTextPreference

android:key="step_target"

android:title="@string/step_target_setting"

android:summary="@string/step_target_setting_details"

android:dialogTitle="@string/step_target_setting_title"

android:defaultValue="10000" />

PreferenceCategory>

PreferenceScreen>

附录7:退出系统

//选择菜单项“退出”弹出提示框

public boolean onOptionsItemSelected(MenuItem item){

AlertDialog.Builder builder=new AlertDialog.Builder(this);

builder.setMessage("您确定要退出吗?")

.setCancelable(false)

.setPositiveButton("确定",new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int id) {

// TODO Auto-generated method stub

MainActivity.this.finish();

}

})

.setNegativeButton("取消", new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int id) {

// TODO Auto-generated method stub

dialog.cancel();

}

});

AlertDialog alert=builder.create();//创建对话

alert.show();//显示对话

return true;

}

随着夏天的日益临近,我的大学生涯即将结束,首先,我深深地感谢叶老师的悉心指导,叶老师从论文的选题、分析和设计等方面都十分耐心的给予我意见,细致认真的查看我的论文并发现其中的问题,并且指导我进行修改。叶老师宽广无私的胸怀、严谨求实的工作态度、诲人不倦的教育风范,都使我受益匪浅,也是我终身学习的榜样。在此我谨向敬爱的导师致以最诚挚的谢意。

还有同学们的帮助,在我遇到问题和不解的时候能够不遗余力地帮助我,在此,我对他们表示诚挚的谢意。

我要感谢我的父母和家人,是他们长期以来无私的支持、关心、教诲和鼓励使我能够顺利的进行我的学业,是他们给了我前进的动力,让我朝着更高的目标发展。

最后,谨向所有关心和帮助过我的老师、同学、家人表示我最诚挚的谢意。

本文来源:https://www.2haoxitong.net/k/doc/f7778dcf02d276a200292e97.html

《android手机计步软件的设计与实现.doc》
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档

文档为doc格式