注:本文翻譯自Google官方的Android Developers Training文檔,譯者技術(shù)一般,由于喜愛安卓而產(chǎn)生了翻譯的念頭,純屬個人興趣愛好。
原文鏈接: http://developer.android.com/training/location/geofencing.html
地理圍欄可以將用戶當(dāng)前地點信息和周圍的地點信息相結(jié)合,它其實是用戶接近潛在的感興趣的地點的程度。要標記一個感興趣的地點,你需要指定它的經(jīng)緯度。要調(diào)整接近的位置,你還需要添加一個半徑。經(jīng)緯度和半徑加起來就成為了一個地理圍欄。你可以同一時間有多個激活的地理圍欄。
定位服務(wù)將一個地理圍欄看做是一塊面積而不是點和距離。這就可以當(dāng)用戶進入或離開地理圍欄時檢測到。對于每一個地理圍欄,你可以讓定位服務(wù)向你發(fā)送進入事件或離開事件或者都發(fā)送。你還可以限制地理圍欄的持續(xù)時間,方法是定義一個有效期(以毫秒為單位)。當(dāng)?shù)乩韲鷻谶^期后,定位服務(wù)會自動移除它。
一). 請求地理圍欄監(jiān)測
請求地理圍欄監(jiān)測的第一步是申請必需的權(quán)限。要使用地理圍欄,你的應(yīng)用必須申請 ACCESS_FINE_LOCATION 。要申請這一權(quán)限,將下列元素添加為 <manifest> 標簽的子標簽:
<
uses-permission
android:name
="android.permission.ACCESS_FINE_LOCATION"
/>
檢查Google Play服務(wù)
位置服務(wù)是Google Play服務(wù)APK的其中一部分。由于用戶設(shè)備的狀態(tài)時難以預(yù)料的,你應(yīng)該一直在你嘗試連接定位服務(wù)之前,檢查APK是否已經(jīng)安裝。要檢查APK是否安裝,可以調(diào)用 GooglePlayServicesUtil.isGooglePlayServicesAvailable() ,它會返回一個整形的結(jié)果碼,其含義可以參閱: ConnectionResult 。如果你遇到了一個錯誤,可以調(diào)用 GooglePlayServicesUtil.getErrorDialog() ,來獲取一個本地的對話框,引導(dǎo)用戶執(zhí)行正確地行為,之后將這一對話框顯示在一個 DialogFragment 上。這一對話框可能允許用戶解決當(dāng)前的問題,此時Google Play服務(wù)會發(fā)回一個結(jié)果到你的activity中。要處理這一結(jié)果,需要覆寫 onActivityResult() 方法。
Note:
要使你的應(yīng)用可以兼容1.6及以后版本的系統(tǒng),顯示 DialogFragment 的activity必須是 FragmentActivity 的子類,而非 Activity 。使用 FragmentActivity 還可以允許你調(diào)用 getSupportFragmentManager() 方法來顯示 DialogFragment 。
由于你一直需要在你的代碼多個地方檢查Google Play服務(wù),所以應(yīng)該定義一個方法將檢查行為進行封裝,之后在每次連接嘗試之前進行檢查。下面的代碼片段包含了檢查Google Play服務(wù)所需要的代碼:
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"
);
}
}
}
...
}
在后續(xù)章節(jié)的代碼片段中,都會調(diào)用這一方法來驗證是否可獲取Google Play服務(wù)。
要使用地理圍欄,首先定義你想要監(jiān)測的地理圍欄。雖然你經(jīng)常要將地理圍欄信息保存到一個本地的數(shù)據(jù)庫或者從網(wǎng)絡(luò)上下載下來,你需要將一個地理圍欄發(fā)送給定位服務(wù)作為一個 Geofence 的實例(通過 Geofence.Builder 創(chuàng)建的)。每一個對象包含下列信息:
經(jīng)緯度和半徑:
給地理圍欄定義一個圓形區(qū)域。使用經(jīng)緯度標記一個感興趣的地點,并且使用半徑來調(diào)整當(dāng)用戶具體該地點多近后地理圍欄會被檢測到。半徑越大,用戶接近地理圍欄時,激活它的可能性就越高。例如,如果一個應(yīng)用提供了一個大半徑的地理圍欄,當(dāng)用戶回家時可以自動打開房間里的燈。由于半徑設(shè)的太大,很有可能用戶離開之后燈還是亮著的。
有效期:
設(shè)置地理圍欄的有效期。一旦超過了有效期,定位服務(wù)將會刪除該地理圍欄。在大多數(shù)情況下,你應(yīng)該指定一個有效期,但你也可能希望為用戶的屋子或者工作地點的地理圍欄長期保留。
過度類型:
當(dāng)用戶進入了地理圍欄的范圍(“ 進入 ”)以及當(dāng)用于離開了此范圍(“ 離開 ”),定位服務(wù)可以檢測到這兩個類型之一,或者兩者都檢測到。
地理圍欄ID:
一個和地理圍欄一起保存的字符串。你應(yīng)該讓這個值保持唯一,所以你可以使用它從定位服務(wù)中移除一個地理圍欄。
定義一個地理圍欄存儲
一個地理圍欄應(yīng)用需要讀寫地理圍欄數(shù)據(jù)以持久化數(shù)據(jù)。你不應(yīng)該使用
Geofence
對象來做這件事情;相反的,使用諸如數(shù)據(jù)庫等存儲技術(shù)來保存相關(guān)的數(shù)據(jù)是比較好的。
作為一個存儲數(shù)據(jù)的例子,下面的代碼片段定義了兩個類,它們使用應(yīng)用的 SharedPreferences 實例持久化數(shù)據(jù)。類 SimpleGeofence ,是一個類似于數(shù)據(jù)庫記錄的類,它以一個“ 稀疏 ”的形式保存一個單一的 Geofence 對象。類 SimpleGeofenceStore 類似于一個數(shù)據(jù)庫,它向 SharedPreferences 實例讀寫 SimpleGeofence 數(shù)據(jù)。
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;
}
}
...
}
創(chuàng)建地理圍欄對象
下面的代碼片段使用 SimpleGeofence 和 SimpleGeofenceStore 類從UI中獲取地理圍欄數(shù)據(jù),把這些對象存儲在一個 SimpleGeofenceStore 對象中,之后創(chuàng)建 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());
}
...
}
除了你希望監(jiān)測的存儲 Geofence 對象的 List ,你還需要向定位服務(wù)提供一個 Intent ,當(dāng)監(jiān)測到地理圍欄轉(zhuǎn)換的時候會將它發(fā)送給你的應(yīng)用。
為地理圍欄轉(zhuǎn)換定義一個Intent
從定位服務(wù)發(fā)送的
Intent
可以激活你應(yīng)用中的多個行為,但是你不應(yīng)該讓它啟動一個activity或者fragment,因為組件只有在用戶行為的出發(fā)條件下變的向用戶可見才行。在很多情況下,用一個
IntentService
來處理intent是一個不錯的方式。一個
IntentService
可以發(fā)布一個通知,在后臺執(zhí)行一個長時間運作的任務(wù),將intent發(fā)送給其它服務(wù),或者發(fā)送一個廣播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);
}
...
}
要向定位服務(wù)請求監(jiān)測地理圍欄,所需的代碼現(xiàn)在你已經(jīng)都有了。
發(fā)送監(jiān)測請求
發(fā)送監(jiān)測請求需要兩種異步操作。第一種操作為請求獲取一個定位客戶端,第二個操作使用客戶端發(fā)送請求。在這兩個情況中,定位服務(wù)會在它完成了操作后調(diào)用一個回調(diào)函數(shù)。要處理這些操作的最佳方法是將這些函數(shù)調(diào)用串聯(lián)起來。下面的代碼片段將演示如何設(shè)置一個acitvity,定義方法,并以正確地順序調(diào)用他們。
首先,修改activity類定義來實現(xiàn)必要的回調(diào)接口。添加下列接口:
當(dāng)一個定位客戶端連接或者斷開連接后,定位服務(wù)需要調(diào)用的方法。
當(dāng)嘗試連接定位客戶端失敗或發(fā)生錯誤后,定位服務(wù)需要調(diào)用的方法。
一旦添加了地理圍欄,定位服務(wù)調(diào)用的方法。
例如:
public
class
MainActivity
extends
FragmentActivity
implements
ConnectionCallbacks,
OnConnectionFailedListener,
OnAddGeofencesResultListener {
...
}
開始請求過程
接下來,定義一個方法,它通過連接定位服務(wù)來開始請求的過程。通過設(shè)置一個全局變量來標記它是一個添加地理圍欄的請求。這將允許你使用 ConnectionCallbacks.onConnected() 這一回調(diào)函數(shù)來添加地理圍欄或者移除它們,這些細節(jié)將在下面的章節(jié)展開。
為了防止競爭場景的發(fā)生(比如你的應(yīng)用在第一個請求結(jié)束之前又發(fā)出了第二個請求),定義一個布爾變量,用來標記當(dāng)前請求的狀態(tài):
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.
*/
}
}
...
}
發(fā)送請求來添加地理圍欄
在你的
ConnectionCallbacks.onConnected()
實現(xiàn)中,調(diào)用
LocationClient.addGeofences()
。注意,如果連接失敗了,
onConnected()
不會被調(diào)用,請求被中止。
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()
會迅速返回,但是請求的狀態(tài)在定位服務(wù)調(diào)用
onAddGeofencesResult()
之前是不定的。一旦這一方法被調(diào)用,你就能夠確定請求是否成功。
檢查定位服務(wù)返回的結(jié)果
當(dāng)定位服務(wù)調(diào)用了你的回調(diào)函數(shù) onAddGeofencesResult() 的實現(xiàn),這就代表請求完成了,之后檢查傳入的狀態(tài)碼。如果請求成功,那么你所請求的地理圍欄將被激活。否則,地理圍欄不會被激活,你需要繼續(xù)嘗試請求或者報告錯誤。例如:
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();
}
...
}
處理連接中斷
在有些情況下,定位服務(wù)可能會在你調(diào)用了 disconnect() 之前就中斷連接了。要處理這種情況,需要實現(xiàn) onDisconnected() 方法。在這個方法中,設(shè)置請求標識,以表明當(dāng)前沒有進行中的請求,并將客戶端移除:
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
;
}
...
}
處理連接錯誤
除了處理定位服務(wù)的常規(guī)回調(diào)函數(shù)外,你還需要提供一個回調(diào)函數(shù),該函數(shù)會在連接錯誤發(fā)生的時候被定為服務(wù)調(diào)用。該回調(diào)函數(shù)可以重用 DialogFragment 類(你在檢查Google Play服務(wù)時所定義的類)。同時它也可以重用當(dāng)用戶與錯誤對話框交互時,接收任何由Google Play服務(wù)返回的結(jié)果的 onActivityResult() 函數(shù)。下面的代碼片段展示了該回調(diào)函數(shù)的一個例子:
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"
);
}
}
}
...
}
二). 處理地理圍欄轉(zhuǎn)換
當(dāng)定位服務(wù)檢測到了用戶進入或者離開了一個地理圍欄,它會發(fā)送一個 Intent ,該 Intent 來自于你請求添加地理圍欄時所用到的 PendingIntent 。
定義一個IntentService
下面的代碼片段展示了當(dāng)一個地理圍欄轉(zhuǎn)換發(fā)生的時候, 如何定義一個 IntentService 。當(dāng)用戶點擊通知時, 顯示 應(yīng)用的主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
要在系統(tǒng)中使用 IntentService ,在應(yīng)用清單文件中添加一個 <service> 標簽,例如:
<
service
android:name
="com.example.android.location.ReceiveTransitionsIntentService"
android:label
="@string/app_name"
android:exported
="false"
>
</
service
>
注意,你不需要為該服務(wù)指定intent過濾器,因為它僅會接收顯式的intent。如何創(chuàng)建地理圍欄轉(zhuǎn)換intent,可以閱讀: Send the monitoring request 。
停止地理圍欄監(jiān)控
要停止地理圍欄監(jiān)控,你需要將它們移除。你可以通過一個
PendingIntent
將所有地理圍欄全部移除,或者只移除一部分。過程與添加地理圍欄類似。首先需要為移除請求獲取定位客戶端,然后使用客戶端提出申請。
定位服務(wù)在完成移除后所調(diào)用的回調(diào)函數(shù)在 LocationClient.OnRemoveGeofencesResultListener 接口中被定義。將該接口聲明為你的類定義的一部分,之后添加其兩個方法的定義:
onRemoveGeofencesByPendingIntentResult()
當(dāng)定位服務(wù)使用函數(shù) removeGeofences(PendingIntent, LocationClient.OnRemoveGeofencesResultListener) 移除了所有地理圍欄后被調(diào)用。
onRemoveGeofencesByRequestIdsResult(List<String>, LocationClient.OnRemoveGeofencesResultListener)
當(dāng)定位服務(wù)使用函數(shù) removeGeofences(List<String>, LocationClient.OnRemoveGeofencesResultListener) 將給定ID所對應(yīng)的部分地理圍欄移除后被調(diào)用。
下面給出這些方法的使用樣例:
移除所有地理圍欄
由于移除地理圍欄會使用一些添加地理圍欄時所使用的方法,我們從定義另一個請求類型開始:
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}
...
}
通過獲取定位服務(wù)的連接開始移除請求。如果連接失敗了, onConnected() 不會被調(diào)用,請求中止。下面的代碼片段展示了如何開始請求:
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.
*/
}
}
...
}
當(dāng)定位服務(wù)調(diào)用了回調(diào)函數(shù)指明連接已建立,那么就發(fā)出移除所有地理圍欄的請求。再發(fā)出請求后記得關(guān)閉連接。例如:
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) 的調(diào)用后,服務(wù)端會馬上返回,但移除請求的結(jié)果在定位服務(wù)調(diào)用 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.
*/
}
}
...
}
當(dāng)定位服務(wù)激活了回調(diào)函數(shù)表明這個鏈接已經(jīng)建立以后,發(fā)出該請求來移除列表中的地理圍欄。在發(fā)出請求之后關(guān)閉連接。例如:
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() 的實現(xiàn)。定位服務(wù)會激活該回調(diào)函數(shù)來指出這個移除地理圍欄的請求已經(jīng)完成。在該方法中,檢查傳入的狀態(tài)碼然后采取對應(yīng)的措施:
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();
}
...
}
你可以將地理圍欄和其它地點感知的功能結(jié)合起來,比如定期的地點更新或者行為認知等,這些會在該系列課程中的后續(xù)課程中展開。
在下一節(jié)課程中,會向你展示請求和接收activity更新。在定期的間隔中,定位服務(wù)可以給你發(fā)送有關(guān)用戶當(dāng)前物理行為的信息。基于這一信息,你可以改變你的應(yīng)用行為,例如,如果你檢測到用戶在步行而不在開車,你可以增加定期更新的間隔。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

