Android Geofence的学习(二)继续翻译官方文档

保佑我六级能过。害羞如有错误欢迎指正。

转载请注明,原地址:http://blog.csdn.net/btyh17mxy/article/details/9007287

官方文档地址:https://developer.android.com/training/location/geofencing.html

创建并监视一个Geofence

地理围栏结合用户当前的位置和附近特点

Geofencing combines awareness of the user's current location with awareness of nearby features, defined as the user's proximity to locations that may be of interest. 为了创建一个兴趣点,你需要指定它的经纬度。为了调整Geofence的范围,你需要增加半径。经纬度和半径定义了一个地理围栏。 你可以同时拥有多个活跃的地理围栏。

位置服务将地理围栏视为一个区域而不是一个点和与这个点的接近度。这允许它探测用户何时进入和退出该地理围栏。对于每个地理围栏,你可以向位置服务请求发送给你退出事件、进入事件或者两者。你也可以指定一个以毫秒为单位的地理围栏的过期时间。 在地理围栏到期后,位置服务将会自动移除它。

请求监视地理围栏


请求地理围栏监视的第一步时请求必要的权限。为了使用地理围栏,你的APP必须请求ACCESS_FINE_LOCATION权限。为了请求该权限,在<manifest>中增加如下子节点:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

检查Google Play Services

位置服务使 Google Play services APK的一部分。 由于用户设备的状态很难预测,你应该总是在试图使用位置服务前检查该app是否已经安装。为了检查APK是否安装,可调用GooglePlayServicesUtil.isGooglePlayServicesAvailable()方法,它返回API参考文档中给出整形结果中的一个。如果发生错误, 就调用GooglePlayServicesUtil.getErrorDialog()方法来本地化一个对话框提示用户采取正确的操作,然后将对话框显示在 DialogFragment上。该对话框可以让用户纠正问题,在这种情况下,Google Play services可能会发送一个结果返回给你的Activity。要处理这样的结果,重写该方法onActivityResult()

注意: 为了使你的app兼容1.6或之后的版本,显示DialogFragment的Activity一定要继承FragmentActivity而不是Activity。使用 FragmentActivity 也允许你调用 getSupportFragmentManager() 来显示 DialogFragment.

既然你通常需要在代码中的多个地方检察Google Play services,最好定义一个封装这个检查的方法,然后再在每次尝试连接之前调用它。下面的代码片段包含了检查Google Play services需要的所有代码:

public class MainActivity extends FragmentActivity {
    ...
    // 全局常量
    /*
     * 定义发送到Google Play services的请求码
     * 这个代码是在Activity.onActivityResult返回的
     */
    private final static int
            CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;
    ...
    // 定义一个 DialogFragment 用于显示 error dialog
    public static class ErrorDialogFragment extends DialogFragment {
        // 包含error dialog的全局字段
        private Dialog mDialog;
        ...
        // 默认构造函数。 设置对话框字段为 null
        public ErrorDialogFragment() {
            super();
            mDialog = null;
        }
        ...
        // Set the dialog to display
        public void setDialog(Dialog dialog) {
            mDialog = dialog;
        }
        ...
        // 向DialogFragment返回一个Dialog.
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return mDialog;
        }
        ...
    }
    ...
    /*
     * 处理由Google Play services 返回给 FragmentActivity的结果
     */
     @Override
    protected void onActivityResult(
            int requestCode, int resultCode, Intent data) {
        // 根据原始请求代码确定做什么
        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() {
        // 检查 Google Play services 是否可用
        int resultCode =
                GooglePlayServicesUtil.
                        isGooglePlayServicesAvailable(this);
        // 如果 Google Play services 可用
        if (ConnectionResult.SUCCESS == resultCode) {
            // 在debug模式下, log 这个状态
            Log.d("Geofence Detection",
                    "Google Play services is available.");
            // 继续
            return true;
        // 由于某些原因Google Play services不可用
        } else {
            // 获取错误代码
            int errorCode = connectionResult.getErrorCode();
            // 从 Google Play services 获取error dialog
            Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
                    errorCode,
                    this,
                    CONNECTION_FAILURE_RESOLUTION_REQUEST);

            // 如果 Google Play services 能提供一个error dialog
            if (errorDialog != null) {
                // 给这个error dialog创建一个新的 DialogFragment
                ErrorDialogFragment errorFragment =
                        new ErrorDialogFragment();
                // Set the dialog in the DialogFragment
                errorFragment.setDialog(errorDialog);
                // 在 DialogFragment 中显示这个error dialog
                errorFragment.show(
                        getSupportFragmentManager(),
                        "Geofence Detection");
            }
        }
    }
    ...
}

