在 Android* 商务应用中实施地图和地理围栏特性(抄录下来的)

在 Android* 商务应用中实施地图和地理围栏特性

摘要

本案例研究讨论了如何将地图和地理定位特性构建到 Android* 商务应用中,包括在 Google Maps* 上覆盖商店位置,以及在设备进入商店地理围栏邻近区域时借助地理围栏通知用户。

目录

  1. 摘要
  2. 概述
  3. 在 Google Maps 上显示商店位置
    1. Google Maps Android API v2
    2. 在应用清单中指定应用设置
    3. 添加地图 Fragment
  4. 发送地理围栏通知
    1. 注册和取消注册地理围栏
    2. 实施位置服务回调
    3. 实施意向服务
  5. 总结
  6. 参考文献
  7. 作者介绍

概述

在本案例研究中,我们将会把地图和地理定位功能集成到基于 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"/>
05<!-- The following two permissions are not required to use
06     Google Maps Android API v2, but are recommended. -->
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" />
10 
11  <uses-feature
12    android:glEsVersion="0x00020000"
13    android:required="true"/>

代码示例 1。 建议在使用 Google Maps Android API 的应用上指定的权限。 包括 “ACCESS_MOCK_LOCATION” 权限(仅当需要使用模拟位置对应用进行测试时使用)

我们同样需要将在 <meta-data> 元素中获得的 Google Play 服务版本和 API 密钥作为 <application> 元素的子项(代码示例 2)。

    
    
1     <meta-data
2              android:name="com.google.android.gms.version"
3              android:value="@integer/google_play_services_version" />
4         
5      <meta-data
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)。

    
    
1<fragment
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"
6/>

代码示例 3。 在 Activity 布局中添加 MapFragment **

在你的 activity 类中,您可以检索 Google Maps MapFragment 对象并在每个商店位置处绘制商店图标。

    
    
01
02private static final LatLng CHANDLER = new LatLng(33.455,-112.0668);
03
04private 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"))
11};
12…   
13      @Override
14    protected void onCreate(Bundle savedInstanceState) {
15        super.onCreate(savedInstanceState);
16        setContentView(R.layout.geolocation_view);
17         
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)));
26        }
27

代码示例 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) 函数,位置客户端可通过该函数申请添加或删除地理围栏。

    
    
