Как получать обновления местоположения каждые 5 минут с помощью API FusedLocation

В настоящее время я работаю над приложением, которое должно проверять местоположение пользователя каждые пять минут и отправлять координаты на сервер. Я решил использовать API FusedLocation в Сервисах Google Play вместо старого простого API LocationManager, главным образом потому, что я заметил уровень приоритета LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY, который утверждает, что обеспечивает 100-метровый уровень точности при разумных использование батареи, что ИМЕННО то, что мне нужно.

В моем случае у меня есть Activity, структура наследования которого:

public class MainActivity extends AppCompatActivity implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener, LocationListener

и который реализует соответствующие обратные вызовы (onConnected, onConnectionFailed, onConnectionSuspended, onLocationChanged). Я также получаю экземпляр GoogleApiClient с помощью этого метода, как это предлагается в официальной документации:

protected synchronized GoogleApiClient buildGoogleApiClient() {
        return new GoogleApiClient.Builder(this).addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API).build();

В onConnected я запускаю обновления местоположения, используя

LocationServices.FusedLocationApi.requestLocationUpdates(mApiClient,
                mLocationRequest, this);

... и фиксируйте изменения в onLocationChanged().

Однако я быстро обнаружил, что обновления местоположения, кажется, прекращаются через некоторое время. Возможно, это потому, что этот метод привязан к жизненному циклу Activity, я не уверен. Во всяком случае, я попытался обойти это, создав внутренний класс, который расширяет IntentService и запустил его с помощью AlarmManager. Итак, в onConnected я сделал это:

AlarmManager alarmMan = (AlarmManager) this
                .getSystemService(Context.ALARM_SERVICE);

        Intent updateIntent = new Intent(this, LocUpService.class);

        PendingIntent pIntent = PendingIntent.getService(this, 0, updateIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        alarmMan.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 0,
                1000 * 60 * 5, pIntent);

Класс LocUpService выглядит так:

public static class LocUpService extends IntentService {

        public LocUpService() {
            super("LocUpService");

        }

        @Override
        protected void onHandleIntent(Intent intent) {
            Coords coords = LocationUpdater.getLastKnownLocation(mApiClient);


        }

    }

LocationUpdater — это еще один класс, который содержит статический метод getLastKnownLocation, а именно:

public static Coords getLastKnownLocation(GoogleApiClient apiClient) {

        Coords coords = new Coords();
        Location location = LocationServices.FusedLocationApi
                .getLastLocation(apiClient);

        if (location != null) {

            coords.setLatitude(location.getLatitude());
            coords.setLongitude(location.getLongitude());

            Log.e("lat ", location.getLatitude() + " degrees");
            Log.e("lon ", location.getLongitude() + " degrees");

        }
        return coords;
    }

Но сюрприз!! Я получаю «IllegalArgumentException: требуется параметр GoogleApiClient», когда я четко передаю ссылку на статический метод, который снова, я думаю, должен иметь какое-то отношение к экземпляру GoogleApiClient, связанному с жизненным циклом Activity, и что-то идет не так с передачей экземпляра в Служба намерений.

Вот я и думаю: как мне получать регулярные обновления местоположения каждые пять минут и при этом не сойти с ума? Должен ли я расширять службу, реализовывать все обратные вызовы интерфейса в этом компоненте, создавать там экземпляр GoogleApiClient и поддерживать его работу в фоновом режиме? Нужно ли мне, чтобы AlarmManager запускал службу, которая расширяет IntentService каждые пять минут для выполнения работы, снова имея все соответствующие обратные вызовы и GoogleApiClient, созданные в IntentService? Продолжать ли мне делать то, что я делаю сейчас, но создавать GoogleApiClient как синглтон, ожидая, что это будет иметь значение? Как бы вы это сделали?

Спасибо и извините, что так многословно.


person Antonis427    schedule 06.10.2015    source источник
comment
Я бы создал настраиваемую службу вместо IntentService, поскольку вы хотите, чтобы поиск вашего местоположения продолжался, пока приложение живо. Вы бы поместили свою конструкцию GoogleApiClient и все ваши обратные вызовы и все остальное в эту службу. Если бы вам нужно было передать информацию о состоянии обратно в ваши действия, я бы, вероятно, использовал в этом случае настраиваемые обратные вызовы, чтобы просто отправить все, что им нужно знать. Дайте мне знать, если вам нужна помощь с кодом. У меня есть несколько фрагментов, которые могут вам помочь.   -  person NoChinDeluxe    schedule 07.10.2015


