В Android Oreo вы не можете создать долго работающий сервис в качестве фонового сервиса.Таким образом, вы должны создать службу переднего плана, которую служба должна привязать к текущей операции
Другая проблема - это обновление местоположения.Когда вы используете geoQuery
внутри службы, вы должны обновить местоположение также в службе.При запуске обновления местоположения вы должны передать это обновление в действие, тогда только пользовательский интерфейс может обновлять.В моем решении я использую интерфейс для обновления пользовательского интерфейса.Вы также можете использовать широковещательные слушатели.
Когда вы создаете службу и привязываете ее к действию, вы можете использовать Geofence внутри службы
public void startService(LatLng latLng, double radius) {
if (!isServiceRunning) {
isServiceRunning = true;
} else {
Log.e(TAG, "startTimer request for an already running timer");
}
if (geoQuery!=null){
geoQuery.removeAllListeners();
}
geoQuery = geoFire.queryAtLocation(new GeoLocation(latLng.latitude, latLng.longitude), 2f);
geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
@Override
public void onKeyEntered(String key, GeoLocation location) {
sendNotification("MRF", String.format("%s entered the dangerous area", key));
}
@Override
public void onKeyExited(String key) {
sendNotification("MRF", String.format("%s exit the dangerous area", key));
}
@Override
public void onKeyMoved(String key, GeoLocation location) {
Log.d("MOVE", String.format("%s move within the dangerous area [%f/%f]", key, location.latitude, location.longitude));
}
@Override
public void onGeoQueryReady() {
}
@Override
public void onGeoQueryError(DatabaseError error) {
Log.d("ERROR", "" + error);
}
});
}
Вы можете вызывать этот метод из вашей активности как
geoService.startService(dangerous_area,2000);
Это будет работать, даже если ваше приложение будет уничтожено.
Полный код выглядит следующим образом
public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {
private GoogleMap mMap;
//Play Service Location
private static final int MY_PERMISSION_REQUEST_CODE = 7192;
private static final int PLAY_SERVICE_RESULATION_REQUEST = 300193;
private Location mLastLocaiton;
private static int UPDATE_INTERVAL = 5000;
private static int FATEST_INTERVAL = 3000;
private static int DISPLACEMENT = 10;
Marker mCurrent;
VerticalSeekBar mVerticalSeekBar;
private static final String TAG = MapsActivity.class.getSimpleName();
private GeoService geoService;
private boolean serviceBound;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
mVerticalSeekBar = (VerticalSeekBar) findViewById(R.id.verticalSeekBar);
mVerticalSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mMap.animateCamera(CameraUpdateFactory.zoomTo(progress), 1500, null);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case MY_PERMISSION_REQUEST_CODE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (checkPlayService()) {
geoService.buildGoogleApiClient();
geoService.createLocationRequest();
geoService.displayLocation();
geoService.setLocationChangeListener(new GeoService.LocationChangeListener() {
@Override
public void onLocationChange(Location location) {
if (mCurrent != null)
mCurrent.remove();
mCurrent = mMap.addMarker(new MarkerOptions()
.position(new LatLng(location.getLatitude(), location.getLongitude()))
.title("You"));
LatLng coordinate = new LatLng(location.getLatitude(), location.getLongitude());
CameraUpdate yourLocation = CameraUpdateFactory.newLatLngZoom(coordinate, 12);
mMap.animateCamera(yourLocation);
}
});
}
}
break;
}
}
private void setUpdateLocation() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
}, MY_PERMISSION_REQUEST_CODE);
} else {
if (checkPlayService()) {
geoService.buildGoogleApiClient();
geoService.createLocationRequest();
geoService.displayLocation();
geoService.setLocationChangeListener(new GeoService.LocationChangeListener() {
@Override
public void onLocationChange(Location location) {
if (mCurrent != null)
mCurrent.remove();
mCurrent = mMap.addMarker(new MarkerOptions()
.position(new LatLng(location.getLatitude(), location.getLongitude()))
.title("You"));
LatLng coordinate = new LatLng(location.getLatitude(), location.getLongitude());
CameraUpdate yourLocation = CameraUpdateFactory.newLatLngZoom(coordinate, 12);
mMap.animateCamera(yourLocation);
}
});
}
}
}
private boolean checkPlayService() {
GoogleApiAvailability googleAPI = GoogleApiAvailability.getInstance();
int result = googleAPI.isGooglePlayServicesAvailable(this);
if (result != ConnectionResult.SUCCESS) {
if (googleAPI.isUserResolvableError(result)) {
googleAPI.getErrorDialog(this, result, PLAY_SERVICE_RESULATION_REQUEST).show();
} else {
Toast.makeText(this, "This Device is not supported.", Toast.LENGTH_SHORT).show();
}
return false;
}
return true;
}
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
LatLng dangerous_area = new LatLng(8.5324236, 76.8842189);
mMap.addCircle(new CircleOptions()
.center(dangerous_area)
.radius(2000)
.strokeColor(Color.BLUE)
.fillColor(0x220000FF)
.strokeWidth(5.0f));
geoService.startService(dangerous_area,2000);
}
@Override
protected void onStart() {
super.onStart();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Starting and binding service");
}
Intent i = new Intent(this, GeoService.class);
startService(i);
bindService(i, mConnection, 0);
}
@Override
protected void onStop() {
super.onStop();
if (serviceBound) {
// If a timer is active, foreground the service, otherwise kill the service
if (geoService.isServiceRunning()) {
geoService.foreground();
} else {
stopService(new Intent(this, GeoService.class));
}
// Unbind the service
unbindService(mConnection);
serviceBound = false;
}
}
/**
* Callback for service binding, passed to bindService()
*/
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Service bound");
}
GeoService.RunServiceBinder binder = (GeoService.RunServiceBinder) service;
geoService = binder.getService();
serviceBound = true;
// Ensure the service is not in the foreground when bound
geoService.background();
setUpdateLocation();
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(MapsActivity.this);
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Service disconnect");
}
serviceBound = false;
}
};
public static class GeoService extends Service implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
LocationListener {
private LocationRequest mLocationRequest;
private GoogleApiClient mGoogleApiClient;
private Location mLastLocation;
private DatabaseReference ref;
private GeoFire geoFire;
private LocationChangeListener mLocationChangeListener;
private static final String TAG = GeoService.class.getSimpleName();
// Is the service tracking time?
private boolean isServiceRunning;
// Foreground notification id
private static final int NOTIFICATION_ID = 1;
// Service binder
private final IBinder serviceBinder = new RunServiceBinder();
private GeoQuery geoQuery;
public class RunServiceBinder extends Binder {
GeoService getService() {
return GeoService.this;
}
}
@Override
public void onCreate() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Creating service");
}
ref = FirebaseDatabase.getInstance().getReference("MyLocation");
geoFire = new GeoFire(ref);
isServiceRunning = false;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Starting service");
}
return Service.START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Binding service");
}
return serviceBinder;
}
@Override
public void onDestroy() {
super.onDestroy();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Destroying service");
}
}
/**
* Starts the timer
*/
public void startService(LatLng latLng, double radius) {
if (!isServiceRunning) {
isServiceRunning = true;
} else {
Log.e(TAG, "startService request for an already running Service");
}
if (geoQuery!=null){
geoQuery.removeAllListeners();
}
geoQuery = geoFire.queryAtLocation(new GeoLocation(latLng.latitude, latLng.longitude), 2f);
geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
@Override
public void onKeyEntered(String key, GeoLocation location) {
sendNotification("MRF", String.format("%s entered the dangerous area", key));
}
@Override
public void onKeyExited(String key) {
sendNotification("MRF", String.format("%s exit the dangerous area", key));
}
@Override
public void onKeyMoved(String key, GeoLocation location) {
Log.d("MOVE", String.format("%s move within the dangerous area [%f/%f]", key, location.latitude, location.longitude));
}
@Override
public void onGeoQueryReady() {
}
@Override
public void onGeoQueryError(DatabaseError error) {
Log.d("ERROR", "" + error);
}
});
}
/**
* Stops the timer
*/
public void stopService() {
if (isServiceRunning) {
isServiceRunning = false;
geoQuery.removeAllListeners();
} else {
Log.e(TAG, "stopTimer request for a timer that isn't running");
}
}
/**
* @return whether the service is running
*/
public boolean isServiceRunning() {
return isServiceRunning;
}
/**
* Place the service into the foreground
*/
public void foreground() {
startForeground(NOTIFICATION_ID, createNotification());
}
/**
* Return the service to the background
*/
public void background() {
stopForeground(true);
}
/**
* Creates a notification for placing the service into the foreground
*
* @return a notification for interacting with the service when in the foreground
*/
private Notification createNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setContentTitle("Service is Active")
.setContentText("Tap to return to the Map")
.setSmallIcon(R.mipmap.ic_launcher);
Intent resultIntent = new Intent(this, MapsActivity.class);
PendingIntent resultPendingIntent =
PendingIntent.getActivity(this, 0, resultIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(resultPendingIntent);
return builder.build();
}
private void sendNotification(String title, String content) {
Notification.Builder builder = new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(title)
.setContentText(content);
NotificationManager manager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MapsActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
builder.setContentIntent(contentIntent);
Notification notification = builder.build();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.defaults |= Notification.DEFAULT_SOUND;
manager.notify(new Random().nextInt(), notification);
}
@Override
public void onConnected(@Nullable Bundle bundle) {
displayLocation();
startLocationUpdate();
}
private void startLocationUpdate() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
}
@Override
public void onConnectionSuspended(int i) {
mGoogleApiClient.connect();
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
}
@Override
public void onLocationChanged(Location location) {
mLastLocation = location;
displayLocation();
}
interface LocationChangeListener {
void onLocationChange(Location location);
}
private void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL);
mLocationRequest.setFastestInterval(FATEST_INTERVAL);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setSmallestDisplacement(DISPLACEMENT);
}
private void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API).build();
mGoogleApiClient.connect();
}
private void displayLocation() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
if (mLastLocation != null) {
final double latitude = mLastLocation.getLatitude();
final double longitude = mLastLocation.getLongitude();
geoFire.setLocation("You", new GeoLocation(latitude, longitude), new GeoFire.CompletionListener() {
@Override
public void onComplete(String key, DatabaseError error) {
if (mLocationChangeListener!=null) {
mLocationChangeListener.onLocationChange(mLastLocation);
}
}
});
Log.d("MRF", String.format("Your last location was chaged: %f / %f", latitude, longitude));
} else {
Log.d("MRF", "Can not get your location.");
}
}
public void setLocationChangeListener(LocationChangeListener mLocationChangeListener) {
this.mLocationChangeListener = mLocationChangeListener;
}
}
}
Не забудьте добавить службу в манифест
<service android:name=".MapsActivity$GeoService" />
Полный исходный код github.com / vinayakb73 / GeoFence-GeoFire