下面的章节调用该方法来验证Google Play services 是否可用。

要使用地理围栏,首先要定义你要监视的地理围栏。尽管你通常在本地数据库中储存GeoFence或者从网上下载它,你需要用Geofence.Builder创建一个Geofence,并作为一个实例传给Location Services。每个Geofence 对象包含以下信息。

Latitude(维度), longitude(经度), 和 radius(半径)
为Geofence定义一个圆形区域。 使用经纬度标记一个感兴趣的位置,然后使用radius(半径)调整在geofence探测到之前用户需要多么靠近那个点。半径越大,用户越可能通过接近geofence触发geofence taansition alert(过度警报)。例如,在geofencing app中使用大的半径来控制当用户回家时打开用户家的灯,也许会造成即使用户只是匆匆而过也会打开灯。
Expiration time(过期时间)
geofence 持续活动的时间。一旦到达过期时间, Location Services 会删除这个geofence。大多数时候,你应该制定一个过期时间,但是你也许会希望在用户家或者工作的地方弄一个永久的geofence。
Transition type(过渡类型)
Location Services 可以探测当用户在geofence半径范围内(视为进入)或者在半径外(视为离开),或者两者都探测。
Geofence ID(Geofence 编号)
一个同geofence一起储存的字符串。你应该让它是唯一的-,因而你可以用它来从  Location Services tracking(位置服务追踪)中删除geofence。

定义地理围栏储存

一个 geofencing app (地理围栏app?)需要从永久储存中读写geofence数据。你不应该是用Geofence对象来做这项工作,而应该使用诸如数据库技术的可以储存一组相关数据的方法来储存。

下面的片段使用SharedPreferences来说明geofence数据的永久储存。类SimpleGeofence,类似数据库记录,以“扁平化”形式(我不明白)储存一个geofence对象。类SimpleGeofenceStore ,类似数据库,从SharedPreferences实例读写SimpleGeofence数据。

public class MainActivity extends FragmentActivity {
    ...
    /**
     * 由其中心和半径定义的一个geofence对象
     */
    public class SimpleGeofence {
            // 实例变量
            private final String mId;
            private final double mLatitude;
            private final double mLongitude;
            private final float mRadius;
            private long mExpirationDuration;
            private int mTransitionType;

        /**
         * @param geofenceId Geofence的请求ID
         * @param latitude Geofence中心的纬度.
         * @param longitude Geofence中心的经度
         * @param radius Geofence的半径
         * @param expiration Geofence到期的时间
         * @param transition Geofence过度类型
         */
        public SimpleGeofence(
                String geofenceId,
                double latitude,
                double longitude,
                float radius,
                long expiration,
                int transition) {
            // 在构造函数中设定实例变量的值
            this.mId = geofenceId;
            this.mLatitude = latitude;
            this.mLongitude = longitude;
            this.mRadius = radius;
            this.mExpirationDuration = expiration;
            this.mTransitionType = transition;
        }
        // getter们
        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;
        }
        /**
         * 使用SimpleGeofence创建一个Location Services Geofence 对象
         *
         * @return Geofence对象
         */
        public Geofence toGeofence() {
            // Build a new Geofence object
            return new Geofence.Builder()
                    .setRequestId(getId())
                    .setTransitionTypes(mTransitionType)
                    .setCircularRegion(
                            getLatitude(), getLongitude(), getRadius())
                    .setExpirationDuration(mExpirationDuration)
                    .build();
        }
    }
    ...
    /**
     * 使用SharedPreferences实现储存Geofence的数据
     */
    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";
        // 创建私有访问的SharedPreferences储存
        public SimpleGeofenceStore(Context context) {
            mPrefs =
                    context.getSharedPreferences(
                            SHARED_PREFERENCES,
                            Context.MODE_PRIVATE);
        }
        /**
         * 根据储存的geofence的id返回geofence, 如果id不存在返回空
         *
         * @param id 储存的geofence的id
         * @return 由其中心和半径定义的geofence
         */
        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;
        }
    }
    ...
}

创建Geofence对象

下面的片段时用SimpleGeofence 和 SimpleGeofenceStore 类从UI获取geofence数据,储存在SimpleGeofence 对象中,并最终将其储存在 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),你还需要向Location services提供Intent,当Location services探测到geofence过渡的时候会把它发送给你的app。