Ответы (2)


В настоящее время я работаю над приложением, которое должно проверять местоположение пользователя каждые пять минут и отправлять координаты на сервер. Я решил использовать API FusedLocation в сервисах Google Play вместо старого простого API LocationManager.

Наше приложение имеет точно такое же требование, я реализовал его пару дней назад, и вот как я это сделал.

В действии запуска или в любом месте, где вы хотите начать, настройте LocationTracker для запуска каждые 5 минут с помощью AlarmManager.

private void startLocationTracker() {
    // Configure the LocationTracker's broadcast receiver to run every 5 minutes.
    Intent intent = new Intent(this, LocationTracker.class);
    AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis(),
            LocationProvider.FIVE_MINUTES, pendingIntent);
}

LocationTracker.java

public class LocationTracker extends BroadcastReceiver {

    private PowerManager.WakeLock wakeLock;

    @Override
    public void onReceive(Context context, Intent intent) {
        PowerManager pow = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        wakeLock = pow.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "");
        wakeLock.acquire();

        Location currentLocation = LocationProvider.getInstance().getCurrentLocation();

        // Send new location to backend. // this will be different for you
        UserService.registerLocation(context, new Handlers.OnRegisterLocationRequestCompleteHandler() {
            @Override
            public void onSuccess() {
                Log.d("success", "UserService.RegisterLocation() succeeded");

                wakeLock.release();
            }

            @Override
            public void onFailure(int statusCode, String errorMessage) {
                Log.d("error", "UserService.RegisterLocation() failed");
                Log.d("error", errorMessage);

                wakeLock.release();
            }
        }, currentLocation);
    }
}

LocationProvider.java

public class LocationProvider {

    private static LocationProvider instance = null;
    private static Context context;

    public static final int ONE_MINUTE = 1000 * 60;
    public static final int FIVE_MINUTES = ONE_MINUTE * 5;

    private static Location currentLocation;

    private LocationProvider() {

    }

    public static LocationProvider getInstance() {
        if (instance == null) {
            instance = new LocationProvider();
        }

        return instance;
    }

    public void configureIfNeeded(Context ctx) {
        if (context == null) {
            context = ctx;
            configureLocationUpdates();
        }
    }

    private void configureLocationUpdates() {
        final LocationRequest locationRequest = createLocationRequest();
        final GoogleApiClient googleApiClient = new GoogleApiClient.Builder(context)
                .addApi(LocationServices.API)
                .build();

        googleApiClient.registerConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
            @Override
            public void onConnected(Bundle bundle) {
                startLocationUpdates(googleApiClient, locationRequest);
            }

            @Override
            public void onConnectionSuspended(int i) {

            }
        });
        googleApiClient.registerConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
            @Override
            public void onConnectionFailed(ConnectionResult connectionResult) {

            }
        });

        googleApiClient.connect();
    }

    private static LocationRequest createLocationRequest() {
        LocationRequest locationRequest = new LocationRequest();
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        locationRequest.setInterval(FIVE_MINUTES);
        return locationRequest;
    }

    private static void startLocationUpdates(GoogleApiClient client, LocationRequest request) {
        LocationServices.FusedLocationApi.requestLocationUpdates(client, request, new com.google.android.gms.location.LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                currentLocation = location;
            }
        });
    }

    public Location getCurrentLocation() {
        return currentLocation;
    }
}

Сначала я создаю экземпляр LocationProvider в классе, который расширяет приложение, создавая экземпляр при запуске приложения:

MyApp.java

public class MyApp extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        LocationProvider locationProvider = LocationProvider.getInstance();
        locationProvider.configureIfNeeded(this);
    }
}

LocationProvider создается и настраивается для обновления местоположения ровно один раз, поскольку он является одноэлементным. Каждые 5 минут он будет обновлять свое значение currentLocation, которое мы можем получить из любого места, где нам нужно, с помощью

Location loc = LocationProvider.getInstance().getCurrentLocation();

Запуск какой-либо фоновой службы не требуется. AlarmManager будет транслировать в LocationTracker.onReceive() каждые 5 минут, а частичная блокировка гарантирует, что код завершит работу, даже если устройство находится в режиме ожидания. Это также энергоэффективно.

Обратите внимание, что вам нужны следующие разрешения

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

