45,6
рейтинг
25 августа 2013 в 15:05

Разработка → Определение местоположения пользователя с помощью Сервисов Google Play tutorial

Добрый день, Друзья!

Вопрос определения местоположения пользователя на максимальном количестве девайсов мучил меня примерно полгода. Дело доходило даже до велосипедов, описанных тут и тут. Дело было в том, что находились устройства, на которых местоположение не определялось, по неизвестной причине, однако другие приложения работали вполне хорошо. Очередной раз, копаясь в коде и рыская в просторах гула в поисках того, что я упускаю, и реализации, которой я еще не опробовал, наткнулся на свеженькую статью-мануал от google и, о боги(!), это заработало.

В данной статье я хочу рассказать, как я использовал это в своих целях и привести простой пример.
Хочу исходники


В официальном примере показывается как все это сделать в одном activity, так как им важно показать только возможности, а мне нужно было удобство, поэтому я все вынес в отдельный класс.

Для начала работы, необходимо инициализировать экземпляры классов LocationClient и LocationRequest. Первый отвечает за доступ к основным методам API определения местоположения и Geofence, второй обслуживает LocationClient и отвечает за обновления, т.е через него осуществляются колбэки(callbacks). С помощью LocationRequest, как в примере ниже, можно задавать интервалы обновления, приоритет для точности позиционирования и интервалы обновления.

    private LocationRequest mLocationRequest;
    private LocationClient mLocationClient;    

    private LocationListenerGPServices(final Context context)
    {
        mLocationRequest = LocationRequest.create();
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        mLocationRequest.setFastestInterval(FAST_INTERVAL_CEILING_IN_MILLISECONDS);
        mLocationClient = new LocationClient(context, this, this);
    }


Далее есть два варианта развития событий: мы можем взять последнее известное местоположение или запросить поиск нового.
В моем случае, я комбинирую эти два варианта, т.е в начале я получаю последнее известное местоположение и, если оно меня устраивает, использую его, иначе я запрашиваю обновления. В коде ниже, после вызова метода mLocationClient.connect() должен сработать метод onConnected(final Bundle bundle) у интерфейса GooglePlayServicesClient.ConnectionCallbacks, который означает, что нам удалось подключиться к Сервисам Google Play, т.е. теперь мы можем подписаться на обновление местоположения.


    public void enableMyLocation()
    {
        log("enableMyLocation");
        mLocation = null;
        mLocationClient.connect();
    }

    private boolean useCurrentLocation()
    {
        final Location location = mLocationClient.getLastLocation();
        if (System.currentTimeMillis() - location.getTime() < HALF_MINUTE)
        {
            log("useCurrentLocation");
            disableMyLocation();
            if (locationRunnable != null)
                locationRunnable.locationUpdate(location);
            return true;
        }
        return false;
    }




Когда мы запросили определение местоположения, чтобы скоротать как то время и не грузить UI — поток, я использую AsynkTask в качестве таймаута, который работает какое-то заданное время, и по завершению возвращает приложению найденное местоположение и отписывается от обновлений местоположения.

    @Override
    public void onConnected(final Bundle bundle)
    {
        if (!useCurrentLocation())
        {
            mLocationClient.requestLocationUpdates(mLocationRequest, this);
            if (findLocation != null && !findLocation.isCancelled())
                findLocation.cancel(true);
            findLocation = new FindLocation();
            findLocation.execute();
        }
    }

    private Location endFind()
    {
        long sec = System.currentTimeMillis();
        while (this.mLocation == null && System.currentTimeMillis() - sec < TIME_OUT)
        {}
        return this.mLocation;
    }

   private class FindLocation extends AsyncTask<Void, Void, Location>
    {
        @Override
        protected Location doInBackground(final Void... params)
        {return endFind();}

        @Override
        protected void onPostExecute(final Location location)
        {
            if (locationRunnable != null)
                locationRunnable.locationUpdate(location);
            disableMyLocation();
        }
    }

    @Override
    public void disableMyLocation()
    {
       if (mLocationClient.isConnected())
            mLocationClient.removeLocationUpdates(this);
        mLocationClient.disconnect();
    }
    
    public interface LocationRunnable
    { public void locationUpdate(Location location);}



Собственно как это использовать пример с github:
Для начала нужно получить экземпляр класса, для себя я реализовал singletone, так как он более подходит, для моего случая
locationListener = LocationListenerGPServices.getInstance(this);

Затем подписаться на получение местоположения и делать все, что вздумается с ним.
locationListener.setLocationRunnable(new ILocationListener.LocationRunnable() {
            @Override
            public void locationUpdate(final Location location)
            {}});



Собственно вот и все, что я хотел рассказать по данной теме. Тут я постарался рассказать вкратце, и привел минимум кода по тексту, в исходниках на github я показал на примере, как найти местоположение пользователя и отсортировать список станций метро по расстоянию до пользователя.

