在 Android* 商務應用中實施地圖和地理圍欄特性

摘要

本案例研究討論了怎樣將地圖和地理定位特性構建到 Android* 商務應用中。包含在 Google Maps* 上覆蓋商店位置,以及在設備進入商店地理圍欄鄰近區域時藉助地理圍欄通知用戶。html

文件夾

  1. 摘要
  2. 概述
  3. 在 Google Maps 上顯示商店位置
    1. Google Maps Android API v2
    2. 在應用清單中指定應用設置
    3. 加入地圖 Fragment
  4. 發送地理圍欄通知
    1. 註冊和取消註冊地理圍欄
    2. 實施位置服務回調
    3. 實施意向服務
  5. 總結
  6. 參考文獻
  7. 做者介紹

概述

在本案例研究中,咱們將會把地圖和地理定位功能集成到基於 Android 平板電腦的餐館商務應用中(圖 1)。 用戶可以從主菜單項「位置和地理圍欄」訪問地理定位功能(圖 2)。java


圖 1 餐館應用主界面android


圖 2 浮出控件菜單項git

在 Google Maps 上顯示商店位置

對於一款商務應用而言,顯示商店在地圖上的位置對用戶很直觀和實用(圖 3)。 Google Maps Android API 可提供一種簡單的方式將 Google Maps 集成至 Android 應用。api

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.htmlapp

而後,你需要從谷歌開發者控制檯(Google Developers Console)上對你的項目進行註冊並獲取一個 API 密鑰:https://console.developers.google.com/project異步

你需要在 AndroidManifest.xml 文件里加入 API 密鑰。ide


圖 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)。

public class ReceiveGeofenceTransitionIntentService extends IntentService {

	    /**

	     * Sets an identifier for the service

	     */

	    public ReceiveGeofenceTransitionIntentService() {

	        super("ReceiveGeofenceTransitionsIntentService");

	    }

    @Override

	    protected void onHandleIntent(Intent intent) {

	        

	        // Create a local broadcast Intent

	        Intent broadcastIntent = new Intent();

        // Give it the category for all intents sent by the Intent Service

	        broadcastIntent.addCategory(CATEGORY_LOCATION_SERVICES);

       

	        // First check for errors

	        if (LocationClient.hasError(intent)) {

	            // Get the error code with a static method

	            int errorCode = LocationClient.getErrorCode(intent);

	        } 

	        else {

	            // Get the type of transition (entry or exit)

	            int transition =

	                    LocationClient.getGeofenceTransition(intent);

	            

	            if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER)  ||

	                    (transition == Geofence.GEOFENCE_TRANSITION_EXIT)) {

                // Post a notification

	            } 

	            else {

	                // handle the error

	            }

	        }

	    }

	}


代碼演示樣例 7。 實施 IntentService 類以處理地理圍欄切換

總結

在本文中,咱們介紹了怎樣將地圖和地理圍欄特性集成至 Android 商務應用。

這些特性可支持豐富的地理定位內容和基於強大定位功能的服務,並參照了應用中的已有案例。

參考文獻

關於做者

Miao Wei 是英特爾軟件及服務事業部的軟件project師。

他眼下負責英特爾® 凌動™ 處理器大規模支持項目。

* 其它的名稱和品牌多是其它所有者的資產。
** 該演示樣例源碼依據英特爾演示樣例源碼許可協議公佈

優化聲明

英特爾的編譯器針對非英特爾微處理器的優化程度可能與英特爾微處理器一樣(或不一樣)。

這些優化包含 SSE2,SSE3 和 SSSE3 指令集以及其餘優化。 對於在非英特爾製造的微處理器上進行的優化,英特爾不正確對應的可用性、功能或有效性提供擔保。

此產品中依賴於處理器的優化僅適用於英特爾微處理器。 某些不是專門面向英特爾微體系結構的優化保留專供英特爾微處理器使用。 請參閱對應的產品用戶和參考指南。以瞭解關於本通知涉及的特定指令集的不少其它信息。

通知版本號 #20110804

相關文章
相關標籤/搜索