基于Android的谷歌地图地理围栏功能开发

发布时间:2019-05-31
技术:谷歌地理围栏+谷歌地图play-services-maps:16.0.0+谷歌题图play-services-location:16.0.0

概述

国内Android应用基于百度和高德的地理围栏已经很成熟了,而在国外,百度和高德等的地理围栏支持不够,且国外的用户很少使用国内的地图应用,大都使用谷歌地图,所以针对谷歌地图开发了一个地理围栏的demo

详细

一、android使用谷歌地图权限

Android上使用谷歌地图

必备条件:翻墙、Android设备上安装Google Play Service

由于谷歌在国内已经被墙了,所以我们只能使用翻墙软件使用谷歌地图,而使用谷歌地图的同时,我们需要安装Google Play Service,这个翻墙之后用google浏览器下载就行了。

接下来是简单的教程:

1、注册账号并登录 https://code.google.com/apis/console/

2、创建项目并选择项目、点击凭据


3、创建凭据、需要提供包名和AS的SHA1指纹证书

AS的SHA1的指纹证书,可以通过命令行打印,在命令行进入jdk的目录,输入:

keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android

结果如下


最后、保存、将生成的key值写进谷歌地图工程的配置文件中,如下

二、开发基于谷歌的地图围栏功能

1、相关权限

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

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

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

以上三个权限为定位和加载地图所用

2、注册监听地理围栏服务

<service
            android:name=".GeofenceIntentService"
            android:enabled="true"
            android:exported="true" />


3、创建地理围栏管理器

package geofence.dan.com.googlegeofence;

import android.Manifest;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.util.Log;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingClient;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;

import java.util.ArrayList;

/**
 * Created by Dan on 2019/5/15.
 */

public class GeofenceManager implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback<Status>, LocationListener {

    private static final String TAG = "Dan";
    private static GoogleApiClient googleApiClient;
    private static GeofenceManager _singleInstance;
    GeofencingClient geofencingClient;
    private static Context appContext;
    private static final String ERR_MSG = "Application Context is not set!! " +
            "Please call GeofenceSngleton.init() with proper application context";
    private PendingIntent mGeofencePendingIntent;
    private static ArrayList<Geofence> mGeofenceList;


    private GeofenceManager() {
        if (appContext == null)
            throw new IllegalStateException(ERR_MSG);
        this.googleApiClient = new GoogleApiClient.Builder(this.appContext)
                .addApi(LocationServices.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();
        this.googleApiClient.connect();
        geofencingClient = LocationServices.getGeofencingClient(appContext);
        mGeofenceList = new ArrayList<Geofence>();
    }
    public static void init(Context applicationContext) {
        appContext = applicationContext;
    }
    public static GeofenceManager getInstance() {
        if (_singleInstance == null)
            synchronized (GeofenceManager.class) {
                if (_singleInstance == null)
                    _singleInstance = new GeofenceManager();
            }
        return _singleInstance;
    }

    @Override
    public void onConnected(Bundle bundle) {
        createLocationRequest();
    }

    @Override
    public void onConnectionSuspended(int i) {
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {

    }
    //添加地理围栏
    public void addGeofence(GeofenceInfo geofenceInfo) {
        mGeofenceList.add(new Geofence.Builder()
                .setRequestId(geofenceInfo.getUid())
                .setCircularRegion(
                        geofenceInfo.getLatitude(),
                        geofenceInfo.getLongitude(),
                        geofenceInfo.getRadius()
                )
                .setExpirationDuration(5000000)
                .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT)
                .build());
    }
    /**
     * 构建地理围栏请求
     * */
    private GeofencingRequest getGeofencingRequest() {
        GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
        builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
        builder.addGeofences(mGeofenceList);
        return builder.build();
    }
    /**
     * 返回地理围栏监听服务的Intent
     * */
    private PendingIntent getGeofencePendingIntent() {
        if (mGeofencePendingIntent != null) {
            return mGeofencePendingIntent;
        }
        Intent intent = new Intent(appContext, GeofenceIntentService.class);
        return PendingIntent.getService(appContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    public void startGeofencing() {
        if (!googleApiClient.isConnected()) {
            Log.i(TAG,"无法连接谷歌服务");
            return;
        }
        if(checkPermission())
            geofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent()).addOnSuccessListener(new OnSuccessListener<Void>() {
                @Override
                public void onSuccess(Void aVoid) {
                    Log.i(TAG,"add success");
                    Log.i(TAG,"地理围栏服务开启");
                }
            }).addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    Log.i(TAG,"add fail");
                }
            });
    }

    public static boolean checkPermission(){
        if (ActivityCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
        return true;
    }
    public void removeGeofence() {
        geofencingClient.removeGeofences(getGeofencePendingIntent());
    }

    @Override
    public void onResult(Status status) {
        if (status.isSuccess()) {
            Log.i(TAG,"执行地理围栏服务");
        } else {
            Log.i(TAG,"地理围栏服务错误");
        }
    }

    public static long UPDATE_INTERVAL_IN_MILLISECONDS = 10000;

    public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
            UPDATE_INTERVAL_IN_MILLISECONDS / 2;
    public LocationRequest mLocationRequest;

    /**
     * 创建位置请求对象mLocationRequest,封装监听参数
     */
    protected void createLocationRequest() {
        mLocationRequest = new LocationRequest();
        if(checkPermission())
            LocationServices.GeofencingApi.addGeofences(googleApiClient, getGeofencingRequest(), getGeofencePendingIntent()).setResultCallback(this);
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        startLocationUpdates();
    }

    /**
     * 开始监听位置变化
     */
    protected void startLocationUpdates() {
        if (ActivityCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, mLocationRequest, this);
    }
    @Override
    public void onLocationChanged(Location location) {
    }
}


