注:本文翻譯自Google官方的Android Developers Training文檔,譯者技術一般,由于喜愛安卓而產生了翻譯的念頭,純屬個人興趣愛好。
原文鏈接: http://developer.android.com/training/location/geofencing.html
地理圍欄可以將用戶當前地點信息和周圍的地點信息相結合,它其實是用戶接近潛在的感興趣的地點的程度。要標記一個感興趣的地點,你需要指定它的經緯度。要調整接近的位置,你還需要添加一個半徑。經緯度和半徑加起來就成為了一個地理圍欄。你可以同一時間有多個激活的地理圍欄。
定位服務將一個地理圍欄看做是一塊面積而不是點和距離。這就可以當用戶進入或離開地理圍欄時檢測到。對于每一個地理圍欄,你可以讓定位服務向你發送進入事件或離開事件或者都發送。你還可以限制地理圍欄的持續時間,方法是定義一個有效期(以毫秒為單位)。當地理圍欄過期后,定位服務會自動移除它。
一). 請求地理圍欄監測
請求地理圍欄監測的第一步是申請必需的權限。要使用地理圍欄,你的應用必須申請 ACCESS_FINE_LOCATION 。要申請這一權限,將下列元素添加為 <manifest> 標簽的子標簽:
< uses-permission android:name ="android.permission.ACCESS_FINE_LOCATION" />
檢查Google Play服務
位置服務是Google Play服務APK的其中一部分。由于用戶設備的狀態時難以預料的,你應該一直在你嘗試連接定位服務之前,檢查APK是否已經安裝。要檢查APK是否安裝,可以調用 GooglePlayServicesUtil.isGooglePlayServicesAvailable() ,它會返回一個整形的結果碼,其含義可以參閱: ConnectionResult 。如果你遇到了一個錯誤,可以調用 GooglePlayServicesUtil.getErrorDialog() ,來獲取一個本地的對話框,引導用戶執行正確地行為,之后將這一對話框顯示在一個 DialogFragment 上。這一對話框可能允許用戶解決當前的問題,此時Google Play服務會發回一個結果到你的activity中。要處理這一結果,需要覆寫 onActivityResult() 方法。
Note:
要使你的應用可以兼容1.6及以后版本的系統,顯示 DialogFragment 的activity必須是 FragmentActivity 的子類,而非 Activity 。使用 FragmentActivity 還可以允許你調用 getSupportFragmentManager() 方法來顯示 DialogFragment 。
由于你一直需要在你的代碼多個地方檢查Google Play服務,所以應該定義一個方法將檢查行為進行封裝,之后在每次連接嘗試之前進行檢查。下面的代碼片段包含了檢查Google Play服務所需要的代碼:
public class MainActivity extends FragmentActivity { ... // Global constants /* * Define a request code to send to Google Play services * This code is returned in Activity.onActivityResult */ private final static int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000 ; ... // Define a DialogFragment that displays the error dialog public static class ErrorDialogFragment extends DialogFragment { // Global field to contain the error dialog private Dialog mDialog; ... // Default constructor. Sets the dialog field to null public ErrorDialogFragment() { super (); mDialog = null ; } ... // Set the dialog to display public void setDialog(Dialog dialog) { mDialog = dialog; } ... // Return a Dialog to the DialogFragment. @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return mDialog; } ... } ... /* * Handle results returned to the FragmentActivity * by Google Play services */ @Override protected void onActivityResult( int requestCode, int resultCode, Intent data) { // Decide what to do based on the original request code switch (requestCode) { ... case CONNECTION_FAILURE_RESOLUTION_REQUEST : /* * If the result code is Activity.RESULT_OK, try * to connect again */ switch (resultCode) { ... case Activity.RESULT_OK : /* * Try the request again */ ... break ; } ... } ... } ... private boolean servicesConnected() { // Check that Google Play services is available int resultCode = GooglePlayServicesUtil. isGooglePlayServicesAvailable( this ); // If Google Play services is available if (ConnectionResult.SUCCESS == resultCode) { // In debug mode, log the status Log.d("Geofence Detection" , "Google Play services is available." ); // Continue return true ; // Google Play services was not available for some reason } else { // Get the error code int errorCode = connectionResult.getErrorCode(); // Get the error dialog from Google Play services Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog( errorCode, this , CONNECTION_FAILURE_RESOLUTION_REQUEST); // If Google Play services can provide an error dialog if (errorDialog != null ) { // Create a new DialogFragment for the error dialog ErrorDialogFragment errorFragment = new ErrorDialogFragment(); // Set the dialog in the DialogFragment errorFragment.setDialog(errorDialog); // Show the error dialog in the DialogFragment errorFragment.show( getSupportFragmentManager(), "Geofence Detection" ); } } } ... }
在后續章節的代碼片段中,都會調用這一方法來驗證是否可獲取Google Play服務。
要使用地理圍欄,首先定義你想要監測的地理圍欄。雖然你經常要將地理圍欄信息保存到一個本地的數據庫或者從網絡上下載下來,你需要將一個地理圍欄發送給定位服務作為一個 Geofence 的實例(通過 Geofence.Builder 創建的)。每一個對象包含下列信息:
經緯度和半徑:
給地理圍欄定義一個圓形區域。使用經緯度標記一個感興趣的地點,并且使用半徑來調整當用戶具體該地點多近后地理圍欄會被檢測到。半徑越大,用戶接近地理圍欄時,激活它的可能性就越高。例如,如果一個應用提供了一個大半徑的地理圍欄,當用戶回家時可以自動打開房間里的燈。由于半徑設的太大,很有可能用戶離開之后燈還是亮著的。
有效期:
設置地理圍欄的有效期。一旦超過了有效期,定位服務將會刪除該地理圍欄。在大多數情況下,你應該指定一個有效期,但你也可能希望為用戶的屋子或者工作地點的地理圍欄長期保留。
過度類型:
當用戶進入了地理圍欄的范圍(“ 進入 ”)以及當用于離開了此范圍(“ 離開 ”),定位服務可以檢測到這兩個類型之一,或者兩者都檢測到。
地理圍欄ID:
一個和地理圍欄一起保存的字符串。你應該讓這個值保持唯一,所以你可以使用它從定位服務中移除一個地理圍欄。
定義一個地理圍欄存儲
一個地理圍欄應用需要讀寫地理圍欄數據以持久化數據。你不應該使用
Geofence
對象來做這件事情;相反的,使用諸如數據庫等存儲技術來保存相關的數據是比較好的。
作為一個存儲數據的例子,下面的代碼片段定義了兩個類,它們使用應用的 SharedPreferences 實例持久化數據。類 SimpleGeofence ,是一個類似于數據庫記錄的類,它以一個“ 稀疏 ”的形式保存一個單一的 Geofence 對象。類 SimpleGeofenceStore 類似于一個數據庫,它向 SharedPreferences 實例讀寫 SimpleGeofence 數據。
public class MainActivity extends FragmentActivity { ... /** * A single Geofence object, defined by its center and radius. */ public class SimpleGeofence { // Instance variables private final String mId; private final double mLatitude; private final double mLongitude; private final float mRadius; private long mExpirationDuration; private int mTransitionType; /** * @param geofenceId The Geofence's request ID * @param latitude Latitude of the Geofence's center. * @param longitude Longitude of the Geofence's center. * @param radius Radius of the geofence circle. * @param expiration Geofence expiration duration * @param transition Type of Geofence transition. */ public SimpleGeofence( String geofenceId, double latitude, double longitude, float radius, long expiration, int transition) { // Set the instance fields from the constructor this .mId = geofenceId; this .mLatitude = latitude; this .mLongitude = longitude; this .mRadius = radius; this .mExpirationDuration = expiration; this .mTransitionType = transition; } // Instance field getters public String getId() { return mId; } public double getLatitude() { return mLatitude; } public double getLongitude() { return mLongitude; } public float getRadius() { return mRadius; } public long getExpirationDuration() { return mExpirationDuration; } public int getTransitionType() { return mTransitionType; } /** * Creates a Location Services Geofence object from a * SimpleGeofence. * * @return A Geofence object */ public Geofence toGeofence() { // Build a new Geofence object return new Geofence.Builder() .setRequestId(getId()) .setTransitionTypes(mTransitionType) .setCircularRegion( getLatitude(), getLongitude(), getRadius()) .setExpirationDuration(mExpirationDuration) .build(); } } ... /** * Storage for geofence values, implemented in SharedPreferences. */ public class SimpleGeofenceStore { // Keys for flattened geofences stored in SharedPreferences public static final String KEY_LATITUDE = "com.example.android.geofence.KEY_LATITUDE" ; public static final String KEY_LONGITUDE = "com.example.android.geofence.KEY_LONGITUDE" ; public static final String KEY_RADIUS = "com.example.android.geofence.KEY_RADIUS" ; public static final String KEY_EXPIRATION_DURATION = "com.example.android.geofence.KEY_EXPIRATION_DURATION" ; public static final String KEY_TRANSITION_TYPE = "com.example.android.geofence.KEY_TRANSITION_TYPE" ; // The prefix for flattened geofence keys public static final String KEY_PREFIX = "com.example.android.geofence.KEY" ; /* * Invalid values, used to test geofence storage when * retrieving geofences */ public static final long INVALID_LONG_VALUE = -999l ; public static final float INVALID_FLOAT_VALUE = -999.0f ; public static final int INVALID_INT_VALUE = -999 ; // The SharedPreferences object in which geofences are stored private final SharedPreferences mPrefs; // The name of the SharedPreferences private static final String SHARED_PREFERENCES = "SharedPreferences" ; // Create the SharedPreferences storage with private access only public SimpleGeofenceStore(Context context) { mPrefs = context.getSharedPreferences( SHARED_PREFERENCES, Context.MODE_PRIVATE); } /** * Returns a stored geofence by its id, or returns null * if it's not found. * * @param id The ID of a stored geofence * @return A geofence defined by its center and radius. See */ public SimpleGeofence getGeofence(String id) { /* * Get the latitude for the geofence identified by id, or * INVALID_FLOAT_VALUE if it doesn't exist */ double lat = mPrefs.getFloat( getGeofenceFieldKey(id, KEY_LATITUDE), INVALID_FLOAT_VALUE); /* * Get the longitude for the geofence identified by id, or * INVALID_FLOAT_VALUE if it doesn't exist */ double lng = mPrefs.getFloat( getGeofenceFieldKey(id, KEY_LONGITUDE), INVALID_FLOAT_VALUE); /* * Get the radius for the geofence identified by id, or * INVALID_FLOAT_VALUE if it doesn't exist */ float radius = mPrefs.getFloat( getGeofenceFieldKey(id, KEY_RADIUS), INVALID_FLOAT_VALUE); /* * Get the expiration duration for the geofence identified * by id, or INVALID_LONG_VALUE if it doesn't exist */ long expirationDuration = mPrefs.getLong( getGeofenceFieldKey(id, KEY_EXPIRATION_DURATION), INVALID_LONG_VALUE); /* * Get the transition type for the geofence identified by * id, or INVALID_INT_VALUE if it doesn't exist */ int transitionType = mPrefs.getInt( getGeofenceFieldKey(id, KEY_TRANSITION_TYPE), INVALID_INT_VALUE); // If none of the values is incorrect, return the object if ( lat != GeofenceUtils.INVALID_FLOAT_VALUE && lng != GeofenceUtils.INVALID_FLOAT_VALUE && radius != GeofenceUtils.INVALID_FLOAT_VALUE && expirationDuration != GeofenceUtils.INVALID_LONG_VALUE && transitionType != GeofenceUtils.INVALID_INT_VALUE) { // Return a true Geofence object return new SimpleGeofence( id, lat, lng, radius, expirationDuration, transitionType); // Otherwise, return null. } else { return null ; } } /** * Save a geofence. * @param geofence The SimpleGeofence containing the * values you want to save in SharedPreferences */ public void setGeofence(String id, SimpleGeofence geofence) { /* * Get a SharedPreferences editor instance. Among other * things, SharedPreferences ensures that updates are atomic * and non-concurrent */ Editor editor = mPrefs.edit(); // Write the Geofence values to SharedPreferences editor.putFloat( getGeofenceFieldKey(id, KEY_LATITUDE), ( float ) geofence.getLatitude()); editor.putFloat( getGeofenceFieldKey(id, KEY_LONGITUDE), ( float ) geofence.getLongitude()); editor.putFloat( getGeofenceFieldKey(id, KEY_RADIUS), geofence.getRadius()); editor.putLong( getGeofenceFieldKey(id, KEY_EXPIRATION_DURATION), geofence.getExpirationDuration()); editor.putInt( getGeofenceFieldKey(id, KEY_TRANSITION_TYPE), geofence.getTransitionType()); // Commit the changes editor.commit(); } public void clearGeofence(String id) { /* * Remove a flattened geofence object from storage by * removing all of its keys */ Editor editor = mPrefs.edit(); editor.remove(getGeofenceFieldKey(id, KEY_LATITUDE)); editor.remove(getGeofenceFieldKey(id, KEY_LONGITUDE)); editor.remove(getGeofenceFieldKey(id, KEY_RADIUS)); editor.remove(getGeofenceFieldKey(id, KEY_EXPIRATION_DURATION)); editor.remove(getGeofenceFieldKey(id, KEY_TRANSITION_TYPE)); editor.commit(); } /** * Given a Geofence object's ID and the name of a field * (for example, KEY_LATITUDE), return the key name of the * object's values in SharedPreferences. * * @param id The ID of a Geofence object * @param fieldName The field represented by the key * @return The full key name of a value in SharedPreferences */ private String getGeofenceFieldKey(String id, String fieldName) { return KEY_PREFIX + "_" + id + "_" + fieldName; } } ... }
創建地理圍欄對象
下面的代碼片段使用 SimpleGeofence 和 SimpleGeofenceStore 類從UI中獲取地理圍欄數據,把這些對象存儲在一個 SimpleGeofenceStore 對象中,之后創建 Geofence 對象:
public class MainActivity extends FragmentActivity { ... /* * Use to set an expiration time for a geofence. After this amount * of time Location Services will stop tracking the geofence. */ private static final long SECONDS_PER_HOUR = 60 ; private static final long MILLISECONDS_PER_SECOND = 1000 ; private static final long GEOFENCE_EXPIRATION_IN_HOURS = 12 ; private static final long GEOFENCE_EXPIRATION_TIME = GEOFENCE_EXPIRATION_IN_HOURS * SECONDS_PER_HOUR * MILLISECONDS_PER_SECOND; ... /* * Handles to UI views containing geofence data */ // Handle to geofence 1 latitude in the UI private EditText mLatitude1; // Handle to geofence 1 longitude in the UI private EditText mLongitude1; // Handle to geofence 1 radius in the UI private EditText mRadius1; // Handle to geofence 2 latitude in the UI private EditText mLatitude2; // Handle to geofence 2 longitude in the UI private EditText mLongitude2; // Handle to geofence 2 radius in the UI private EditText mRadius2; /* * Internal geofence objects for geofence 1 and 2 */ private SimpleGeofence mUIGeofence1; private SimpleGeofence mUIGeofence2; ... // Internal List of Geofence objects List<Geofence> mGeofenceList; // Persistent storage for geofences private SimpleGeofenceStore mGeofenceStorage; ... @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); ... // Instantiate a new geofence storage area mGeofenceStorage = new SimpleGeofenceStore( this ); // Instantiate the current List of geofences mCurrentGeofences = new ArrayList<Geofence> (); } ... /** * Get the geofence parameters for each geofence from the UI * and add them to a List. */ public void createGeofences() { /* * Create an internal object to store the data. Set its * ID to "1". This is a "flattened" object that contains * a set of strings */ mUIGeofence1 = new SimpleGeofence( "1" , Double.valueOf(mLatitude1.getText().toString()), Double.valueOf(mLongitude1.getText().toString()), Float.valueOf(mRadius1.getText().toString()), GEOFENCE_EXPIRATION_TIME, // This geofence records only entry transitions Geofence.GEOFENCE_TRANSITION_ENTER); // Store this flat version mGeofenceStorage.setGeofence("1" , mUIGeofence1); // Create another internal object. Set its ID to "2" mUIGeofence2 = new SimpleGeofence( "2" , Double.valueOf(mLatitude2.getText().toString()), Double.valueOf(mLongitude2.getText().toString()), Float.valueOf(mRadius2.getText().toString()), GEOFENCE_EXPIRATION_TIME, // This geofence records both entry and exit transitions Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT); // Store this flat version mGeofenceStorage.setGeofence(2 , mUIGeofence2); mGeofenceList.add(mUIGeofence1.toGeofence()); mGeofenceList.add(mUIGeofence2.toGeofence()); } ... }
除了你希望監測的存儲 Geofence 對象的 List ,你還需要向定位服務提供一個 Intent ,當監測到地理圍欄轉換的時候會將它發送給你的應用。
為地理圍欄轉換定義一個Intent
從定位服務發送的
Intent
可以激活你應用中的多個行為,但是你不應該讓它啟動一個activity或者fragment,因為組件只有在用戶行為的出發條件下變的向用戶可見才行。在很多情況下,用一個
IntentService
來處理intent是一個不錯的方式。一個
IntentService
可以發布一個通知,在后臺執行一個長時間運作的任務,將intent發送給其它服務,或者發送一個廣播intent。下面的代碼片段展示了如何定義一個
PendingIntent
來啟動一個
IntentService
:
public class MainActivity extends FragmentActivity { ... /* * Create a PendingIntent that triggers an IntentService in your * app when a geofence transition occurs. */ private PendingIntent getTransitionPendingIntent() { // Create an explicit Intent Intent intent = new Intent( this , ReceiveTransitionsIntentService. class ); /* * Return the PendingIntent */ return PendingIntent.getService( this , 0 , intent, PendingIntent.FLAG_UPDATE_CURRENT); } ... }
要向定位服務請求監測地理圍欄,所需的代碼現在你已經都有了。
發送監測請求
發送監測請求需要兩種異步操作。第一種操作為請求獲取一個定位客戶端,第二個操作使用客戶端發送請求。在這兩個情況中,定位服務會在它完成了操作后調用一個回調函數。要處理這些操作的最佳方法是將這些函數調用串聯起來。下面的代碼片段將演示如何設置一個acitvity,定義方法,并以正確地順序調用他們。
首先,修改activity類定義來實現必要的回調接口。添加下列接口:
當一個定位客戶端連接或者斷開連接后,定位服務需要調用的方法。
當嘗試連接定位客戶端失敗或發生錯誤后,定位服務需要調用的方法。
一旦添加了地理圍欄,定位服務調用的方法。
例如:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... }
開始請求過程
接下來,定義一個方法,它通過連接定位服務來開始請求的過程。通過設置一個全局變量來標記它是一個添加地理圍欄的請求。這將允許你使用 ConnectionCallbacks.onConnected() 這一回調函數來添加地理圍欄或者移除它們,這些細節將在下面的章節展開。
為了防止競爭場景的發生(比如你的應用在第一個請求結束之前又發出了第二個請求),定義一個布爾變量,用來標記當前請求的狀態:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... // Holds the location client private LocationClient mLocationClient; // Stores the PendingIntent used to request geofence monitoring private PendingIntent mGeofenceRequestIntent; // Defines the allowable request types. public enum REQUEST_TYPE = {ADD} private REQUEST_TYPE mRequestType; // Flag that indicates if a request is underway. private boolean mInProgress; ... @Override protected void onCreate(Bundle savedInstanceState) { ... // Start with the request flag set to false mInProgress = false ; ... } ... /** * Start a request for geofence monitoring by calling * LocationClient.connect(). */ public void addGeofences() { // Start a request to add geofences mRequestType = ADD; /* * Test for Google Play services after setting the request type. * If Google Play services isn't present, the proper request * can be restarted. */ if (! servicesConnected()) { return ; } /* * Create a new location client object. Since the current * activity class implements ConnectionCallbacks and * OnConnectionFailedListener, pass the current activity object * as the listener for both parameters */ mLocationClient = new LocationClient( this , this , this ) // If a request is not already underway if (! mInProgress) { // Indicate that a request is underway mInProgress = true ; // Request a connection from the client to Location Services mLocationClient.connect(); } else { /* * A request is already underway. You can handle * this situation by disconnecting the client, * re-setting the flag, and then re-trying the * request. */ } } ... }
發送請求來添加地理圍欄
在你的
ConnectionCallbacks.onConnected()
實現中,調用
LocationClient.addGeofences()
。注意,如果連接失敗了,
onConnected()
不會被調用,請求被中止。
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... /* * Provide the implementation of ConnectionCallbacks.onConnected() * Once the connection is available, send a request to add the * Geofences */ @Override private void onConnected(Bundle dataBundle) { ... switch (mRequestType) { case ADD : // Get the PendingIntent for the request mTransitionPendingIntent = getTransitionPendingIntent(); // Send a request to add the current geofences mLocationClient.addGeofences( mCurrentGeofences, pendingIntent, this ); ... } } ... }
注意
addGeofences()
會迅速返回,但是請求的狀態在定位服務調用
onAddGeofencesResult()
之前是不定的。一旦這一方法被調用,你就能夠確定請求是否成功。
檢查定位服務返回的結果
當定位服務調用了你的回調函數 onAddGeofencesResult() 的實現,這就代表請求完成了,之后檢查傳入的狀態碼。如果請求成功,那么你所請求的地理圍欄將被激活。否則,地理圍欄不會被激活,你需要繼續嘗試請求或者報告錯誤。例如:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... /* * Provide the implementation of * OnAddGeofencesResultListener.onAddGeofencesResult. * Handle the result of adding the geofences * */ @Override public void onAddGeofencesResult( int statusCode, String[] geofenceRequestIds) { // If adding the geofences was successful if (LocationStatusCodes.SUCCESS == statusCode) { /* * Handle successful addition of geofences here. * You can send out a broadcast intent or update the UI. * geofences into the Intent's extended data. */ } else { // If adding the geofences failed /* * Report errors here. * You can log the error using Log.e() or update * the UI. */ } // Turn off the in progress flag and disconnect the client mInProgress = false ; mLocationClient.disconnect(); } ... }
處理連接中斷
在有些情況下,定位服務可能會在你調用了 disconnect() 之前就中斷連接了。要處理這種情況,需要實現 onDisconnected() 方法。在這個方法中,設置請求標識,以表明當前沒有進行中的請求,并將客戶端移除:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... /* * Implement ConnectionCallbacks.onDisconnected() * Called by Location Services once the location client is * disconnected. */ @Override public void onDisconnected() { // Turn off the request flag mInProgress = false ; // Destroy the current location client mLocationClient = null ; } ... }
處理連接錯誤
除了處理定位服務的常規回調函數外,你還需要提供一個回調函數,該函數會在連接錯誤發生的時候被定為服務調用。該回調函數可以重用 DialogFragment 類(你在檢查Google Play服務時所定義的類)。同時它也可以重用當用戶與錯誤對話框交互時,接收任何由Google Play服務返回的結果的 onActivityResult() 函數。下面的代碼片段展示了該回調函數的一個例子:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... // Implementation of OnConnectionFailedListener.onConnectionFailed @Override public void onConnectionFailed(ConnectionResult connectionResult) { // Turn off the request flag mInProgress = false ; /* * If the error has a resolution, start a Google Play services * activity to resolve it. */ if (connectionResult.hasResolution()) { try { connectionResult.startResolutionForResult( this , CONNECTION_FAILURE_RESOLUTION_REQUEST); } catch (SendIntentException e) { // Log the error e.printStackTrace(); } // If no resolution is available, display an error dialog } else { // Get the error code int errorCode = connectionResult.getErrorCode(); // Get the error dialog from Google Play services Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog( errorCode, this , CONNECTION_FAILURE_RESOLUTION_REQUEST); // If Google Play services can provide an error dialog if (errorDialog != null ) { // Create a new DialogFragment for the error dialog ErrorDialogFragment errorFragment = new ErrorDialogFragment(); // Set the dialog in the DialogFragment errorFragment.setDialog(errorDialog); // Show the error dialog in the DialogFragment errorFragment.show( getSupportFragmentManager(), "Geofence Detection" ); } } } ... }
二). 處理地理圍欄轉換
當定位服務檢測到了用戶進入或者離開了一個地理圍欄,它會發送一個 Intent ,該 Intent 來自于你請求添加地理圍欄時所用到的 PendingIntent 。
定義一個IntentService
下面的代碼片段展示了當一個地理圍欄轉換發生的時候, 如何定義一個 IntentService 。當用戶點擊通知時, 顯示 應用的主activity:
public class ReceiveTransitionsIntentService extends IntentService { ... /** * Sets an identifier for the service */ public ReceiveTransitionsIntentService() { super ("ReceiveTransitionsIntentService" ); } /** * Handles incoming intents * @param intent The Intent sent by Location Services. This * Intent is provided * to Location Services (inside a PendingIntent) when you call * addGeofences() */ @Override protected void onHandleIntent(Intent intent) { // First check for errors if (LocationClient.hasError(intent)) { // Get the error code with a static method int errorCode = LocationClient.getErrorCode(intent); // Log the error Log.e("ReceiveTransitionsIntentService" , "Location Services error: " + Integer.toString(errorCode)); /* * You can also send the error code to an Activity or * Fragment with a broadcast Intent */ /* * If there's no error, get the transition type and the IDs * of the geofence or geofences that triggered the transition */ } else { // Get the type of transition (entry or exit) int transitionType = LocationClient.getGeofenceTransition(intent); // Test that a valid transition was reported if ( (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER) || (transitionType == Geofence.GEOFENCE_TRANSITION_EXIT) ) { List <Geofence> triggerList = getTriggeringGeofences(intent); String[] triggerIds = new String[geofenceList.size()]; for ( int i = 0; i < triggerIds.length; i++ ) { // Store the Id of each geofence triggerIds[i] = triggerList.get(i).getRequestId(); } /* * At this point, you can store the IDs for further use * display them, or display the details associated with * them. */ } // An invalid transition was reported } else { Log.e( "ReceiveTransitionsIntentService" , "Geofence transition error: " + Integer.toString()transitionType)); } } ...
}
在清單列表中聲明IntentService
要在系統中使用 IntentService ,在應用清單文件中添加一個 <service> 標簽,例如:
< service android:name ="com.example.android.location.ReceiveTransitionsIntentService" android:label ="@string/app_name" android:exported ="false" > </ service >
注意,你不需要為該服務指定intent過濾器,因為它僅會接收顯式的intent。如何創建地理圍欄轉換intent,可以閱讀: Send the monitoring request 。
停止地理圍欄監控
要停止地理圍欄監控,你需要將它們移除。你可以通過一個
PendingIntent
將所有地理圍欄全部移除,或者只移除一部分。過程與添加地理圍欄類似。首先需要為移除請求獲取定位客戶端,然后使用客戶端提出申請。
定位服務在完成移除后所調用的回調函數在 LocationClient.OnRemoveGeofencesResultListener 接口中被定義。將該接口聲明為你的類定義的一部分,之后添加其兩個方法的定義:
onRemoveGeofencesByPendingIntentResult()
當定位服務使用函數 removeGeofences(PendingIntent, LocationClient.OnRemoveGeofencesResultListener) 移除了所有地理圍欄后被調用。
onRemoveGeofencesByRequestIdsResult(List<String>, LocationClient.OnRemoveGeofencesResultListener)
當定位服務使用函數 removeGeofences(List<String>, LocationClient.OnRemoveGeofencesResultListener) 將給定ID所對應的部分地理圍欄移除后被調用。
下面給出這些方法的使用樣例:
移除所有地理圍欄
由于移除地理圍欄會使用一些添加地理圍欄時所使用的方法,我們從定義另一個請求類型開始:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... // Enum type for controlling the type of removal requested public enum REQUEST_TYPE = {ADD, REMOVE_INTENT} ... }
通過獲取定位服務的連接開始移除請求。如果連接失敗了, onConnected() 不會被調用,請求中止。下面的代碼片段展示了如何開始請求:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... /** * Start a request to remove geofences by calling * LocationClient.connect() */ public void removeGeofences(PendingIntent requestIntent) { // Record the type of removal request mRequestType = REMOVE_INTENT; /* * Test for Google Play services after setting the request type. * If Google Play services isn't present, the request can be * restarted. */ if (! servicesConnected()) { return ; } // Store the PendingIntent mGeofenceRequestIntent = requestIntent; /* * Create a new location client object. Since the current * activity class implements ConnectionCallbacks and * OnConnectionFailedListener, pass the current activity object * as the listener for both parameters */ mLocationClient = new LocationClient( this , this , this ); // If a request is not already underway if (! mInProgress) { // Indicate that a request is underway mInProgress = true ; // Request a connection from the client to Location Services mLocationClient.connect(); } else { /* * A request is already underway. You can handle * this situation by disconnecting the client, * re-setting the flag, and then re-trying the * request. */ } } ... }
當定位服務調用了回調函數指明連接已建立,那么就發出移除所有地理圍欄的請求。再發出請求后記得關閉連接。例如:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... /** * Once the connection is available, send a request to remove the * Geofences. The method signature used depends on which type of * remove request was originally received. */ private void onConnected(Bundle dataBundle) { /* * Choose what to do based on the request type set in * removeGeofences */ switch (mRequestType) { ... case REMOVE_INTENT : mLocationClient.removeGeofences( mGeofenceRequestIntent, this ); break ; ... } } ... }
雖然對 removeGeofences(PendingIntent, LocationClient.OnRemoveGeofencesResultListener) 的調用后,服務端會馬上返回,但移除請求的結果在定位服務調用 onRemoveGeofencesByPendingIntentResult() 之前是不定的。下面的代碼片段展示了如何定義這一方法:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... /** * When the request to remove geofences by PendingIntent returns, * handle the result. * * @param statusCode the code returned by Location Services * @param requestIntent The Intent used to request the removal. */ @Override public void onRemoveGeofencesByPendingIntentResult( int statusCode, PendingIntent requestIntent) { // If removing the geofences was successful if (statusCode == LocationStatusCodes.SUCCESS) { /* * Handle successful removal of geofences here. * You can send out a broadcast intent or update the UI. * geofences into the Intent's extended data. */ } else { // If adding the geocodes failed /* * Report errors here. * You can log the error using Log.e() or update * the UI. */ } /* * Disconnect the location client regardless of the * request status, and indicate that a request is no * longer in progress */ mInProgress = false ; mLocationClient.disconnect(); } ... }
移除單個地理圍欄
移除單個地理圍欄或者部分地理圍欄的過程同刪除全部地理圍欄相似。要指定你想要移除的地理圍欄,需要把地理圍欄的ID添加到一個String的 List 對象中。將這個 List 傳遞給 removeGeofences,該方法之后便開始移除。
通過添加一個移除地理圍欄請求類型的list,然后添加一個全局變量來存儲地理圍欄的list:
... // Enum type for controlling the type of removal requested public enum REQUEST_TYPE = {ADD, REMOVE_INTENT, REMOVE_LIST} // Store the list of geofence Ids to remove String<List> mGeofencesToRemove;
之后定義你想要移除的地理圍欄list。例如,在下面的例子中,要移除的 Geofence 的ID為“1”:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... List <String> listOfGeofences = Collections.singletonList( "1" ); removeGeofences(listOfGeofences); ... }
下面的代碼片段定義了removeGeofences()方法:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... /** * Start a request to remove monitoring by * calling LocationClient.connect() * */ public void removeGeofences(List<String> geofenceIds) { // If Google Play services is unavailable, exit // Record the type of removal request mRequestType = REMOVE_LIST; /* * Test for Google Play services after setting the request type. * If Google Play services isn't present, the request can be * restarted. */ if (! servicesConnected()) { return ; } // Store the list of geofences to remove mGeofencesToRemove = geofenceIds; /* * Create a new location client object. Since the current * activity class implements ConnectionCallbacks and * OnConnectionFailedListener, pass the current activity object * as the listener for both parameters */ mLocationClient = new LocationClient( this , this , this ); // If a request is not already underway if (! mInProgress) { // Indicate that a request is underway mInProgress = true ; // Request a connection from the client to Location Services mLocationClient.connect(); } else { /* * A request is already underway. You can handle * this situation by disconnecting the client, * re-setting the flag, and then re-trying the * request. */ } } ... }
當定位服務激活了回調函數表明這個鏈接已經建立以后,發出該請求來移除列表中的地理圍欄。在發出請求之后關閉連接。例如:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... private void onConnected(Bundle dataBundle) { ... switch (mRequestType) { ... // If removeGeofencesById was called case REMOVE_LIST : mLocationClient.removeGeofences( mGeofencesToRemove, this ); break ; ... } ... } ... } ?
定義 onRemoveGeofencesByRequestIdsResult() 的實現。定位服務會激活該回調函數來指出這個移除地理圍欄的請求已經完成。在該方法中,檢查傳入的狀態碼然后采取對應的措施:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnAddGeofencesResultListener { ... /** * When the request to remove geofences by IDs returns, handle the * result. * * @param statusCode The code returned by Location Services * @param geofenceRequestIds The IDs removed */ @Override public void onRemoveGeofencesByRequestIdsResult( int statusCode, String[] geofenceRequestIds) { // If removing the geocodes was successful if (LocationStatusCodes.SUCCESS == statusCode) { /* * Handle successful removal of geofences here. * You can send out a broadcast intent or update the UI. * geofences into the Intent's extended data. */ } else { // If removing the geofences failed /* * Report errors here. * You can log the error using Log.e() or update * the UI. */ } // Indicate that a request is no longer in progress mInProgress = false ; // Disconnect the location client mLocationClient.disconnect(); } ... }
你可以將地理圍欄和其它地點感知的功能結合起來,比如定期的地點更新或者行為認知等,這些會在該系列課程中的后續課程中展開。
在下一節課程中,會向你展示請求和接收activity更新。在定期的間隔中,定位服務可以給你發送有關用戶當前物理行為的信息。基于這一信息,你可以改變你的應用行為,例如,如果你檢測到用戶在步行而不在開車,你可以增加定期更新的間隔。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