001public class GeolocationActivity extends Activity implements
002        GooglePlayServicesClient.ConnectionCallbacks
003
004{
005…  
006    private LocationClient mLocationClient;
007     
008
009 
010    static class StoreLocation {
011        public LatLng mLatLng;
012        public String mId;
013        StoreLocation(LatLng latlng, String id) {
014            mLatLng = latlng;
015            mId = id;
016        }
017    }
018 
019    @Override
020    protected void onCreate(Bundle savedInstanceState) {
021        super.onCreate(savedInstanceState);
022        setContentView(R.layout.geolocation_view);
023 
024        mLocationClient = new LocationClient(this, this, this);
025 
026        // Create a new broadcast receiver to receive updates from the listeners and service
027        mGeofenceBroadcastReceiver = new ResturantGeofenceReceiver();
028 
029        // Create an intent filter for the broadcast receiver
030        mIntentFilter = new IntentFilter();
031 
032        // Action for broadcast Intents that report successful addition of geofences
033        mIntentFilter.addAction(ACTION_GEOFENCES_ADDED);
034 
035        // Action for broadcast Intents that report successful removal of geofences
036        mIntentFilter.addAction(ACTION_GEOFENCES_REMOVED);
037 
038        // Action for broadcast Intents containing various types of geofencing errors
039        mIntentFilter.addAction(ACTION_GEOFENCE_ERROR);
040 
041        // All Location Services sample apps use this category
042        mIntentFilter.addCategory(CATEGORY_LOCATION_SERVICES);
043 
044        createGeofences();
045 
046        mRegisterGeofenceButton = (Button)findViewById(R.id.geofence_switch);
047        mGeofenceState = CAN_START_GEOFENCE;
048     
049    }
050     
051    @Override
052    protected void onResume() {
053        super.onResume();
054        // Register the broadcast receiver to receive status updates
055        LocalBroadcastManager.getInstance(this).registerReceiver(
056            mGeofenceBroadcastReceiver, mIntentFilter);
057    }
058         
059    /**
060     * Create a Geofence list
061     */
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)
067                .setCircularRegion(
068                    ALLRESTURANTLOCATIONS[ix].mLatLng.latitude, ALLRESTURANTLOCATIONS[ix].mLatLng.longitude, GEOFENCERADIUS)
069                .setExpirationDuration(Geofence.NEVER_EXPIRE)
070                .build();
071            mGeofenceList.add(fence);
072        }
073    }
074 
075    // callback function when the mRegisterGeofenceButton is clicked
076    public void onRegisterGeofenceButtonClick(View view) {
077        if (mGeofenceState == CAN_REGISTER_GEOFENCE) {
078            registerGeofences();
079            mGeofenceState = GEOFENCE_REGISTERED;
080            mGeofenceButton.setText(R.string.unregister_geofence);
081            mGeofenceButton.setClickable(true);           
082        else {
083            unregisterGeofences();
084            mGeofenceButton.setText(R.string.register_geofence);
085            mGeofenceButton.setClickable(true);
086            mGeofenceState = CAN_REGISTER_GEOFENCE;
087        }
088    }
089 
090    private boolean checkGooglePlayServices() {
091        int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
092        if (result == ConnectionResult.SUCCESS) {
093            return true;
094        }
095        else {
096            Dialog errDialog = GooglePlayServicesUtil.getErrorDialog(
097                    result,
098                    this,
099                    CONNECTION_FAILURE_RESOLUTION_REQUEST);
100 
101            if (errorDialog != null) {
102                errorDialog.show();
103            }
104        }
105        return false;
106   }
107 
108 
109    public void registerGeofences() {
110     
111        if (!checkGooglePlayServices()) {
112 
113            return;
114        }
115        mRequestType = REQUEST_TYPE.ADD;
116 
117        try {
118            // Try to add geofences
119            requestConnectToLocationClient();
120        } catch (UnsupportedOperationException e) {
121            // handle the exception
122        }
123         
124    }
125 
126    public void unregisterGeofences() {
127 
128        if (!checkGooglePlayServices()) {
129            return;
130        }
131 
132        // Record the type of removal
133          mRequestType = REQUEST_TYPE.REMOVE;
134 
135        // Try to make a removal request
136        try {
137            mCurrentIntent = getRequestPendingIntent());
138            requestConnectToLocationClient();
139 
140        } catch (UnsupportedOperationException e) {
141            // handle the exception
142        }
143    }
144 
145    public void requestConnectToLocationServices () throws UnsupportedOperationException {
146        // If a request is not already in progress
147        if (!mRequestInProgress) {
148            mRequestInProgress = true;
149 
150            locationClient().connect();
151        }
152        else {
153            // Throw an exception and stop the request
154            throw new UnsupportedOperationException();
155        }
156    }
157 
158 
159    /**
160     * Get a location client and disconnect from Location Services
161     */
162    private void requestDisconnectToLocationServices() {
163 
164        // A request is no longer in progress
165        mRequestInProgress = false;
166 
167        locationClient().disconnect();
168         
169        if (mRequestType == REQUEST_TYPE.REMOVE) {
170            mCurrentIntent.cancel();
171        }
172 
173    }
174 
175    /**
176     * returns A LocationClient object
177     */
178    private GooglePlayServicesClient locationClient() {
179        if (mLocationClient == null) {
180 
181            mLocationClient = new LocationClient(this, this, this);
182        }
183        return mLocationClient;
184 
185}
186 
187    /*
188     Called back from the Location Services when the request to connect the client finishes successfully. At this point, you can
189request the current location or start periodic updates
190     */
191    @Override
192    public void onConnected(Bundle bundle) {
193        if (mRequestType == REQUEST_TYPE.ADD) {
194        // Create a PendingIntent for Location Services to send when a geofence transition occurs
195        mGeofencePendingIntent = createRequestPendingIntent();
196 
197        // Send a request to add the current geofences
198        mLocationClient.addGeofences(mGeofenceList, mGeofencePendingIntent, this);
199 
200        }
201        else if (mRequestType == REQUEST_TYPE.REMOVE){
202 
203            mLocationClient.removeGeofences(mCurrentIntent, this);       
204        }
205}
206
207}

代码示例 5。 通过位置服务申请地理围栏监控 **

实施位置服务回调

