Pull to refresh

Определение доступности GPS в Android

Reading time 4 min
Views 32K
Приветствую, хабрасообщество!

Эта статья, надеюсь, станет хорошим подспорьем начинающим в области программирования под Android. А может даже и матерые профи что-нибудь почерпнут.

Итак, понадобилось мне как-то определять, доступен ли в настоящее время GPS-фикс. Казалось бы, LBS (location-based service) — вещь перспективная и популярная, и Google, прекрасно это понимая, предоставит простой в обращении инструмент для их разработки. Ага, разбежался… Не так-то все и просто, поэтому приходится в определенной мере изощряться.

Ну и в чем тут у нас собственно проблема? Проблема в определении текущего местоположения пользователя. Видов существует несколько, но ТЗ велит использовать GPS и позиционирование по сотовым вышкам. Задача — определить текущие координаты с максимальной точностью, т.е. в идеале по GPS. Если он недоступен, то по вышкам. Если есть сигнал GPS, то все легко и просто — берем со спутника координаты и делаем с ними что угодно. Если сигнала нет, то при обработке координат вы рискуете нарваться на null, в чем очень мало хорошего, а при недолжной обработке исключений может быть еще и что-нибудь с печальными последствиями. Значит, надо как-то определить, а есть ли у нас фикс?

Ну что ж, проблема видна — будем решать!

Начнем с ковыряния LocationManager. Есть в нем занятное свойство isProviderEnabled(), возвращающее булево значение. Ура? Рано… Это значение всего лишь характеризует, включен GPS-приемник вашего телефона или нет (собственно, можно было и по названию догадаться). Первый блин получился как всегда, идем дальше.

Залезем во внутренности LocationListener. Что мы видим? Ба, да это обработчик onStatusChanged()! В идеале реагирует на изменение статуса провайдера, выставляя соответствующие значения. В идеале… Не реагирует он ни на что начиная с андроида версии 2.1! С грустью проходим мимо.

Продолжим? Конечно продолжим! Очевидным выглядит следующий финт ушами — сравнение времени последнего пришедшего фикса с текущим системным временем. Казалось бы, логично — раз фикс старый, то GPS недоступен. Не совсем так: фиксы приходят только при движении, соответственно можно перепутать недоступность спутников с простым сидением на месте. Согласитесь, будет не совсем приятно, если вы сидите себе сидите, и тут вдруг — оппа! — и ваш телефон решил, что вы телепортнулись метров на 400-500. Снова не то, но приемчик запомним — пригодится.

Теперь посмотрим в сторону GpsStatus.Listener, реализующий метод onGpsStatusChanged(int event). Переменная event может принимать несколько значений, нас же интересует GPS_EVENT_SATELLITE_STATUS. Возникновение такого события говорит о том, что ваш приемник анализирует GPS-спутники. Вот это-то нам и надо! Дальше все просто и понятно — берем текущий статус GPS и вытаскиваем из него доступные спутники. В самом простом случае нас просто интересует их количество.

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

Выглядеть обработчик статуса будет примерно так:
LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);    
	GpsStatus.Listener lGPS = new GpsStatus.Listener() {
		public void onGpsStatusChanged(int event) {
	        if( event == GpsStatus.GPS_EVENT_SATELLITE_STATUS){
	            GpsStatus status = lm.getGpsStatus(null); 
	            Iterable<GpsSatellite> sats = status.getSatellites();
	            doSomething();
	        }
	    }
	};
lm.addGpsStatusListener(lGPS);

В переменной status лежит информация о всех доступных спутниках

Итак, дальше все ну совсем замечательно — смотрим количество спутников, если их меньше четырех, то фикса никакого нет и быть не может, значит используем другие методы позиционирования (уж извините, но конкретную реализацию описывать не буду). Этот метод можно скрестить с сопоставлением времен, описанным на пару абзацев выше. Так можно выставить определенный период «доверия» фиксу

Подобьем баланс. Все выше приведенное не дает вам точных гарантий определения доступности фикса. На самом деле просто отсекаются ситуации, в которых фикса уж точно нет. Это, конечно, не совсем то, что хотелось, но уже что-то!

UPD: Похоже, решение найдено! Свершилось это благодаря r_ii.
Итак, ваш GPS-приемник, будучи во включенном состоянии, постоянно принимает сигналы в соответствии с протоколом NMEA. Вот его-то нам и надо!
Для просмотра этих сигналов добавляем в код следующее:

lm.addNmeaListener(new GpsStatus.NmeaListener() {
public void onNmeaReceived(long timestamp, String nmea) {
parseNMEA(nmea);
}});


За этот код спасибо вот этому топику 2m0nd. Полное описание протокола здесь (pdf, англ).

Собственно, дело за малым — парсить полученную строку. В данном случае нас интересует строки с ключевым (первым) полем $GPGGA, а в них параметр №6, по умному называемый GPS Quality Indicator. Он принимает следующие значения:
  • 0-фикс не доступен
  • 1-GPS-фикс
  • 2-дифференциальный фикс
Бинго!

P.S. Ни в коем случае не претендую на авторство указанных методов, т.к. все найдено исключительно с помощью гугла. Целью статьи является сбор всей информации в одну кучу и кое-какое ее структурирование для того, чтобы следующие путешествующие в сказочный мир навигации и программирования мобильных устройств с маленьким зеленым существом не тратили время и нервы.
P.P.S. Очевидно, что метод далек от идеала, поэтому его совершенствование продолжается. Любая помощь и критика (объективная) только приветствуется!
Tags:
Hubs:
+28
Comments 15
Comments Comments 15

Articles