<!-- For keeping the LocationTracker alive while it is doing networking -->
<uses-permission android:name="android.permission.WAKE_LOCK" />

и не забудьте зарегистрировать приемник:

<receiver android:name=".LocationTracker" />
person Tim    schedule 06.10.2015
comment
Предложения по переименованию OnRegisterLocationRequestCompleteHandler приветствуются - person Tim; 07.10.2015
comment
Спасибо, это был отличный ответ! На самом деле я никогда не расширял Application, поэтому я буду делать это с небольшим трепетом (по какой-то причине мне всегда казалось, что это немного запрещено, и я не так хорошо знаком с его жизненным циклом), но самое время! - person Antonis427; 07.10.2015
comment
@Antonis427Antonis427 Если я найду время сегодня, я обновлю ответ еще немного о классе приложений. - person Tim; 07.10.2015
comment
еще раз спасибо. Любая причина, по которой вы можете придумать, почему я получаю нулевое местоположение? - person Antonis427; 07.10.2015
comment
@ Antonis427 на самом деле нет. Это всегда работает для меня. У тебя включен GPS? - person Tim; 07.10.2015
comment
Оказывается, службы определения местоположения Google не поддерживаются на Кипре (где я использую приложение), так что, вероятно, это причина, по которой возвращается значение null! Если FusedLocation API возвращает null (что, как я полагаю, происходит везде, где сервисы определения местоположения Google не поддерживаются), я просто возвращаюсь к LocationManager.getLastKnownLocation() с настройкой NETWORK_PROVIDER, чтобы получить разумную точность. Спасибо! - person Antonis427; 07.10.2015
comment
LocationProvider.getInstance().getCurrentLocation() Не удается разрешить метод .getInstance() для LocationTracker.java - person Gennady Kozlov; 13.10.2015
comment
@GennadyKozlov ваша строка кода и ошибка не совпадают. Метод getInstance() принадлежит классу LocationProvider, а не трекеру. - person Tim; 13.10.2015
comment
LocationTracker не видит метод getInstanse() в классе LocationProvider - person Gennady Kozlov; 13.10.2015
comment
@GennadyKozlov Является ли метод public static LocationProvider getInstance()? Не может быть, что не может найти, где-то в вашем коде должна быть опечатка - person Tim; 13.10.2015
comment
Почему мой LocationTracker (BroadcastReceiver) не видит одноэлементный LokationProvider? - person Gennady Kozlov; 13.10.2015
comment
‹receiver android:name=.LocationTracker /› Достаточно описания манифеста приемника? - person Gennady Kozlov; 16.10.2015
comment
Android Studio создает неподходящий BroadcastReceiver. Можете привести пример UceroService?) - person Gennady Kozlov; 16.10.2015
comment
Пользовательский сервис @GennadyKozlov не является частью примера, просто замените эту часть кода своей собственной реализацией. - person Tim; 16.10.2015
comment
Запустите алармменеджер. Если я выхожу из приложения кнопкой НАЗАД, возникает ошибка FATAL EXCEPTION: main java.lang.RuntimeException: Unable to start Receiver ru.myapplication.LocationTracker: java.lang.NullPointerException< /b> Location currentLocation = LocationProvider.getInstance().getCurrentLocation(); равен нулю. - person Gennady Kozlov; 27.10.2015
comment
@GennadyKozlov раздел комментариев не предназначен для помощи в отладке, просто задайте новый вопрос, если у вас есть проблемы - person Tim; 27.10.2015
comment
К сожалению, этот пример не решает проблему, потому что местоположение обновления уничтожается при закрытии приложения и является НЕИСПРАВНЫМ ИСКЛЮЧЕНИЕМ. - person Gennady Kozlov; 28.10.2015
comment
да .. после закрытия приложения .. к сожалению, остановилась ошибка, есть ли способ решить эту ошибку? я хочу это только тогда, когда приложение находится на переднем плане? - person nafees ahmed; 16.04.2020

Что касается вашего первого метода, когда вы используете действие для запроса обновлений местоположения, они не должны останавливаться, если вы не отключите клиент местоположения в методе onPause() действия. Поэтому, пока ваша деятельность находится в фоновом/переднем плане, вы должны продолжать получать обновления местоположения. Но если активность будет уничтожена, вы, конечно, не получите обновлений.

Проверьте, отключаете ли вы клиент местоположения в жизненном цикле активности.

person pgiitu    schedule 06.10.2015