第9章 位置服务与地图应用
本章学习目标: 了解位置服务的概念 了解地图密钥的申请方法 掌握获取位置信息的方法 掌握MapView和MapController的使用方法 掌握Google地图覆盖层的使用方法
9.1 位置服务 位置服务 位置服务(Location-Based Services,LBS),又称定位服务或基于位置的服务,融合了GPS定位、移动通信、导航等多种技术,提供了与空间位置相关的综合应用服务 位置服务首先在日本得到商业化的应用 2001年7月,DoCoMo发布了第一款具有三角定位功能的手持设备 2001年12月,KDDI发布第一款具有GPS功能的手机 基于位置的服务发展迅速,已涉及到商务、医疗、工作和生活的各个方面,为用户提供定位、追踪和敏感区域警告等一系列服务
9.1 位置服务 位置服务 Android平台支持提供位置服务的API,在开发过程中主要用到LocationManager和LocationProviders对象 LocationManager可以用来获取当前的位置,追踪设备的移动路线,或设定敏感区域,在进入或离开敏感区域时设备会发出特定警报 LocationProviders是能够提供定位功能的组件集合,集合中的每种组件以不同的技术提供设备的当前位置,区别在于定位的精度、速度和成本等方面
9.1 位置服务 位置服务 提供位置服务,首先需要获得LocationManager对象 获取LocationManager可以通过调用android.app.Activity.getSystemService()函数实现 android.app.Activity.getSystemService()函数代码如下 代码第1行的Context.LOCATION_SERVICE指明获取的服务是位置服务 代码第2行的getSystemService()函数,可以根据服务名称获取Android提供的系统级服务 String serviceString = Context.LOCATION_SERVICE; LocationManager LocationManager = (LocationManager)getSystemService(serviceString);
9.1 位置服务 位置服务 Android支持的系统级服务表 Context类的静态常量 值 返回对象 说明 LOCATION_SERVICE location LocationManager 控制位置等设备的更新 WINDOW_SERVICE window WindowManager 最顶层的窗口管理器 LAYOUT_INFLATER_SERVICE layout_inflater LayoutInflater 将XML资源实例化为View POWER_SERVICE power PowerManager 电源管理 ALARM_SERVICE alarm AlarmManager 在指定时间接受Intent NOTIFICATION_SERVICE notification NotificationManager 后台事件通知 KEYGUARD_SERVICE keyguard KeyguardManager 锁定或解锁键盘 SEARCH_SERVICE search SearchManager 访问系统的搜索服务 VIBRATOR_SERVICE vibrator Vibrator 访问支持振动的硬件 CONNECTIVITY_SERVICE connection ConnectivityManager 网络连接管理 WIFI_SERVICE wifi WifiManager Wi-Fi连接管理 INPUT_METHOD_SERVICE input_method InputMethodManager 输入法管理
9.1 位置服务 位置服务 在获取到LocationManager后,还需要指定LocationManager的定位方法,然后才能够调用LocationManager getLastKnowLocation()方法获取当前位置 LocationManager支持的定位方法有两种 GPS定位:可以提供更加精确的位置信息,但定位速度和质量受到卫星数量和环境情况的影响 网络定位:提供的位置信息精度差,但速度较GPS定位快
LocationManager类的静态常量 9.1 位置服务 位置服务 LocationManager支持定位方法 在指定LocationManager的定位方法后,则可以调用getLastKnowLocation()方法获取当前的位置信息 LocationManager类的静态常量 值 说明 GPS_PROVIDER gps 使用GPS定位,利用卫星提供精确的位置信息,需要android.permissions.ACCESS_FINE_LOCATION用户权限 NETWORK_PROVIDER network 使用网络定位,利用基站或Wi-Fi提供近似的位置信息,需要具有如下权限: android.permission.ACCESS_COARSE_LOCATION或android.permission.ACCESS_FINE_LOCATION.
9.1 位置服务 位置服务 以使用GPS定位为例,获取位置信息的代码如下 代码第2行返回的Location对象中,包含了可以确定位置的信息,如经度、纬度和速度等 通过调用Location中的getLatitude()和getLonggitude()方法可以分别获取位置信息中的纬度和经度,示例代码如下 String provider = LocationManager.GPS_PROVIDER; Location location = locationManager.getLastKnownLocation(provider); double lat = location.getLatitude(); double lng = location.getLongitude();
9.1 位置服务 位置服务 LocationManager提供了一种便捷、高效的位置监视方法requestLocationUpdates(),可以根据位置的距离变化和时间间隔设定产生位置改变事件的条件,这样可以避免因微小的距离变化而产生大量的位置改变事件 LocationManager中设定监听位置变化的代码如下 第1个参数是定位的方法,GPS定位或网络定位 第2个参数是产生位置改变事件的时间间隔,单位为微秒 第3个参数是距离条件,单位是米 第4个参数是回调函数,在满足条件后的位置改变事件的处理函数 locationManager.requestLocationUpdates(provider, 2000, 10, locationListener);
9.1 位置服务 位置服务 代码将产生位置改变事件的条件设定为距离改变10米,时间间隔为2秒 实现locationListener的代码如下 第2行代码onLocationChanged()在设备的位置改变时被调用 LocationListener locationListener = new LocationListener(){ public void onLocationChanged(Location location) { } public void onProviderDisabled(String provider) { public void onProviderEnabled(String provider) { public void onStatusChanged(String provider, int status, Bundle extras) { };
9.1 位置服务 位置服务 第4行的onProviderDisabled()在用户禁用具有定位功能的硬件时被调用 第6行的onProviderEnabled()在用户启用具有定位功能的硬件时被调用 第8行的onStatusChanged()在提供定位功能的硬件的状态改变时被调用,如从不可获取位置信息状态到可以获取位置信息的状态,反之亦然
9.1 位置服务 位置服务 为了使GPS定位功能生效,还需要在AndroidManifest.xml文件中加入用户许可 实现代码如下 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
9.1 位置服务 位置服务 CurrentLocationDemo是一个提供位置服务的基本示例,提供了显示当前位置新的功能,并能够监视设备的位置变化
9.1 位置服务 位置服务 位置服务一般都需要使用设备上的硬件,最理想的调试方式是将程序上传到物理设备上运行,但在没有物理设备的情况下,也可以使用Android模拟器提供的虚拟方式模拟设备的位置变化,调试具有位置服务的应用程序 首先打开DDMS中的模拟器控制,在Location Controls中的Longitude和Latitude部分输入设备当前的经度和纬度,然后点击Send按钮,就将虚拟的位置信息发送到Android模拟器中
9.1 位置服务 位置服务 在程序运行过程中,可以在模拟器控制器中改变经度和纬度坐标值,程序在检测到位置的变化后,会将最新的位置信息显示在界面上 但笔者在1.5版本的Android模拟器中进行调试时,发现模拟器控制器只能成功的将虚拟坐标发送到模拟器中2次,超过2次后模拟器对新发送的虚拟坐标不再响应
9.1 位置服务 位置服务 下面是CurrentLocationDemo示例中LocationBasedServiceDemo.java文件的完整代码 package edu.hrbeu.LocationBasedServiceDemo; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.widget.TextView; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager;
9.1 位置服务 位置服务 public class LocationBasedServiceDemo extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); String serviceString = Context.LOCATION_SERVICE; LocationManager locationManager = (LocationManager)getSystemService(serviceString); String provider = LocationManager.GPS_PROVIDER; Location location = locationManager.getLastKnownLocation(provider); getLocationInfo(location); locationManager.requestLocationUpdates(provider, 2000, 0, locationListener); }
9.1 位置服务 位置服务 private void getLocationInfo(Location location){ String latLongInfo; TextView locationText = (TextView)findViewById(R.id.txtshow); if (location != null){ double lat = location.getLatitude(); double lng = location.getLongitude(); latLongInfo = "Lat: " + lat + "\nLong: " + lng; } else{ latLongInfo = "No location found"; locationText.setText("Your Current Position is:\n" + latLongInfo); private final LocationListener locationListener = new LocationListener(){ @Override public void onLocationChanged(Location location) { getLocationInfo(location);
9.1 位置服务 位置服务 @Override public void onProviderDisabled(String provider) { getLocationInfo(null); } public void onProviderEnabled(String provider) { public void onStatusChanged(String provider, int status, Bundle extras) { };
9.2 Google地图应用 9.2.1 申请地图密钥 首先向Google申请一组经过验证的“地图密钥”(Map API Key),然后使用MapView(com.google.android.maps.MapView)就可以将Google地图嵌入到Android应用程序中,才能正常使用Google的地图服务 “地图密钥”是访问Google地图数据的密钥,无论是模拟器还是在真实设备中需要使用这个密钥
9.2 Google地图应用 9.2.1 申请地图密钥 注册“地图密钥”的第一步是申请一个Google账户,也就是Gmail电子邮箱,申请地址是https://www.google.com/accounts/Login 找到保存Debug证书的keystore的保存位置,并获取证书的MD5散列值 keystore是一个密码保护的文件,用来存储Android提供的用于调试的证书,获取MD5散列值的主要目的是为下一步申请“地图密钥”做准备
9.2 Google地图应用 9.2.1 申请地图密钥 获取证书的保存地址:首先打开Eclipse,通过Window → Preferences打开配置窗体,在Android → Build栏中的Default debug keystore中可以找到
9.2 Google地图应用 9.2.1 申请地图密钥 为了获取Debug证书MD5散列值的,需要打开命令行工具CMD,然后切换到keystore的目录,输入如下命令 如果提示无法找到keytool,可以将<Java SDK>/bin的路径添加到系统的PATH变量中 keytool –list –keystore debug.keystore
9.2 Google地图应用 9.2.1 申请地图密钥 在提示输入keystore密码时,输入缺省密码android,MD5散列将显示在最下方。笔者的MD5散列值为68:76:89:C8:A4:24:61:F9:EA:F3:F7:70:CC:FD:C8:15
9.2 Google地图应用 9.2.1 申请地图密钥 申请“地图密钥”的最后一步是打开申请页面,输入MD5散列值 申请页面的地址是http://code.google.com/intl/zh-CN/android/add-ons/google-apis/maps-api-signup.html
9.2 Google地图应用 9.2.1 申请地图密钥
9.2 Google地图应用 9.2.1 申请地图密钥 输入MD5散列值后,点击Generate API Key按钮,将提示用户输入Google账户,正确输入Google账户后,将产生申请“地图密钥”的获取结果
9.2 Google地图应用 9.2.1 申请地图密钥 笔者获取的“地图密钥”是0mVK8GeO6WUz4S2F94z52CIGSSlvlTwnrE4DsiA,在以后使用到MapView的时候都需要输入这个密钥 但需要注意的是,读者并不能使用这个密钥,读者需要根据自己的Debug证书的MD5散列值,重新到Google网站上申请一个用于调试程序的“地图密钥”
9.2 Google地图应用 9.2.2 使用Google地图 MapView的地图的显示控件,可以设置不同的显示模式,例如卫星模式、街道模式或交通模式 MapController则是MapView的控制器,可以控制MapView的显示中心和缩放级别等功能 下面的内容以GoogleMapDemo为例,说明如何在Android系统中开发Google地图程序 这个示例将在程序内部设置一个坐标点,然后在程序启动时,使用MapView控件在地图上显示这个坐标点的位置
9.2 Google地图应用 9.2.2 使用Google地图 在建立工程时将com.google.android.maps的扩展库添加到工程中,这样就可以使用Google地图的所有功能 添加com.google.android.maps扩展库的方式是在创建工程时,在Build Target项中选择Google APIs 创建工程后,修改/res/layout/main.xml文件,在布局中加入一个MapView控件,并设置刚获取的“地图密钥”
9.2 Google地图应用 9.2.2 使用Google地图 main.xml文件的完整代码如下 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello"/> <com.google.android.maps.MapView android:id="@+id/mapview" android:layout_height="fill_parent" android:enabled="true" android:clickable="true" android:apiKey="0mVK8GeO6WUz4S94z52CIGSSlvlTwnrE4DsiA"/> </LinearLayout>
9.2 Google地图应用 9.2.2 使用Google地图 仅在布局中添加MapView控件,还不能够直接在程序中调用这个控件,还需要将程序本身设置成MapActivity(com.google.android.maps.MapActivity) MapActivity类负责处理显示Google地图所需的生命周期和后台服务管理 下面先给出整个GoogleMapDemo.java文件的完整代码 package edu.hrbeu.GoogleMapDemo; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapActivity; import com.google.android.maps.MapController; import com.google.android.maps.MapView;
9.2 Google地图应用 9.2.2 使用Google地图 import android.os.Bundle; public class GoogleMapDemo extends MapActivity { private MapView mapView; private MapController mapController; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mapView = (MapView)findViewById(R.id.mapview); mapController = mapView.getController(); Double lng = 126.676530486 * 1E6; Double lat = 45.7698895661 * 1E6; GeoPoint point = new GeoPoint(lat.intValue(), lng.intValue());
9.2 Google地图应用 9.2.2 使用Google地图 25. mapController.setCenter(point); mapController.setZoom(11); mapController.animateTo(point); mapView.setSatellite(false) } @Override protected boolean isRouteDisplayed() { // TODO Auto-generated method stub return false;
9.2 Google地图应用 9.2.2 使用Google地图 第20行代码获取了MapController 第22行和第23行代码设定的经度为126.676530486 * 1E6、纬度为45.7698895661 * 1E6的地理坐标点 第26行代码将这个坐标转化为GeoPoint再使用 第26行代码设置MapView的“显示中点” 第27行代码设置放大层级 在第28行代码将MapView显示区域的中心移动到第26行设置的“显示中心” 第30行代码是设定MapView的地图显示模式是否为卫星模式,设置true则为卫星模式,设置false则为普通模式 第34行代码isRouteDisplayed()方法,是用来统计程序是否显示在Google地图中显示路径信息,默认为不显示
9.2 Google地图应用 9.2.2 使用Google地图 由于获取Google地图是需要使用互联网的,所以在运行前还需要在AndroidManifest.xml文件中,添加允许访问互联网的许可 AndroidManifest.xml文件的完整代码如下 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="edu.hrbeu.GoogleMapDemo" android:versionCode="1" android:versionName="1.0">
9.2 Google地图应用 9.2.2 使用Google地图 <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".GoogleMapDemo“ android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <uses-library android:name="com.google.android.maps"></uses-library> </application> <uses-sdk android:minSdkVersion="3" /> <uses-permission android:name="android.permission.INTERNET"/> </manifest>
9.2 Google地图应用 9.2.2 使用Google地图 运行结果如图 地图模式 卫星模式
9.2 Google地图应用 9.2.3 使用Overlay 通过在MapView上添加覆盖层,可以在指定的位置加添加注解、绘制图像或处理进行鼠标事件等 Google地图上可以加入多个覆盖层,所有覆盖层均都在地图图层之上,每个覆盖层均可以对用户的点击事件做出响应 创建覆盖层继承Overlay类的子类,并通过重载draw()方法为指定位置添加注解,重载onTap()方法处理用户的点击操作
9.2 Google地图应用 9.2.3 使用Overlay 下面的代码是创建Overlay的最小代码集合 public class TextOverlay extends Overlay { @Override public void draw(Canvas canvas, MapView mapView, boolean shadow) { if (shadow == false){ } else{ super.draw(canvas, mapView, shadow); public boolean onTap(GeoPoint p, MapView mapView) { return false;
9.2 Google地图应用 9.2.3 使用Overlay 第3行代码中draw()方法,shadow变量是用来区分绘制图层的 false表示在覆盖层上进行绘制 true则表示在隐藏层上进行绘制 第14行代码是onTap()方法的返回值 返回false表示覆盖层不处理点击事件 返回true则表示已经处理了点击事件 在覆盖层绘制图形或文字需要使用“画布”(Canvas)来实现,绘制的位置是屏幕坐标,这就需要将地图上的物理坐标与屏幕坐标进行转换
9.2 Google地图应用 9.2.3 使用Overlay Projection类提供了物理坐标和屏幕坐标的转换功能,可在经度和纬度表示的GeoPoint点和屏幕上Point点进行转换 toPixels()方法将物理坐标转换为屏幕坐标 fromPixels()方法将屏幕坐标转换为物理坐标 两个方法的具体使用方法可以参考下面的代码 Projection projection = mapView.getProjection(); projection.toPixels(geoPoint, point); projection.fromPixels(point.x, point.y);
9.2 Google地图应用 9.2.3 使用Overlay 下面的内容以MapOverlayDemo示例,说明如何在Google地图上添加覆盖层,并在预订的物理坐标上显示提示信息 右图是MapOverlayDemo示例的运行结果
9.2 Google地图应用 9.2.3 使用Overlay TextOverlay类是MapOverlayDemo示例的覆盖层,主要重载了draw()方法,在指定的物理坐标上绘制了标记点和提示文字 TextOverlay.java文件的核心代码如下 public class TextOverlay extends Overlay { private final int mRadius = 5; @Override public void draw(Canvas canvas, MapView mapView, boolean shadow) { Projection projection = mapView.getProjection();
9.2 Google地图应用 9.2.3 使用Overlay if (shadow == false){ Double lng = 126.676530486 * 1E6; Double lat = 45.7698895661 * 1E6; GeoPoint geoPoint = new GeoPoint(lat.intValue(), lng.intValue()); Point point = new Point(); projection.toPixels(geoPoint, point); RectF oval = new RectF(point.x - mRadius, point.y - mRadius, point.x + mRadius, point.y + mRadius); Paint paint = new Paint(); paint.setARGB(250, 250, 0, 0); paint.setAntiAlias(true); paint.setFakeBoldText(true);
9.2 Google地图应用 9.2.3 使用Overlay 第2行代码定义了绘制半径变量mRadius,供定义绘制范围使用 第14行代码使用Projection完成了从物理坐标到屏幕坐标的转换 canvas.drawOval(oval, paint); canvas.drawText("标记点", point.x+2*mRadius, point.y, paint); } super.draw(canvas, mapView, shadow); @Override public boolean onTap(GeoPoint p, MapView mapView) { return false;
9.2 Google地图应用 9.2.3 使用Overlay 第16行代码oval设定标记点的大小 第19行设置了绘制颜色 第20行开启了平滑设置,防止文字出现锯齿 第23行代码绘制了圆形的标记点 第24行代码绘制了提示文字,第2个和第3个参数是绘制屏幕的x坐标和y坐标
9.2 Google地图应用 9.2.3 使用Overlay 建立了覆盖层后,还需要把覆盖层添加到MapView上 MapOverlayDemo.java的核心代码如下 public class MapOverlayDemo extends MapActivity { private MapView mapView; private MapController mapController; private TextOverlay textOverlay; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); 10. 11. mapView = (MapView)findViewById(R.id.mapview); mapController = mapView.getController();
9.2 Google地图应用 9.2.3 使用Overlay Double lng = 126.676530486 * 1E6; Double lat = 45.7698895661 * 1E6; GeoPoint point = new GeoPoint(lat.intValue(), lng.intValue()); mapController.setCenter(point); mapController.setZoom(11); mapController.animateTo(point); textOverlay = new TextOverlay(); List<Overlay> overlays = mapView.getOverlays(); overlays.add(textOverlay); } @Override protected boolean isRouteDisplayed() { return false;
9.2 Google地图应用 9.2.3 使用Overlay 第22行代码实例化了TextOverlay对象 第23行代码通过getOverlays()方法,获取MapView已有的覆盖层 第24行代码使用add()方法将TextOverlay对象对象添加到MapView中
习题: 1.讨论位置服务和地图应用的发展前景。 2.编程实现轨迹追踪软件。每间隔60秒,同时距离移动大于1米的情况下,记录一次位置信息,在Google地图上绘制600秒的行动轨迹。