Android(安卓)Geofence的学习(三)总结、Demo和问题
转载请注明: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
更多相关文章
- 解决通知关闭Toast失效问题
- 在命令行中通过adb shell am broadcast发送广播通知
- android 通知栏背景颜色跟随app导航栏背景颜色变化而变化
- Android(安卓)学习记录 之 notification
- Android(安卓)之 Shape (圆角输入框)
- Android中View和SurfaceView
- 在android状态栏上添加多个图标
- Android(安卓)Shell命令dumpsys
- android 保存图片到相册并正常显示