定义一个地理围栏过渡的Intent

从Location Services传递过来的Intent 可以触发你的app中的各种活动,但你不应该有它开始的Activity或fragment,因为组件应该只在响应用户操作变得可见。在许多情况下,IntentService 是一个来处理这个intent的好方法。 IntentService  可以发布通知,做长时间运行的后台工作,将intent发送到其他services,或发送broadcast 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);
    }
    ...
}

现在你拥有发送请求到LocationService来监视geofences的所有代码。

发送监控请求

发送监控请求需要两个异步操作。第一个操作获取这个请求需要的location client,第二个操作使用这个client创建请求。在这两种情况下,Location Services都在完成该操作的时候调用一个回调方法。处理这些操作的最好方法是 chain together the method calls(不明白)。下面的代码片段演示了如何设置一个Activity,定义方法,并以正确的顺序调用它们。

首先修改Activity的定义,导入必要的接口。导入如下接口:

ConnectionCallbacks
指定当location client连接或断开时Location Services调用的方法。(当location clent 连接或断开时Location services会调用该方法)
OnConnectionFailedListener
指定如果在试图连接location client时发生错误Location Services回调的方法。
OnAddGeofencesResultListener
指定当Location Services 添加了一个geofence时它回调的方法。

例如:

public class MainActivity extends FragmentActivity implements
        ConnectionCallbacks,
        OnConnectionFailedListener,
        OnAddGeofencesResultListener {
    ...
}
开始请求过程

接下来,定义一个通过连接Location Services开始请求过程的方法。通过设置一个全局变量,将其作为一个添加geofence的请求。这使您可以通过回调ConnectionCallbacks.onConnected() 来添加和移除geofence,这些将在后面的章节描述。

为了防止当你同时开始两个请求是出现冲突,需要定义一个布尔型标志变量来标记当前进行的请求状态:

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.
             */
        }
    }
    ...
}
发送请求来添加geofence

在你实现的 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() 会立即返回,但是请求的状态(是否成功)需要等到Location Services调用onAddGeofencesResult() 才明确。一旦该方法被调用,你就能判断请求是否成功。

检查Location Services返回的结果

一旦Location Services调用你实现的回调方法 onAddGeofencesResult(),就表明请求完成,此时应检查传来的状态码。如果请求成功,那么你请求的geofence就处于活动状态。如果请求不成功,那么显然你的geofence就处于非活动状态,你应该重试请求或者报告错误。例如:

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();
    }
    ...
}

处理连接断开的情况

由于某些原因,Location Services会在你调用disconnect()之前断开链接。为了处理这种情况,导入onDisconnected()接口。在这个方法里,设置请求标记来表示请求没有处于运行状态,并删除这个client:

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;
    }
    ...
}

处理连接错误

除了正常的回调之外,你也应该为Location Services提供一个当发生错误时调用的回调方法。这个回调方法可以重用你为了检查Google Play services定义的DialogFragment类。同样的它也可以重用你复写的用于接收Google Play services返回结果的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");
            }
        }
    }
    ...
}

处理Geofence过度


当Location Services探测到用户进入或离开geofence,将发用一个包含在你请求添加geofences是置入的PendingIntent中的 Intent这个Intent 是。

定义一个 IntentService

下面的章节展示了如何定义一个 IntentService ,它将在geofence过度发生时发送一个消息(norification)。当用户点击这个消息的时候,这个app的主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));
        }
    }
    ...
}

在manifest中注册IntentService 

为了像系统确认IntentService ,向app的manifest添加<service> 标签。例如:

<service
    android:name="com.example.android.location.ReceiveTransitionsIntentService"
    android:label="@string/app_name"
    android:exported="false">
</service>

注意,你不必为service制定intent filters ,因为它只接收特定的intents。传来的geofence过度intents是怎样创建的在章节Send the monitoring request中。

停止Geofence监视


停止geofence监视,你需要移除它们。你可以移除一组特定的geofences或者所有与同一个 PendingIntent相关联的geofence。过程跟添加geofences相似。第一步是为移除geofence的请求获取一个location client,第二步是用这个client发起请求。

当完成一处geofences操作后,LocationServices会调用由接口LocationClient.OnRemoveGeofencesResultListener定义的回调方法。在你的类中导入上述接口,并定义下面两个方法:

