摘要
本案例研究讨论了如何将地图和地理定位特性构建到 Android* 商务应用中,包括在 Google Maps* 上覆盖商店位置,以及在设备进入商店地理围栏邻近区域时借助地理围栏通知用户。
目录
- 摘要
- 概述
- 在 Google Maps 上显示商店位置
- Google Maps Android API v2
- 在应用清单中指定应用设置
- 添加地图 Fragment
- 发送地理围栏通知
- 注册和取消注册地理围栏
- 实施位置服务回调
- 实施意向服务
- 总结
- 参考文献
- 作者介绍
概述
在本案例研究中,我们将会把地图和地理定位功能集成到基于 Android 平板电脑的餐馆商务应用中(图 1)。 用户可以从主菜单项“位置和地理围栏”访问地理定位功能(图 2)。

图 1 餐馆应用主界面

图 2 浮出控件菜单项
在 Google Maps 上显示商店位置
对于一款商务应用而言,显示商店在地图上的位置对用户非常直观和有用(图 3)。 Google Maps Android API 可提供一种简单的方式将 Google Maps 整合至您的 Android 应用。
Google Maps Android API v2
Google Maps Android API v2 是 Google Play 服务 APK 的一部分。 为了创建使用 Google Maps Android API v2 的 Android 应用,需要下载并配置 Google Play 服务 SDK,获取 API 密钥并在应用的 AndroidManifest.xml 文件中添加所需的设置来对开发环境进行设置。
首先,你需要按照以下网站上的说明来设置 Google Play 服务 SDK:http://developer.android.com/google/play-services/setup.html。
然后,你需要从谷歌开发人员控制台(Google Developers Console)上对你的项目进行注册并获取一个 API 密钥:https://console.developers.google.com/project。 你需要在 AndroidManifest.xml 文件中添加 API 密钥。

