摘要
本案例研究讨论了怎样将地图和地理定位特性构建到 Android* 商务应用中。包含在 Google Maps* 上覆盖商店位置,以及在设备进入商店地理围栏邻近区域时借助地理围栏通知用户。
文件夹
概述
在本案例研究中,我们将会把地图和地理定位功能集成到基于 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()
调用与位置client成功建立连接后,位置服务将会调用onConnected(Bundle bundle)
函数,位置client可通过该函数申请加入或删除地理围栏。
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 can
request 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()
调用和位置client建立连接后,位置服务将会调用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);
}
} @Override
public 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 商务应用。
这些特性可支持丰富的地理定位内容和基于强大定位功能的服务,并參照了应用中的已有案例。
參考文献
关于作者
Miao Wei 是英特尔软件及服务事业部的软件project师。
他眼下负责英特尔® 凌动™ 处理器大规模支持项目。
* 其它的名称和品牌可能是其它全部者的资产。
** 该演示样例源码依据英特尔演示样例源码许可协议公布
优化声明
英特尔的编译器针对非英特尔微处理器的优化程度可能与英特尔微处理器同样(或不同)。
这些优化包含 SSE2,SSE3 和 SSSE3 指令集以及其他优化。 对于在非英特尔制造的微处理器上进行的优化,英特尔不正确对应的可用性、功能或有效性提供担保。
此产品中依赖于处理器的优化仅适用于英特尔微处理器。 某些不是专门面向英特尔微体系结构的优化保留专供英特尔微处理器使用。 请參阅对应的产品用户和參考指南。以了解关于本通知涉及的特定指令集的很多其它信息。
通知版本号 #20110804