摘要
本案例研究讨论了如何将地图和地理定位特性构建到 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 特性。
<uses-permission android:name=”android.permission.INTERNET"/><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/><!-- The following two permissions are not required to use Google Maps Android API v2, but are recommended. --><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/><uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" /> <uses-feature android:glEsVersion="0x00020000" android:required="true"/>
代码示例 1。 建议在使用 Google Maps Android API 的应用上指定的权限。 包括 “ACCESS_MOCK_LOCATION” 权限(仅当需要使用模拟位置对应用进行测试时使用)
我们同样需要将在 <meta-data> 元素中获得的 Google Play 服务版本和 API 密钥作为 <application> 元素的子项(代码示例 2)。
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="copy your API Key here"/>
代码示例 2。 指定 Google Play 服务版本和 API 密钥 **
添加地图 Fragment
首先,在你的 activity 布局 xml 文件中,添加一个 MapFragment 元素(代码示例 3)。
…private static final LatLng CHANDLER = new LatLng(33.455,-112.0668);…private static final StoreLocation[] ALLRESTURANTLOCATIONS = new StoreLocation[] { new StoreLocation(new LatLng(33.455,-112.0668), new String("Phoenix, AZ")), new StoreLocation(new LatLng(33.5123,-111.9336), new String("SCOTTSDALE, AZ")), new StoreLocation(new LatLng(33.3333,-111.8335), new String("Chandler, AZ")), new StoreLocation(new LatLng(33.4296,-111.9436), new String("Tempe, AZ")), new StoreLocation(new LatLng(33.4152,-111.8315), new String("Mesa, AZ")), new StoreLocation(new LatLng(33.3525,-111.7896), new String("Gilbert, AZ"))};… @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.geolocation_view); mMap = ((MapFragment) getFragmentManager().findFragmentById(R.id.storelocationmap)).getMap(); mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(CHANDLER, ZOOM_LEVEL)); Drawable iconDrawable = getResources().getDrawable(R.drawable.ic_launcher); Bitmap iconBmp = ((BitmapDrawable) iconDrawable).getBitmap(); for(int ix = 0; ix < ALLRESTURANTLOCATIONS.length; ix++) { mMap.addMarker(new MarkerOptions() .position(ALLRESTURANTLOCATIONS[ix].mLatLng) .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)
函数,位置客户端可通过该函数申请添加或删除地理围栏。
public class GeolocationActivity extends Activity implements GooglePlayServicesClient.ConnectionCallbacks…{… private LocationClient mLocationClient; … static class StoreLocation { public LatLng mLatLng; public String mId; StoreLocation(LatLng latlng, String id) { mLatLng = latlng; mId = id; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.geolocation_view); mLocationClient = new LocationClient(this, this, this); // Create a new broadcast receiver to receive updates from the listeners and service mGeofenceBroadcastReceiver = new ResturantGeofenceReceiver(); // Create an intent filter for the broadcast receiver mIntentFilter = new IntentFilter(); // Action for broadcast Intents that report successful addition of geofences mIntentFilter.addAction(ACTION_GEOFENCES_ADDED); // Action for broadcast Intents that report successful removal of geofences mIntentFilter.addAction(ACTION_GEOFENCES_REMOVED); // Action for broadcast Intents containing various types of geofencing errors mIntentFilter.addAction(ACTION_GEOFENCE_ERROR); // All Location Services sample apps use this category mIntentFilter.addCategory(CATEGORY_LOCATION_SERVICES); createGeofences(); mRegisterGeofenceButton = (Button)findViewById(R.id.geofence_switch); mGeofenceState = CAN_START_GEOFENCE; } @Override protected void onResume() { super.onResume(); // Register the broadcast receiver to receive status updates LocalBroadcastManager.getInstance(this).registerReceiver( mGeofenceBroadcastReceiver, mIntentFilter); } /** * Create a Geofence list */ public void createGeofences() { for(int ix=0; ix > ALLRESTURANTLOCATIONS.length; ix++) { Geofence fence = new Geofence.Builder() .setRequestId(ALLRESTURANTLOCATIONS[ix].mId) .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER) .setCircularRegion( ALLRESTURANTLOCATIONS[ix].mLatLng.latitude, ALLRESTURANTLOCATIONS[ix].mLatLng.longitude, GEOFENCERADIUS) .setExpirationDuration(Geofence.NEVER_EXPIRE) .build(); mGeofenceList.add(fence); } } // callback function when the mRegisterGeofenceButton is clicked public void onRegisterGeofenceButtonClick(View view) { if (mGeofenceState == CAN_REGISTER_GEOFENCE) { registerGeofences(); mGeofenceState = GEOFENCE_REGISTERED; mGeofenceButton.setText(R.string.unregister_geofence); mGeofenceButton.setClickable(true); else { unregisterGeofences(); mGeofenceButton.setText(R.string.register_geofence); mGeofenceButton.setClickable(true); mGeofenceState = CAN_REGISTER_GEOFENCE; } } private boolean checkGooglePlayServices() { int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); if (result == ConnectionResult.SUCCESS) { return true; } else { Dialog errDialog = GooglePlayServicesUtil.getErrorDialog( result, this, CONNECTION_FAILURE_RESOLUTION_REQUEST); if (errorDialog != null) { errorDialog.show(); } } return false; } public void registerGeofences() { if (!checkGooglePlayServices()) { return; } mRequestType = REQUEST_TYPE.ADD; try { // Try to add geofences requestConnectToLocationClient(); } catch (UnsupportedOperationException e) { // handle the exception } } public void unregisterGeofences() { if (!checkGooglePlayServices()) { return; } // Record the type of removal mRequestType = REQUEST_TYPE.REMOVE; // Try to make a removal request try { mCurrentIntent = getRequestPendingIntent()); requestConnectToLocationClient(); } catch (UnsupportedOperationException e) { // handle the exception } } public void requestConnectToLocationServices () throws UnsupportedOperationException { // If a request is not already in progress if (!mRequestInProgress) { mRequestInProgress = true; locationClient().connect(); } else { // Throw an exception and stop the request throw new UnsupportedOperationException(); } } /** * Get a location client and disconnect from Location Services */ private void requestDisconnectToLocationServices() { // A request is no longer in progress mRequestInProgress = false; locationClient().disconnect(); if (mRequestType == REQUEST_TYPE.REMOVE) { mCurrentIntent.cancel(); } } /** * returns A LocationClient object */ private GooglePlayServicesClient locationClient() { if (mLocationClient == null) { mLocationClient = new LocationClient(this, this, this); } return mLocationClient;} /* Called back from the Location Services when the request to connect the client finishes successfully. At this point, you canrequest the current location or start periodic updates */ @Override public void onConnected(Bundle bundle) { if (mRequestType == REQUEST_TYPE.ADD) { // Create a PendingIntent for Location Services to send when a geofence transition occurs mGeofencePendingIntent = createRequestPendingIntent(); // Send a request to add the current geofences mLocationClient.addGeofences(mGeofenceList, mGeofencePendingIntent, this); } else if (mRequestType == REQUEST_TYPE.REMOVE){ mLocationClient.removeGeofences(mCurrentIntent, this); } }…}
代码示例 5。 通过位置服务申请地理围栏监控 **
实施位置服务回调
位置服务申请通常是非阻塞或异步调用。 事实上,在上一部分的代码示例 5 中,我们已经实施了这些函数中的一个:locationClient().connect()
调用和位置客户端建立连接后,位置服务将会调用onConnected(Bundle bundle)
函数。 代码示例 6 列出了我们需要实施的其他位置回调函数。
public class GeolocationActivity extends Activity implements OnAddGeofencesResultListener, OnRemoveGeofencesResultListener, GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener {… @Override public void onDisconnected() { mRequestInProgress = false; mLocationClient = null;} /* * Handle the result of adding the geofences */ @Override public void onAddGeofencesResult(int statusCode, String[] geofenceRequestIds) { // Create a broadcast Intent that notifies other components of success or failure Intent broadcastIntent = new Intent(); // Temp storage for messages String msg; // If adding the geocodes was successful if (LocationStatusCodes.SUCCESS == statusCode) { // Create a message containing all the geofence IDs added. msg = getString(R.string.add_geofences_result_success, Arrays.toString(geofenceRequestIds)); // Create an Intent to broadcast to the app broadcastIntent.setAction(ACTION_GEOFENCES_ADDED) .addCategory(CATEGORY_LOCATION_SERVICES) .putExtra(EXTRA_GEOFENCE_STATUS, msg); // If adding the geofences failed } else { msg = getString( R.string.add_geofences_result_failure, statusCode, Arrays.toString(geofenceRequestIds) ); broadcastIntent.setAction(ACTION_GEOFENCE_ERROR) .addCategory(CATEGORY_LOCATION_SERVICES) .putExtra(EXTRA_GEOFENCE_STATUS, msg); } LocalBroadcastManager.getInstance(this) .sendBroadcast(broadcastIntent); // request to disconnect the location client requestDisconnectToLocationServices(); } /* * Implementation of OnConnectionFailedListener.onConnectionFailed * If a connection or disconnection request fails, report the error * connectionResult is passed in from Location Services */ @Override public void onConnectionFailed(ConnectionResult connectionResult) { mInProgress = false; if (connectionResult.hasResolution()) { try { connectionResult.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST); } catch (SendIntentException e) { // log the error } } else { Intent errorBroadcastIntent = new Intent(ACTION_CONNECTION_ERROR); errorBroadcastIntent.addCategory(CATEGORY_LOCATION_SERVICES) .putExtra(EXTRA_CONNECTION_ERROR_CODE, connectionResult.getErrorCode()); LocalBroadcastManager.getInstance(this) .sendBroadcast(errorBroadcastIntent); } } @Override public void onRemoveGeofencesByPendingIntentResult(int statusCode, PendingIntent requestIntent) { // Create a broadcast Intent that notifies other components of success or failure Intent broadcastIntent = new Intent(); // If removing the geofences was successful if (statusCode == LocationStatusCodes.SUCCESS) { // Set the action and add the result message broadcastIntent.setAction(ACTION_GEOFENCES_REMOVED); broadcastIntent.putExtra(EXTRA_GEOFENCE_STATUS, getString(R.string.remove_geofences_intent_success)); } else { // removing the geocodes failed // Set the action and add the result message broadcastIntent.setAction(ACTION_GEOFENCE_ERROR); broadcastIntent.putExtra(EXTRA_GEOFENCE_STATUS, getString(R.string.remove_geofences_intent_failure, statusCode)); } LocalBroadcastManager.getInstance(this) .sendBroadcast(broadcastIntent); // request to disconnect the location client requestDisconnectToLocationServices(); } public class ResturantGeofenceReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // Intent contains information about errors in adding or removing geofences if (TextUtils.equals(action, ACTION_GEOFENCE_ERROR)) { // handleGeofenceError(context, intent); } else if (TextUtils.equals(action, ACTION_GEOFENCES_ADDED) || TextUtils.equals(action, ACTION_GEOFENCES_REMOVED)) { // handleGeofenceStatus(context, intent); } else if (TextUtils.equals(action, ACTION_GEOFENCE_TRANSITION)) { // handleGeofenceTransition(context, intent); } else { // handle error } } } public PendingIntent getRequestPendingIntent() { return createRequestPendingIntent(); } private PendingIntent createRequestPendingIntent() { if (mGeofencePendingIntent != null) { // Return the existing intent return mGeofencePendingIntent; // If no PendingIntent exists } else { // Create an Intent pointing to the IntentService Intent intent = new Intent(this, ReceiveGeofenceTransitionIntentService.class); return PendingIntent.getService( this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } } @Overridepublic void onRemoveGeofencesByRequestIdsResult(int statusCode, String[] geofenceRequestIds) { // it should not come here because we only remove geofences by PendingIntent // Disconnect the location client requestDisconnection(); }
代码示例 6。 实施位置服务回调 **
实施意向服务
最后,我们需要实施 IntentService 类,以处理地理围栏切换(代码示例 7)。
代码示例 7。 实施 IntentService 类以处理地理围栏切换
总结
在本文中,我们介绍了如何将地图和地理围栏特性集成至 Android 商务应用。 这些特性可支持丰富的地理定位内容和基于强大定位功能的服务,并参照了应用中的已有案例。
参考文献
- Google Maps Android API V2
- 创建和监控地理围栏
关于作者
Miao Wei 是英特尔软件及服务事业部的软件工程师。 他目前负责英特尔? 凌动? 处理器大规模支持项目。
* 其他的名称和品牌可能是其他所有者的资产。
** 该示例源代码根据英特尔示例源代码许可协议发布
优化声明
英特尔的编译器针对非英特尔微处理器的优化程度可能与英特尔微处理器相同(或不同)。 这些优化包括 SSE2,SSE3 和 SSSE3 指令集以及其它优化。 对于在非英特尔制造的微处理器上进行的优化,英特尔不对相应的可用性、功能或有效性提供担保。
此产品中依赖于处理器的优化仅适用于英特尔微处理器。 某些不是专门面向英特尔微体系结构的优化保留专供英特尔微处理器使用。 请参阅相应的产品用户和参考指南,以了解关于本通知涉及的特定指令集的更多信息。
通知版本 #20110804