位置服务申请通常是非阻塞或异步调用。 事实上,在上一部分的代码示例 5 中,我们已经实施了这些函数中的一个:locationClient().connect() 调用和位置客户端建立连接后,位置服务将会调用 onConnected(Bundle bundle) 函数。 代码示例 6 列出了我们需要实施的其他位置回调函数。

    
    
001public class GeolocationActivity extends Activity implements
002        OnAddGeofencesResultListener,
003        OnRemoveGeofencesResultListener,
004        GooglePlayServicesClient.ConnectionCallbacks,
005        GooglePlayServicesClient.OnConnectionFailedListener {
006…  
007 
008 
009    @Override
010    public void onDisconnected() {
011        mRequestInProgress = false;
012        mLocationClient = null;
013}
014 
015     
016 
017    /*
018     * Handle the result of adding the geofences
019     */
020    @Override
021    public void onAddGeofencesResult(int statusCode, String[] geofenceRequestIds) {
022 
023        // Create a broadcast Intent that notifies other components of success or failure
024        Intent broadcastIntent = new Intent();
025 
026        // Temp storage for messages
027        String msg;
028 
029        // If adding the geocodes was successful
030        if (LocationStatusCodes.SUCCESS == statusCode) {
031 
032            // Create a message containing all the geofence IDs added.
033            msg = getString(R.string.add_geofences_result_success,
034                    Arrays.toString(geofenceRequestIds));
035 
036            // Create an Intent to broadcast to the app
037            broadcastIntent.setAction(ACTION_GEOFENCES_ADDED)
038                           .addCategory(CATEGORY_LOCATION_SERVICES)
039                           .putExtra(EXTRA_GEOFENCE_STATUS, msg);
040        // If adding the geofences failed
041        } else {
042            msg = getString(
043                    R.string.add_geofences_result_failure,
044                    statusCode,
045                    Arrays.toString(geofenceRequestIds)
046            );
047            broadcastIntent.setAction(ACTION_GEOFENCE_ERROR)
048                           .addCategory(CATEGORY_LOCATION_SERVICES)
049                           .putExtra(EXTRA_GEOFENCE_STATUS, msg);
050        }
051 
052        LocalBroadcastManager.getInstance(this)
053            .sendBroadcast(broadcastIntent);
054 
055        // request to disconnect the location client
056        requestDisconnectToLocationServices();
057    }
058 
059    /*
060     * Implementation of OnConnectionFailedListener.onConnectionFailed
061     * If a connection or disconnection request fails, report the error
062     * connectionResult is passed in from Location Services
063     */
064    @Override
065    public void onConnectionFailed(ConnectionResult connectionResult) {
066        mInProgress = false;
067        if (connectionResult.hasResolution()) {
068 
069            try {
070                connectionResult.startResolutionForResult(this,
071                    CONNECTION_FAILURE_RESOLUTION_REQUEST);
072            }
073            catch (SendIntentException e) {
074                // log the error
075            }
076        }
077        else {
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);
084        }
085    }
086     
087    @Override
088    public void onRemoveGeofencesByPendingIntentResult(int statusCode,
089            PendingIntent requestIntent) {
090 
091        // Create a broadcast Intent that notifies other components of success or failure
092        Intent broadcastIntent = new Intent();
093 
094        // If removing the geofences was successful
095        if (statusCode == LocationStatusCodes.SUCCESS) {
096 
097            // Set the action and add the result message
098            broadcastIntent.setAction(ACTION_GEOFENCES_REMOVED);
099            broadcastIntent.putExtra(EXTRA_GEOFENCE_STATUS,
100                    getString(R.string.remove_geofences_intent_success));
101 
102        }
103        else {
104            // removing the geocodes failed
105 
106 
107            // Set the action and add the result message
108            broadcastIntent.setAction(ACTION_GEOFENCE_ERROR);
109            broadcastIntent.putExtra(EXTRA_GEOFENCE_STATUS,
110                    getString(R.string.remove_geofences_intent_failure,
111                        statusCode));
112        }
113        LocalBroadcastManager.getInstance(this)
114                .sendBroadcast(broadcastIntent);
115 
116        // request to disconnect the location client
117        requestDisconnectToLocationServices();
118    }
119 
120         
121    public class ResturantGeofenceReceiver extends BroadcastReceiver {
122   
123 
124      @Override
125        public void onReceive(Context context, Intent intent) {
126            String action = intent.getAction();
127 
128            // Intent contains information about errors in adding or removing geofences
129            if (TextUtils.equals(action, ACTION_GEOFENCE_ERROR)) {
130                // handleGeofenceError(context, intent);
131            }
132            else if (TextUtils.equals(action, ACTION_GEOFENCES_ADDED)
133                    ||
134                    TextUtils.equals(action, ACTION_GEOFENCES_REMOVED)) {
135                // handleGeofenceStatus(context, intent);
136            }
137            else if (TextUtils.equals(action, ACTION_GEOFENCE_TRANSITION)) {
138                // handleGeofenceTransition(context, intent);
139            }
140            else {
141                // handle error
142            }
143             
144        }
145    }
146 
147 
148    public PendingIntent getRequestPendingIntent() {
149        return createRequestPendingIntent();
150    }
151 
152    private PendingIntent createRequestPendingIntent() {
153 
154        if (mGeofencePendingIntent != null) {
155 
156            // Return the existing intent
157            return mGeofencePendingIntent;
158 
159        // If no PendingIntent exists
160        } else {
161 
162            // Create an Intent pointing to the IntentService
163            Intent intent = new Intent(this,
164                ReceiveGeofenceTransitionIntentService.class);
165 
166            return PendingIntent.getService(
167                    this,
168                    0,
169                    intent,
170                    PendingIntent.FLAG_UPDATE_CURRENT);
171        }
172    }
173 
174 
175    @Override
176public void onRemoveGeofencesByRequestIdsResult(int statusCode,
177    String[] geofenceRequestIds) {
178 
179        // it should not come here because we only remove geofences by PendingIntent
180        // Disconnect the location client
181        requestDisconnection();
182    }