onRemoveGeofencesByPendingIntentResult()
当Location Services完成可能由 removeGeofences(PendingIntent, LocationClient.OnRemoveGeofencesResultListener)提出的移除所有geofences请求时调用
onRemoveGeofencesByRequestIdsResult(List<String>, LocationClient.OnRemoveGeofencesResultListener)
当Location Services完成可能由 removeGeofences(List<String>, LocationClient.OnRemoveGeofencesResultListener)提出的移除一组geofence的请求时调用

下面的片段时这些方法的实现示例:

移除所有Geofences

由于移除geofences使用了跟你用来添加geofences相同的方法,首先定义另一种请求类型:

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.
             */
        }
    }
    ...
}

当Location Services 调用回调方法时表明连接已经打开,此时可发起请求来移除所有的geofences。在请求完成后断开client连接。就像:

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) 立即返回,但是请求结果需要等到Location Services调用 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();
    }
    ...
}

移除个别的geofences

移除个别或一组geofences的过程跟移除所有的geofences相似。 指定你要移除的geofence,添加它们的geofenceID到一个String对象的List中。将这个List 传递给 removeGeofences 的重载,带上合适的appropriate signature。 然后这个方法就开始了移除进程。

开始先添加按list移除geofences的请求类型,并且也要为储存geofences的list添加全局变量(and also add a global variable for storing the list of geofences):

    ...
    // 控制移除请求类型的枚举
    public enum REQUEST_TYPE = {ADD, REMOVE_INTENT, REMOVE_LIST}
    // Store the list of geofence Ids to remove
    String<List> mGeofencesToRemove;

然后,定义一个包含你要移除的geofences的list。例如,下面的片段移除geofenceID为“1”的geofence: 

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.
             */
        }
    }
    ...
}

当Location Services调用这个回调方法就表明conntction已经打开,可以发起请求来移除list中的geofences。当完成请求时关闭这个client连接。就像:

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()Location Services 调用这个回调方法来表示移除list中的geofences的操作已经完成。在这个方法中,根据传递来的状态码采取相应的动作:

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();
    }
    ...
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Android高德地图提供了电子围栏Geofence)功能,通过该功能,开发者可以在地图上定义一个虚拟的围栏区域,并在进出该区域时触发相应的事件或提醒。 以下是实现电子围栏的步骤: 1. 添加高德地图依赖库,可以在gradle文件中添加以下代码: ``` implementation 'com.amap.api:location:x.y.z' ``` 2. 在AndroidManifest.xml文件中添加以下权限: ``` <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/> ``` 3. 在代码中创建Geofence区域,可以通过以下代码实现: ``` List<Geofence> geofenceList = new ArrayList<>(); geofenceList.add(new Geofence.Builder() .setRequestId("围栏id") .setCircularRegion(纬度, 经度, 半径) .setExpirationDuration(Geofence.NEVER_EXPIRE) .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT) .build()); ``` 其中,setRequestId设置围栏id,setCircularRegion设置圆形区域的中心坐标和半径,setExpirationDuration设置围栏的过期时间,setTransitionTypes设置进入或离开围栏时触发的事件。 4. 创建Geofence请求对象并添加围栏,可以通过以下代码实现: ``` GeofencingRequest geofenceRequest = new GeofencingRequest.Builder() .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER) .addGeofences(geofenceList) .build(); ``` 其中,setInitialTrigger设置初始化时触发的事件,addGeofences添加围栏。 5. 注册Geofence监听器,可以通过以下代码实现: ``` GeofencingClient geofencingClient = LocationServices.getGeofencingClient(this); geofencingClient.addGeofences(geofenceRequest, geofencePendingIntent) .addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { Log.d(TAG, "添加围栏成功"); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { Log.e(TAG, "添加围栏失败:" + e.getMessage()); } }); ``` 其中,geofencePendingIntent是一个PendingIntent对象,用于指定进出围栏时触发的事件。 6. 在Geofence监听器中处理进出围栏的事件,可以通过以下代码实现: ``` public class GeofenceBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (GeofencingEvent.fromIntent(intent).hasError()) { Log.e(TAG, "Geofence error"); return; } int transitionType = GeofencingEvent.fromIntent(intent).getGeofenceTransition(); if (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER) { Log.d(TAG, "进入围栏"); } else if (transitionType == Geofence.GEOFENCE_TRANSITION_EXIT) { Log.d(TAG, "离开围栏"); } } } ``` 其中,GeofencingEvent.fromIntent获取Geofence事件对象,getGeofenceTransition获取进出围栏的事件类型。 以上是Android高德地图电子围栏的实现步骤,希望对你有所帮助。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值