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范围时会通知应用,应用接受到通知后可采取相应的操作,例如在通知栏显示这样的通知:

Android Geofence的学习(三)总结、Demo和问题_第1张图片

一、方法

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,改过之后就没有问题了。实际中还应该根据项目需求调整精确度。       Android Geofence的学习(三)总结、Demo和问题_第2张图片

2、Geofence范围问题  

    Geofence只能描述一个圆形区域,这点似乎很头疼,还应该注意半径的大小,太小(小于1000M)不容易触发。

3、国内阉割Google服务问题      

由于国内许多手机阉割了Google服务,所以不很可能不能使用该服务。不知道有没有相应的办法呢?

4、虚拟机不支持GooglePlayServices的问题

    虽然有办法在虚拟机中装入GooglePlayServices,但是根据Google官方的解释,虚拟机是不支持的(我也测试过,的确不行)。

三、源码下载

源码地址http://download.csdn.net/detail/btyh17mxy/5532345

你可能感兴趣的:(Android)