代码示例 6。 实施位置服务回调 **

实施意向服务

最后,我们需要实施 IntentService 类,以处理地理围栏切换(代码示例 7)。

    
    
01public class ReceiveGeofenceTransitionIntentService extends IntentService {
02    /**
03     * Sets an identifier for the service
04     */
05    public ReceiveGeofenceTransitionIntentService() {
06        super("ReceiveGeofenceTransitionsIntentService");
07    }
08 
09    @Override
10    protected void onHandleIntent(Intent intent) {
11         
12        // Create a local broadcast Intent
13        Intent broadcastIntent = new Intent();
14 
15        // Give it the category for all intents sent by the Intent Service
16        broadcastIntent.addCategory(CATEGORY_LOCATION_SERVICES);
17 
18     
19        // First check for errors
20        if (LocationClient.hasError(intent)) {
21            // Get the error code with a static method
22            int errorCode = LocationClient.getErrorCode(intent);
23        }
24        else {
25            // Get the type of transition (entry or exit)
26            int transition =
27                    LocationClient.getGeofenceTransition(intent);
28             
29            if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER)  ||
30                    (transition == Geofence.GEOFENCE_TRANSITION_EXIT)) {
31 
32                // Post a notification
33            }
34            else {
35                // handle the error
36            }
37        }
38    }
39}

代码示例 7。 实施 IntentService 类以处理地理围栏切换

总结

在本文中,我们介绍了如何将地图和地理围栏特性集成至 Android 商务应用。 这些特性可支持丰富的地理定位内容和基于强大定位功能的服务,并参照了应用中的已有案例。

参考文献

关于作者

Miao Wei 是英特尔软件及服务事业部的软件工程师。 他目前负责英特尔® 凌动™ 处理器大规模支持项目。

* 其他的名称和品牌可能是其他所有者的资产。
** 该示例源代码根据英特尔示例源代码许可协议发布

优化声明

英特尔的编译器针对非英特尔微处理器的优化程度可能与英特尔微处理器相同(或不同)。 这些优化包括 SSE2,SSE3 和 SSSE3 指令集以及其它优化。 对于在非英特尔制造的微处理器上进行的优化,英特尔不对相应的可用性、功能或有效性提供担保。

此产品中依赖于处理器的优化仅适用于英特尔微处理器。 某些不是专门面向英特尔微体系结构的优化保留专供英特尔微处理器使用。 请参阅相应的产品用户和参考指南,以了解关于本通知涉及的特定指令集的更多信息。

通知版本 #20110804

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值