图 3 餐馆应用在谷歌地图上显示商店的位置。
在应用清单中指定应用设置
为了使用 Google Maps Android API v2,需要将一些权限和特性指定为 <manifest> 元素的子项(代码示例 1)。 其中包括网络连接、外部存储和位置访问的一些必要权限。 此外,为了使用 Google Maps Android API,需要使用 OpenGL ES 版本 2 特性。
01 | < uses-permission android:name=”android.permission.INTERNET"/> |
02 | < uses-permission android:name = "android.permission.ACCESS_NETWORK_STATE" /> |
03 | < uses-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE" /> |
04 | < uses-permission android:name = "com.google.android.providers.gsf.permission.READ_GSERVICES" /> |
07 | < uses-permission android:name = "android.permission.ACCESS_COARSE_LOCATION" /> |
08 | < uses-permission android:name = "android.permission.ACCESS_FINE_LOCATION" /> |
09 | < uses-permission android:name = "android.permission.ACCESS_MOCK_LOCATION" /> |
12 | android:glEsVersion = "0x00020000" |
13 | android:required = "true" /> |
代码示例 1。 建议在使用 Google Maps Android API 的应用上指定的权限。 包括 “ACCESS_MOCK_LOCATION” 权限(仅当需要使用模拟位置对应用进行测试时使用)
我们同样需要将在 <meta-data> 元素中获得的 Google Play 服务版本和 API 密钥作为 <application> 元素的子项(代码示例 2)。
2 | android:name = "com.google.android.gms.version" |
3 | android:value = "@integer/google_play_services_version" /> |
6 | android:name = "com.google.android.maps.v2.API_KEY" |
7 | android:value = "copy your API Key here" /> |
代码示例 2。 指定 Google Play 服务版本和 API 密钥 **
添加地图 Fragment
首先,在你的 activity 布局 xml 文件中,添加一个 MapFragment 元素(代码示例 3)。
2 | android:id = "@+id/storelocationmap" |
3 | android:layout_width = "fill_parent" |
4 | android:layout_height = "fill_parent" |
5 | android:name = "com.google.android.gms.maps.MapFragment" |
代码示例 3。 在 Activity 布局中添加 MapFragment **
在你的 activity 类中,您可以检索 Google Maps MapFragment 对象并在每个商店位置处绘制商店图标。
02 | private static final LatLng CHANDLER = new LatLng( 33.455 ,- 112.0668 ); |
04 | private static final StoreLocation[] ALLRESTURANTLOCATIONS = new StoreLocation[] { |
05 | new StoreLocation( new LatLng( 33.455 ,- 112.0668 ), new String( "Phoenix, AZ" )), |
06 | new StoreLocation( new LatLng( 33.5123 ,- 111.9336 ), new String( "SCOTTSDALE, AZ" )), |
07 | new StoreLocation( new LatLng( 33.3333 ,- 111.8335 ), new String( "Chandler, AZ" )), |
08 | new StoreLocation( new LatLng( 33.4296 ,- 111.9436 ), new String( "Tempe, AZ" )), |
09 | new StoreLocation( new LatLng( 33.4152 ,- 111.8315 ), new String( "Mesa, AZ" )), |
10 | new StoreLocation( new LatLng( 33.3525 ,- 111.7896 ), new String( "Gilbert, AZ" )) |
14 | protected void onCreate(Bundle savedInstanceState) { |
15 | super .onCreate(savedInstanceState); |
16 | setContentView(R.layout.geolocation_view); |
18 | mMap = ((MapFragment) getFragmentManager().findFragmentById(R.id.storelocationmap)).getMap(); |
19 | mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(CHANDLER, ZOOM_LEVEL)); |
20 | Drawable iconDrawable = getResources().getDrawable(R.drawable.ic_launcher); |
21 | Bitmap iconBmp = ((BitmapDrawable) iconDrawable).getBitmap(); |
22 | for ( int ix = 0 ; ix < ALLRESTURANTLOCATIONS.length; ix++) { |
23 | mMap.addMarker( new MarkerOptions() |
24 | .position(ALLRESTURANTLOCATIONS[ix].mLatLng) |
25 | .icon(BitmapDescriptorFactory.fromBitmap(iconBmp))); |
代码示例 4。 在 Google Maps 上绘制商店图标 **
地理围栏是一个圆形区域,该区域由一点的经纬度坐标和半径决定。 Android 应用可使用 Android 位置服务注册地理围栏。 Android 应用还可指定地理围栏的使用期限。 无论地理围栏何时切换,例如,当 Android 设备进入注册的地理围栏或从其中退出时,Android 位置服务都会即时通知 Android 应用。
在我们的餐馆应用中,我们能够为每个商店位置定义地理围栏。 当设备进入商店附近时,应用将会发送一条通知,如“您已进入最喜爱的餐馆的附近!” (图 4)。

图 4 我们根据兴趣点和半径将地理围栏定义为一个圆形范围。
注册和取消注册地理围栏
在 Android SDK 中,位置服务也是 Google Play 服务 APK 的一部分,位于 “Extras” 目录下。
如要请求地理围栏监控,首先我们需要在应用的清单中指定 "ACCESS_FINE_LOCATION" 权限,这一点我们在上一部分已经完成。
此外,我们还需要查看 Google Play 服务的可用性(代码示例 5 中的 checkGooglePlayServices()
方法)。 locationClient().connect()
调用与位置客户端成功建立连接后,位置服务将会调用 onConnected(Bundle bundle)
函数,位置客户端可通过该函数申请添加或删除地理围栏。
001 | public class GeolocationActivity extends Activity implements |
002 | GooglePlayServicesClient.ConnectionCallbacks |
006 | private LocationClient mLocationClient; |
010 | static class StoreLocation { |
011 | public LatLng mLatLng; |
013 | StoreLocation(LatLng latlng, String id) { |
020 | protected void onCreate(Bundle savedInstanceState) { |
021 | super .onCreate(savedInstanceState); |
022 | setContentView(R.layout.geolocation_view); |
024 | mLocationClient = new LocationClient( this , this , this ); |
027 | mGeofenceBroadcastReceiver = new ResturantGeofenceReceiver(); |
030 | mIntentFilter = new IntentFilter(); |
033 | mIntentFilter.addAction(ACTION_GEOFENCES_ADDED); |
036 | mIntentFilter.addAction(ACTION_GEOFENCES_REMOVED); |
039 | mIntentFilter.addAction(ACTION_GEOFENCE_ERROR); |
042 | mIntentFilter.addCategory(CATEGORY_LOCATION_SERVICES); |
046 | mRegisterGeofenceButton = (Button)findViewById(R.id.geofence_switch); |
047 | mGeofenceState = CAN_START_GEOFENCE; |
052 | protected void onResume() { |
055 | LocalBroadcastManager.getInstance( this ).registerReceiver( |
056 | mGeofenceBroadcastReceiver, mIntentFilter); |
060 | * Create a Geofence list |
062 | public void createGeofences() { |
063 | for ( int ix= 0 ; ix > ALLRESTURANTLOCATIONS.length; ix++) { |
064 | Geofence fence = new Geofence.Builder() |
065 | .setRequestId(ALLRESTURANTLOCATIONS[ix].mId) |
066 | .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER) |
068 | ALLRESTURANTLOCATIONS[ix].mLatLng.latitude, ALLRESTURANTLOCATIONS[ix].mLatLng.longitude, GEOFENCERADIUS) |
069 | .setExpirationDuration(Geofence.NEVER_EXPIRE) |
071 | mGeofenceList.add(fence); |
076 | public void onRegisterGeofenceButtonClick(View view) { |
077 | if (mGeofenceState == CAN_REGISTER_GEOFENCE) { |
079 | mGeofenceState = GEOFENCE_REGISTERED; |
080 | mGeofenceButton.setText(R.string.unregister_geofence); |
081 | mGeofenceButton.setClickable( true ); |
083 | unregisterGeofences(); |
084 | mGeofenceButton.setText(R.string.register_geofence); |
085 | mGeofenceButton.setClickable( true ); |
086 | mGeofenceState = CAN_REGISTER_GEOFENCE; |
090 | private boolean checkGooglePlayServices() { |
091 | int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable( this ); |
092 | if (result == ConnectionResult.SUCCESS) { |
096 | Dialog errDialog = GooglePlayServicesUtil.getErrorDialog( |
099 | CONNECTION_FAILURE_RESOLUTION_REQUEST); |
101 | if (errorDialog != null ) { |
109 | public void registerGeofences() { |
111 | if (!checkGooglePlayServices()) { |
115 | mRequestType = REQUEST_TYPE.ADD; |
119 | requestConnectToLocationClient(); |
120 | } catch (UnsupportedOperationException e) { |
126 | public void unregisterGeofences() { |
128 | if (!checkGooglePlayServices()) { |
133 | mRequestType = REQUEST_TYPE.REMOVE; |
137 | mCurrentIntent = getRequestPendingIntent()); |
138 | requestConnectToLocationClient(); |
140 | } catch (UnsupportedOperationException e) { |
145 | public void requestConnectToLocationServices () throws UnsupportedOperationException { |
147 | if (!mRequestInProgress) { |
148 | mRequestInProgress = true ; |
150 | locationClient().connect(); |
154 | throw new UnsupportedOperationException(); |
160 | * Get a location client and disconnect from Location Services |
162 | private void requestDisconnectToLocationServices() { |
165 | mRequestInProgress = false ; |
167 | locationClient().disconnect(); |
169 | if (mRequestType == REQUEST_TYPE.REMOVE) { |
170 | mCurrentIntent.cancel(); |
176 | * returns A LocationClient object |
178 | private GooglePlayServicesClient locationClient() { |
179 | if (mLocationClient == null ) { |
181 | mLocationClient = new LocationClient( this , this , this ); |
183 | return mLocationClient; |
192 | public void onConnected(Bundle bundle) { |
193 | if (mRequestType == REQUEST_TYPE.ADD) { |
195 | mGeofencePendingIntent = createRequestPendingIntent(); |
198 | mLocationClient.addGeofences(mGeofenceList, mGeofencePendingIntent, this ); |
201 | else if (mRequestType == REQUEST_TYPE.REMOVE){ |
203 | mLocationClient.removeGeofences(mCurrentIntent, this ); |
代码示例 5。 通过位置服务申请地理围栏监控 **
实施位置服务回调
位置服务申请通常是非阻塞或异步调用。 事实上,在上一部分的代码示例 5 中,我们已经实施了这些函数中的一个:locationClient().connect()
调用和位置客户端建立连接后,位置服务将会调用 onConnected(Bundle bundle)
函数。 代码示例 6 列出了我们需要实施的其他位置回调函数。
001 | public class GeolocationActivity extends Activity implements |
002 | OnAddGeofencesResultListener, |
003 | OnRemoveGeofencesResultListener, |
004 | GooglePlayServicesClient.ConnectionCallbacks, |
005 | GooglePlayServicesClient.OnConnectionFailedListener { |
010 | public void onDisconnected() { |
011 | mRequestInProgress = false ; |
012 | mLocationClient = null ; |
065 | public void onConnectionFailed(ConnectionResult connectionResult) { |
067 | if (connectionResult.hasResolution()) { |
070 | connectionResult.startResolutionForResult( this , |
071 | CONNECTION_FAILURE_RESOLUTION_REQUEST); |
073 | catch (SendIntentException e) { |
078 | Intent errorBroadcastIntent = new Intent(ACTION_CONNECTION_ERROR); |
079 | errorBroadcastIntent.addCategory(CATEGORY_LOCATION_SERVICES) |
080 | .putExtra(EXTRA_CONNECTION_ERROR_CODE, |
081 | connectionResult.getErrorCode()); |
082 | LocalBroadcastManager.getInstance( this ) |
083 | .sendBroadcast(errorBroadcastIntent); |
088 | public void onRemoveGeofencesByPendingIntentResult( int statusCode, |
089 | PendingIntent requestIntent) { |
092 | Intent broadcastIntent = new Intent(); |
095 | if (statusCode == LocationStatusCodes.SUCCESS) { |
098 | broadcastIntent.setAction(ACTION_GEOFENCES_REMOVED); |
099 | broadcastIntent.putExtra(EXTRA_GEOFENCE_STATUS, |
100 | getString(R.string.remove_geofences_intent_success)); |
108 | broadcastIntent.setAction(ACTION_GEOFENCE_ERROR); |
109 | broadcastIntent.putExtra(EXTRA_GEOFENCE_STATUS, |
110 | getString(R.string.remove_geofences_intent_failure, |
113 | LocalBroadcastManager.getInstance( this ) |
114 | .sendBroadcast(broadcastIntent); |
117 | requestDisconnectToLocationServices(); |
121 | public class ResturantGeofenceReceiver extends BroadcastReceiver { |
125 | public void onReceive(Context context, Intent intent) { |
126 | String action = intent.getAction(); |
129 | if (TextUtils.equals(action, ACTION_GEOFENCE_ERROR)) { |
132 | else if (TextUtils.equals(action, ACTION_GEOFENCES_ADDED) |
134 | TextUtils.equals(action, ACTION_GEOFENCES_REMOVED)) { |
137 | else if (TextUtils.equals(action, ACTION_GEOFENCE_TRANSITION)) { |
148 | public PendingIntent getRequestPendingIntent() { |
149 | return createRequestPendingIntent(); |
152 | private PendingIntent createRequestPendingIntent() { |
154 | if (mGeofencePendingIntent != null ) { |
157 | return mGeofencePendingIntent; |
163 | Intent intent = new Intent( this , |
164 | ReceiveGeofenceTransitionIntentService. class ); |
166 | return PendingIntent.getService( |
170 | PendingIntent.FLAG_UPDATE_CURRENT); |
176 | public void onRemoveGeofencesByRequestIdsResult( int statusCode, |
177 | String[] geofenceRequestIds) { |
181 | requestDisconnection(); |
代码示例 6。 实施位置服务回调 **
实施意向服务
最后,我们需要实施 IntentService 类,以处理地理围栏切换(代码示例 7)。
01 | public class ReceiveGeofenceTransitionIntentService extends IntentService { |
03 | * Sets an identifier for the service |
05 | public ReceiveGeofenceTransitionIntentService() { |
06 | super ( "ReceiveGeofenceTransitionsIntentService" ); |
10 | protected void onHandleIntent(Intent intent) { |
13 | Intent broadcastIntent = new Intent(); |
16 | broadcastIntent.addCategory(CATEGORY_LOCATION_SERVICES); |
20 | if (LocationClient.hasError(intent)) { |
22 | int errorCode = LocationClient.getErrorCode(intent); |
27 | LocationClient.getGeofenceTransition(intent); |
29 | if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER) || |
30 | (transition == Geofence.GEOFENCE_TRANSITION_EXIT)) { |
代码示例 7。 实施 IntentService 类以处理地理围栏切换
总结
在本文中,我们介绍了如何将地图和地理围栏特性集成至 Android 商务应用。 这些特性可支持丰富的地理定位内容和基于强大定位功能的服务,并参照了应用中的已有案例。
参考文献
关于作者
Miao Wei 是英特尔软件及服务事业部的软件工程师。 他目前负责英特尔® 凌动™ 处理器大规模支持项目。
* 其他的名称和品牌可能是其他所有者的资产。
** 该示例源代码根据英特尔示例源代码许可协议发布
优化声明
英特尔的编译器针对非英特尔微处理器的优化程度可能与英特尔微处理器相同(或不同)。 这些优化包括 SSE2,SSE3 和 SSSE3 指令集以及其它优化。 对于在非英特尔制造的微处理器上进行的优化,英特尔不对相应的可用性、功能或有效性提供担保。
此产品中依赖于处理器的优化仅适用于英特尔微处理器。 某些不是专门面向英特尔微体系结构的优化保留专供英特尔微处理器使用。 请参阅相应的产品用户和参考指南,以了解关于本通知涉及的特定指令集的更多信息。
通知版本 #20110804