转载请注明:http://blog.csdn.net/btyh17mxy/article/details/9038443
官方给出了一个demo:http://developer.android.com/shareables/training/GeofenceDetection.zip
Geofence是一个基于Google Play Services的虚拟地理区域,是一个由中心点经纬度和半径描述的圆形区域。Location Service会以低功耗的方式获取用户的位置,当用户进入或退出Geofence范围时会通知应用,应用接受到通知后可采取相应的操作,例如在通知栏显示这样的通知:
一、方法
1、检查Google Play Services是否可用
/**
* 驗證Google Play Services是否可用
*
* @return 可用返回真否則返回假
*/
private boolean servicesConnected() {
// 檢查服務是否可用
int resultCode = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(this);
// 如果可用
if (ConnectionResult.SUCCESS == resultCode) {
// Log狀態
Log.d(GeofenceUtils.APPTAG,
getString(R.string.play_services_available));
return true;
// 如果不可用
} else {
// 顯示錯誤提示對話框
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(resultCode,
this, 0);
if (dialog != null) {
ErrorDialogFragment errorFragment = new ErrorDialogFragment();
errorFragment.setDialog(dialog);
errorFragment.show(getSupportFragmentManager(),
GeofenceUtils.APPTAG);
}
return false;
}
}
在应用启动的时候就应该检查一下GoogleServices是否可用,如果不可用的话是不能使用Geofence的。
PS:可能是输入法抽风,在Eclipse里面总是打出繁体字来2、连接和断开LocationClient、添加和移除Geofence(s)、处理连接异常
LocationClient的原型是:
com.google.android.gms.location.LocationClient.LocationClient(Context context, ConnectionCallbacks connectionCallbacks, OnConnectionFailedListener connectionFailedListener)
在官方的例子中是写了一个持有主调Activity强引用并导入上述几个回调接口的类来处理添加Geofence请求。像这样:
/**
* 连接Google Play Services和请求Geofence
*
*
* Note: 必须首先验证Google Play Services可用
* Use GooglePlayServicesUtil.isGooglePlayServicesAvailable() to check.
*
* 调用 AddGeofence()方法添加Geofence
*
*/
public class GeofenceRequester
implements
OnAddGeofencesResultListener,
ConnectionCallbacks,
OnConnectionFailedListener {
// 主调Activity的强引用
private final Activity mActivity;
// Stores the PendingIntent used to send geofence transitions back to the app
private PendingIntent mGeofencePendingIntent;
// Stores the current list of geofences
private ArrayList mCurrentGeofences;
// Stores the current instantiation of the location client
private LocationClient mLocationClient;
/*
* Flag that indicates whether an add or remove request is underway. Check this
* flag before attempting to start a new request.
*/
private boolean mInProgress;
public GeofenceRequester(Activity activityContext) {
// Save the context
mActivity = activityContext;
// Initialize the globals to null
mGeofencePendingIntent = null;
mLocationClient = null;
mInProgress = false;
}
/**
* Set the "in progress" flag from a caller. This allows callers to re-set a
* request that failed but was later fixed.
*
* @param flag Turn the in progress flag on or off.
*/
public void setInProgressFlag(boolean flag) {
// Set the "In Progress" flag.
mInProgress = flag;
}
/**
* Get the current in progress status.
*
* @return The current value of the in progress flag.
*/
public boolean getInProgressFlag() {
return mInProgress;
}
/**
* Returns the current PendingIntent to the caller.
*
* @return The PendingIntent used to create the current set of geofences
*/
public PendingIntent getRequestPendingIntent() {
return createRequestPendingIntent();
}
/**
* Start adding geofences. Save the geofences, then start adding them by requesting a
* connection
*
* @param geofences A List of one or more geofences to add
*/
public void addGeofences(List geofences) throws UnsupportedOperationException {
/*
* Save the geofences so that they can be sent to Location Services once the
* connection is available.
*/
mCurrentGeofences = (ArrayList) geofences;
// If a request is not already in progress
if (!mInProgress) {
// Toggle the flag and continue
mInProgress = true;
// Request a connection to Location Services
requestConnection();
// If a request is in progress
} else {
// Throw an exception and stop the request
throw new UnsupportedOperationException();
}
}
/**
* 请求链接Location Service
*
*/
private void requestConnection() {
getLocationClient().connect();
}
/**
* 返回已经存在的Location Client 或者创建一个新的
*
* @return A LocationClient object
*/
private GooglePlayServicesClient getLocationClient() {
if (mLocationClient == null) {
mLocationClient = new LocationClient(mActivity, this, this);
}
return mLocationClient;
}
/**
* 一旦连接可用,发送请求添加Goefence
*/
private void continueAddGeofences() {
// Get a PendingIntent that Location Services issues when a geofence transition occurs
mGeofencePendingIntent = createRequestPendingIntent();
// Send a request to add the current geofences
mLocationClient.addGeofences(mCurrentGeofences, mGeofencePendingIntent, this);
}
/**
* 处理添加Geofence结果
*/
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 = mActivity.getString(R.string.add_geofences_result_success,
Arrays.toString(geofenceRequestIds));
// In debug mode, log the result
Log.d(GeofenceUtils.APPTAG, msg);
// Create an Intent to broadcast to the app
broadcastIntent.setAction(GeofenceUtils.ACTION_GEOFENCES_ADDED)
.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, msg);
// If adding the geofences failed
} else {
/*
* Create a message containing the error code and the list
* of geofence IDs you tried to add
*/
msg = mActivity.getString(
R.string.add_geofences_result_failure,
statusCode,
Arrays.toString(geofenceRequestIds)
);
// Log an error
Log.e(GeofenceUtils.APPTAG, msg);
// Create an Intent to broadcast to the app
broadcastIntent.setAction(GeofenceUtils.ACTION_GEOFENCE_ERROR)
.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, msg);
}
// Broadcast whichever result occurred
LocalBroadcastManager.getInstance(mActivity).sendBroadcast(broadcastIntent);
// Disconnect the location client
requestDisconnection();
}
/**
* Get a location client and disconnect from Location Services
*/
private void requestDisconnection() {
// A request is no longer in progress
mInProgress = false;
getLocationClient().disconnect();
}
/**
* Called by Location Services once the location client is connected.
*
* Continue by adding the requested geofences.
*/
public void onConnected(Bundle arg0) {
// If debugging, log the connection
Log.d(GeofenceUtils.APPTAG, mActivity.getString(R.string.connected));
// Continue adding the geofences
continueAddGeofences();
}
/**
* Called by Location Services once the location client is disconnected.
*/
public void onDisconnected() {
// Turn off the request flag
mInProgress = false;
// In debug mode, log the disconnection
Log.d(GeofenceUtils.APPTAG, mActivity.getString(R.string.disconnected));
// Destroy the current location client
mLocationClient = null;
}
/**
* 获取一个发送给Location services的PendingIntent.Location Services检测到Geofence过度的时
* 候会调用其中的Intent
*
* @return A PendingIntent for the IntentService that handles geofence transitions.
*/
private PendingIntent createRequestPendingIntent() {
// 如果PendingIntent已經存在
if (null != mGeofencePendingIntent) {
// 返回存在的PendingIntent
return mGeofencePendingIntent;
// 如果PendingIntent不存在
} else {
// 創建一個新的
Intent intent = new Intent(mActivity, ReceiveTransitionsIntentService.class);
/*
* 要使用PendingIntent.FLAG_UPDATE_CURRENT標籤創建,否則Location Services無法匹配
*/
return PendingIntent.getService(
mActivity,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
}
/**
* 實現OnConnectionFailedListener.onConnectionFailed接口
* 如果鏈接或斷開請求失敗,會調用該方法
*/
public void onConnectionFailed(ConnectionResult connectionResult) {
// 關閉請求標籤
mInProgress = false;
/*
* Google Play services 能夠解析一部分錯誤。如果錯誤能夠被解析,嘗試
* 發送Inten來啓動能夠解析該錯誤的Google Play services activity
*/
if (connectionResult.hasResolution()) {
try {
// 啓動嘗試解析該錯誤的Activity
connectionResult.startResolutionForResult(mActivity,
GeofenceUtils.CONNECTION_FAILURE_RESOLUTION_REQUEST);
/*
* Thrown if Google Play services canceled the original
* PendingIntent
*/
} catch (SendIntentException e) {
// Log the error
Log.e(GeofenceUtils.APPTAG,e.toString());
e.printStackTrace();
}
/*
* If no resolution is available, put the error code in
* an error Intent and broadcast it back to the main Activity.
* The Activity then displays an error dialog.
* is out of date.
*/
} else {
Intent errorBroadcastIntent = new Intent(GeofenceUtils.ACTION_CONNECTION_ERROR);
errorBroadcastIntent.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES)
.putExtra(GeofenceUtils.EXTRA_CONNECTION_ERROR_CODE,
connectionResult.getErrorCode());
LocalBroadcastManager.getInstance(mActivity).sendBroadcast(errorBroadcastIntent);
}
}
}
移除的操作跟这个类似,所以不再啰嗦。
3、接收并处理Notification
在创建的时候就需要指定一个用于接收Notification的IntentServeice,像这样
// 創建一個新的
// 使用ReceiveTransitionsIntentService处理Notification
Intent intent = new Intent(mActivity, ReceiveTransitionsIntentService.class);
/*
* 要使用PendingIntent.FLAG_UPDATE_CURRENT標籤創建,否則Location Services無法匹配
*/
return PendingIntent.getService(
mActivity,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
/**
* 本类以接受包含触发事件的Geofence(s)的过度类型和GeofenceID的Intent的形式,接受Location Services
* 发来的Geofence过度事件。
*/
public class ReceiveTransitionsIntentService extends IntentService {
/**
* Sets an identifier for this class' background thread
*/
public ReceiveTransitionsIntentService() {
super("ReceiveTransitionsIntentService");
}
/**
* 处理发送来的Intent
* @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) {
Log.d(GeofenceUtils.APPTAG, "触发");
// Create a local broadcast Intent
Intent broadcastIntent = new Intent();
// Give it the category for all intents sent by the Intent Service
broadcastIntent.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES);
// First check for errors
if (LocationClient.hasError(intent)) {
// Get the error code
int errorCode = LocationClient.getErrorCode(intent);
// Get the error message
String errorMessage = LocationServiceErrorMessages.getErrorString(this, errorCode);
// Log the error
Log.e(GeofenceUtils.APPTAG,
getString(R.string.geofence_transition_error_detail, errorMessage)
);
// Set the action and error message for the broadcast intent
broadcastIntent.setAction(GeofenceUtils.ACTION_GEOFENCE_ERROR)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, errorMessage);
// Broadcast the error *locally* to other components in this app
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);
// If there's no error, get the transition type and create a notification
} else {
// Get the type of transition (entry or exit)
int transition = LocationClient.getGeofenceTransition(intent);
// Test that a valid transition was reported
if (
(transition == Geofence.GEOFENCE_TRANSITION_ENTER)
||
(transition == Geofence.GEOFENCE_TRANSITION_EXIT)
) {
// Post a notification
List geofences = LocationClient.getTriggeringGeofences(intent);
String[] geofenceIds = new String[geofences.size()];
for (int index = 0; index < geofences.size() ; index++) {
geofenceIds[index] = geofences.get(index).getRequestId();
}
String ids = TextUtils.join(GeofenceUtils.GEOFENCE_ID_DELIMITER,geofenceIds);
String transitionType = getTransitionString(transition);
sendNotification(transitionType, ids);
// Log the transition type and a message
Log.d(GeofenceUtils.APPTAG,
getString(
R.string.geofence_transition_notification_title,
transitionType,
ids));
Log.d(GeofenceUtils.APPTAG,
getString(R.string.geofence_transition_notification_text));
// An invalid transition was reported
} else {
// Always log as an error
Log.e(GeofenceUtils.APPTAG,
getString(R.string.geofence_transition_invalid_type, transition));
}
}
}
/**
* 当检测到过度事件的时候向通知栏发送一个通知
* 如果用户点击这个通知,就打开主Activity
* @param transitionType The type of transition that occurred.
*/
private void sendNotification(String transitionType, String ids) {
// Create an explicit content Intent that starts the main Activity
Intent notificationIntent =
new Intent(getApplicationContext(),MainActivity.class);
// Construct a task stack
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Adds the main Activity to the task stack as the parent
stackBuilder.addParentStack(MainActivity.class);
// Push the content Intent onto the stack
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack
PendingIntent notificationPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// Set the notification contents
builder.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(
getString(R.string.geofence_transition_notification_title,
transitionType, ids))
.setContentText(getString(R.string.geofence_transition_notification_text))
.setContentIntent(notificationPendingIntent);
// Get an instance of the Notification manager
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}
/**
* 将过度类型代码转换成文字
* @param transitionType A transition type constant defined in Geofence
* @return A String indicating the type of transition
*/
private String getTransitionString(int transitionType) {
switch (transitionType) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
return getString(R.string.geofence_transition_entered);
case Geofence.GEOFENCE_TRANSITION_EXIT:
return getString(R.string.geofence_transition_exited);
default:
return getString(R.string.geofence_transition_unknown);
}
}
}
二、问题
1、精确度问题
由于Location Services并不是用GPS,所以精确度不高,虽然根据测试的结果精确度在50M左右(如下图是通过LocationClient.getLastLocation()获取到的GPS坐标),但我觉得它远没有那么精确。昨天之所以一直没能触发警报就是因为半径设的太小了,今天Google一下发现stackoverflow上有人说半径不宜小于1000M,改过之后就没有问题了。实际中还应该根据项目需求调整精确度。
2、Geofence范围问题
Geofence只能描述一个圆形区域,这点似乎很头疼,还应该注意半径的大小,太小(小于1000M)不容易触发。
3、国内阉割Google服务问题
由于国内许多手机阉割了Google服务,所以不很可能不能使用该服务。不知道有没有相应的办法呢?
4、虚拟机不支持GooglePlayServices的问题
虽然有办法在虚拟机中装入GooglePlayServices,但是根据Google官方的解释,虚拟机是不支持的(我也测试过,的确不行)。
三、源码下载
源码地址http://download.csdn.net/detail/btyh17mxy/5532345