各个函数模块都有注释,不做赘述

3、主Activity调用类

package geofence.dan.com.googlegeofence;

import android.Manifest;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMyLocationButtonClickListener;
import com.google.android.gms.maps.GoogleMap.OnMyLocationClickListener;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PatternItem;

public class MainActivity extends AppCompatActivity
        implements
        OnMyLocationButtonClickListener,
        OnMyLocationClickListener,
        OnMapReadyCallback,
        GoogleMap.OnMapClickListener,
        ActivityCompat.OnRequestPermissionsResultCallback {
    private static final String TAG = "Dan";
    private static final int LOCATION_PERMISSION_REQUEST_CODE = 202;
    private GoogleMap mMap;
    private GeofenceManager geofenceManager;
    private EditText etGeoLatitude,etGeoLongitude,etGeoArea,etGeoRefresh;
    private String strGgeoLatitude,strGeoLongitude,strGeoArea,strGeoRefresh;
    private Button btnGeoSubmit;
    private boolean mPermissionDenied = false;
    private static final double DEFAULT_RADIUS_METERS = 100;
    private GeofenceInfo geofenceInfo;
    private List<DraggableCircle> mCircles = new ArrayList<>(1);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SupportMapFragment mapFragment =
                (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
        initData();
        doTrans();
    }
    public void initData(){
        etGeoLongitude = findViewById(R.id.geo_longitude);
        etGeoLatitude = findViewById(R.id.geolatitude);
        etGeoArea = findViewById(R.id.geo_area);
        etGeoRefresh = findViewById(R.id.geo_refresh);
        btnGeoSubmit = findViewById(R.id.geo_submit);
    }
    public void doTrans(){
        btnGeoSubmit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                strGeoLongitude = etGeoLongitude.getText().toString();
                strGgeoLatitude = etGeoLatitude.getText().toString();
                strGeoArea = etGeoArea.getText().toString();
                strGeoRefresh = etGeoRefresh.getText().toString();
                geofenceInfo = new GeofenceInfo(Double.parseDouble(strGeoLongitude),Double.parseDouble(strGgeoLatitude),Integer.parseInt(strGeoArea));
                DraggableCircle circle = new DraggableCircle(new LatLng(geofenceInfo.getLatitude(),geofenceInfo.getLongitude()), geofenceInfo.getRadius());
                mCircles.add(circle);
                geofenceManager.addGeofence(geofenceInfo);
                geofenceManager.startGeofencing();
            }
        });
    }
    /**
     *添加地理围栏
     * */
    public void startNewGeofence(GeofenceInfo geofenceInfo){
        DraggableCircle circle = new DraggableCircle(new LatLng(geofenceInfo.getLongitude(),geofenceInfo.getLatitude()), geofenceInfo.getRadius());
        mCircles.add(circle);
        Location geofenceCenter = new Location("");
        geofenceCenter.setLatitude(geofenceInfo.getLatitude());
        geofenceCenter.setLongitude(geofenceInfo.getLongitude());
        GeofenceManager.init(this);
        geofenceManager = GeofenceManager.getInstance();
        geofenceManager.addGeofence(geofenceInfo);
        geofenceManager.startGeofencing();
    }
    /**
     * 地图加载准备
     * */
    @Override
    public void onMapReady(GoogleMap map) {
        mMap = map;

        mMap.setOnMyLocationButtonClickListener(this);
        mMap.setOnMyLocationClickListener(this);
        mMap.setOnMapClickListener(this);
        enableMyLocation();
        geofenceInfo = new GeofenceInfo(114.00043174,22.5964144312,1);
        startNewGeofence(geofenceInfo);
    }

    /**
     * 确认获取定位权限
     */
    private void enableMyLocation() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            XPermission.requestPermissions(this, LOCATION_PERMISSION_REQUEST_CODE,
                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, new XPermission.OnPermissionListener() {
                        @Override
                        public void onPermissionGranted() {
                        }
                        @Override
                        public void onPermissionDenied() {
                            XPermission.showTipsDialog(MainActivity.this);
                        }
                    });
        } else if (mMap != null) {
            mMap.setMyLocationEnabled(true);
        }
    }

    @Override
    public boolean onMyLocationButtonClick() {
        Log.i(TAG, "MyLocation button clicked");
        return false;
    }

    @Override
    public void onMyLocationClick(@NonNull Location location) {
        Toast.makeText(this, "Current location:\n" + location, Toast.LENGTH_LONG).show();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode != LOCATION_PERMISSION_REQUEST_CODE) {
            return;
        }
        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            enableMyLocation();
        } else {
            mPermissionDenied = true;
        }
    }

    @Override
    protected void onResumeFragments() {
        super.onResumeFragments();
        if (mPermissionDenied) {
            showMissingPermissionError();
            mPermissionDenied = false;
        }
    }

    private void showMissingPermissionError() {
        XPermission.showTipsDialog(MainActivity.this);
    }

    @Override
    public void onMapClick(LatLng latLng) {
        etGeoLongitude.setText(latLng.longitude+"");
        etGeoLatitude.setText(latLng.latitude+"");
        if(tempCircle != null)
            tempCircle.remove();
        tempCircle = new DraggableCircle(latLng,0);
    }
    DraggableCircle tempCircle;
    private class DraggableCircle {
        private final Marker mCenterMarker;
        private final Circle mCircle;
        private double mRadiusMeters;

        public DraggableCircle(LatLng center, double radiusMeters) {
            mRadiusMeters = radiusMeters;
            mCenterMarker = mMap.addMarker(new MarkerOptions()
                    .position(center)
                    .draggable(true));
            mCircle = mMap.addCircle(new CircleOptions()
                    .center(center)
                    .radius(radiusMeters)
                    .strokeWidth(2)
                    .strokeColor(R.color.colorPrimaryDark)
                    .fillColor(R.color.colorPrimary)
                    .clickable(false));
        }
        public void remove(){
            mCenterMarker.remove();
            mCircle.remove();
        }
        public void setStrokePattern(List<PatternItem> pattern) {
            mCircle.setStrokePattern(pattern);
        }
        public void setClickable(boolean clickable) {
            mCircle.setClickable(clickable);
        }
    }
    private static LatLng toRadiusLatLng(LatLng center, double radiusMeters) {
        double radiusAngle = Math.toDegrees(radiusMeters / 6371009) /
                Math.cos(Math.toRadians(center.latitude));
        return new LatLng(center.latitude, center.longitude + radiusAngle);
    }
    private static double toRadiusMeters(LatLng center, LatLng radius) {
        float[] result = new float[1];
        Location.distanceBetween(center.latitude, center.longitude,
                radius.latitude, radius.longitude, result);
        return result[0];
    }
}

4、效果图示例

三、功能开发注意事项

1、一定要确保申请key值得包名和自己Android Studio开发环境得SHA1值是一致的,如果不一致日子里面会有key值不存在的报错

2、由于谷歌在国内被墙了,所以在国内使用地理围栏一定要翻墙,而且还要下载谷歌服务,不然会出现地图无法显示、无法定位等异常现象

3、由于大陆采用的定位标准是个国际上定位标准是不一样的,所以谷歌的地位在国内会出现偏移,偏移大概500~1000米,这种现象到香港等地方就变得正常了,当然也可以通过一定的算法转换,让谷歌的地位适应国内的定位





本实例支付的费用只是购买源码的费用,如有疑问欢迎在文末留言交流,如需作者在线代码指导、定制等,在作者开启付费服务后,可以点击“购买服务”进行实时联系,请知悉,谢谢
手机上随时阅读、收藏该文章 ?请扫下方二维码