Спасибо за внимание, надеюсь кому-то это поможет и избавит от мучений.
Макс Ровкин @andreich
карма
43,0
рейтинг 45,6
android developer
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (28)

  • 0
    Я правильно понимаю, что это работает только при включенном интернете? Т. е. просто при работе только GPRS эффекта не будет.
    • 0
      Сорри — GPS.
    • +1
      Если приложение запросило точные координаты, то сработает. И может сработать, если другое приложение запросило точные координаты.
    • 0
      Нет, все прекрасно работает в отсутсвии интернета.
  • 0
    Получается что мы получили местоположение и отрубились? Что можно поменять, чтобы отслеживание местоположения шло постоянно, каждую секунду, как например в навител навигаторе.
    Простите за ламерский вопрос.
    • 0
      для этого нужно не отрубаться :)

      Дотстаточно прописать
      mLocationClient.connect();
      

      Когда подключение случится, можно будет подписаться на получение координат
      mLocationClient.requestLocationUpdates(mLocationRequest, this);
      

      и получать их в методе
          public void onLocationChanged(final Location location)   
      
      • 0
        А можно пример изменений что в вашем коде на гитхабе?
        Премного буду благодарен!
        • +1
          сделал отдельную ветку, там переписал и пример использования тоже попровил
          • 0
            У вас в методе
            public void locationUpdate(final Location location)
            

            стоит
            locationListener.disableMyLocation();
            

            Эта строка разве не вырубит обновление?

            У меня в данном методе
            myLat = location.getLatitude();
            myLng = location.getLongitude();
            myLatLnd = new LatLng(myLat, myLng);
            myMap.animateCamera(CameraUpdateFactory.newLatLng(myLatLnd), 1000, null);
            

            без вышеперечисленной строчки. Вроде полет нормальный.
            • +1
              Да, вырубить, просто в примере у меня разовое использование. и все правильно, убрать нужно для вашего случая
              ocationListener.disableMyLocation();
            • 0
              Судя по коду, вы делаете следование за местоположением. Все можно гораздо проще сделать, без моего кода

              googleMap.setOnMyLocationChangeListener(new GoogleMap.OnMyLocationChangeListener() {
                          @Override
                          public void onMyLocationChange(final Location location)
                          {
                              googleMap.animateCamera(CameraUpdateFactory.newLatLng(new LatLng(location.getLatitude(),location.getLongitude())));
                          }
                      });
              
              • 0
                Попробую.
                • 0
                  Попробовал. Довольно не точно определяет координаты, по сравнению с вашим кодом.
  • 0
    Если необходимо обновление в режиме реально времени, то переменные
    UPDATE_INTERVAL_IN_MILLISECONDS и FAST_INTERVAL_CEILING_IN_MILLISECONDS
    

    обратить в ноль?
    • +1
      В доках говорят An interval of 0 is allowed, but not recommended, since location updates may be extremely fast on future implementations.
      Так что я бы не стал, можете выставить 500 и думаю будет достаточно. Даже одной секунды вполне хватит.
      • 0
        Все просто отлично! Спасибо вам!

        P.S.
        Может еще знаете как в google map в своем приложении сделать кэш тайлов? Я гоняю камеру myMap.animateCamera по ключевым точкам. Может знаете еще какой метод?
        • 0
          никак, на сколько я знаю, сам задавался такой проблемой, но карты сами достаточно хорошо кэшируют все
          • 0
            Это если есть доступ к интернету, — а если я в тайге?
            • 0
              нет, как у OSM сделать нельзя, к сожалению
              • 0
                Тогда остается только мой метод, к сожалению.
                Спасибо!
  • 0
    Я чего-то не понимаю, или вы решили согреть пользователя?
    while (this.mLocation == null && System.currentTimeMillis() - sec < TIME_OUT) {}
    Да и доступ к переменной mLocation никак не синхронизирован. Вы же читаете ее в цикле из другого потока…
    • +1
      И вы вашим асинктаском, который просто ждет неизвестно сколько времени, блокируете от выполнения другие асинк таски. Экзекьютор для асинктасков однопоточный с 4й версии андроида.
      • 0
        В новой ветке ничего такого уже нет.
        • 0
          так обновите статью, новички такого наворотят потом, просто скопировав куски отсюда )
          • 0
            Ну… это как бы не моя статья ))
  • 0
    На моем телефоне падает:
    java.lang.NullPointerException at com.raxel.utils.LocationHelper.useCurrentLocation(LocationListenerGPServices.java:107)
    В location null. На Samsung Ace 2
  • 0
    Любопытно. А чем этот способ отличается от получения местоположения через LocationManager?
    • 0
      Тем, что с помощью LocationManager не всегда определяется местоположение. Сервисы гугла решают эту проблему

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.