Итак, приступим:
Получаем ключ к Google Maps API
Чтобы иметь возможность использовать Google Maps в своём Android-приложении, нужно получить ключ. Ключ приложения генерируется на этой странице на основании сертификата, которым будет в дальнейшем подписано ваше приложение. Обычно готовое приложение подписывается отдельным сертификатом, а в процессе разработки используется другой из хранилища debug.keystore, которое находится в каталоге .android вашей домашней директории. В этом случае вам потребуется сгенерировать два ключа и не забыть заменить отладочный ключ на "боевой", перед финальной сборкой приложения. Для генерации ключа вам потребуется получить "сertificate fingerprint" командой
keytool -list -keystore ~/.android/debug.keystore
Пароль от debug.keystore обычно - пустая строка.
Доступы и библиотеки
Google Maps API в состав Android SDK входит как отдельная библиотека, поэтому её нужно отдельно подключить в AndroidManifest.xml вашего приложения командой
Эта строка должна находиться внутри тега "application".
Также не забудем попросить разрешения на получение координат и доступ в интернет:
android.permission.ACCESS_FINE_LOCATION
android.permission.INTERNET
android.permission.ACCESS_WIFI_STATE
Последнее из разрешений, нужно нам для отслеживания состояния wifi-подключения. Это позволит нам выбрать подходящий способ получения приблизительных координат, пока наше устройство будет искать спутники и получать от них точные координаты.
Рисуем гуглокарту
Карту нам нарисует MapView, в конструктор которого мы передаём ключ, полученный на первом шаге. Сразу после создания желательно установить zoom level карты (1 - весь мир на экране, больше - детальнее) , а также методом setCenter(new GeoPoint(latitude, longitude)) спозиционировать карту куда-нибудь. Обе операции выполняем при помощи MapController-а, который получаем из экземпляра MapView методом getController(). Чтобы показать кнопки изменения масштаба вызовем метод setBuiltInZoomControls(true). И, главное, не забудьте сделать карту кликабельной методом setClickable(true).
Далее "вставляем" MapView в RelativeLayout вместе с остальными компонентами. В нашем случае это будет панель с двумя элементами управления: переключателем режима спутниковых фотографий и пиктограммой для возврата карты к текущему местоположению.
В целом картинка должна быть примерно такой, как на иллюстрации слева.
Все картинки возьмём из android.R.drawable, чтобы не морочить себе голову подготовкой своей графики.
Получаем текущие координаты
Вот мы и подошли к самому интересному. Весь код нашего приложения помещается в одном файле главного Activity:
import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Point; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.net.wifi.WifiManager; import android.os.Bundle; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.Toast; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapActivity; import com.google.android.maps.MapController; import com.google.android.maps.MapView; import com.google.android.maps.Overlay; public class GMapsActivity extends MapActivity { private MapView map; private LocationManager manager; private Location loc; private LocationListener listener = new LocationListener() { @Override public void onLocationChanged(Location loc) { setLocation(loc); GeoPoint p = new GeoPoint((int) (loc.getLatitude() * 1e6), (int) (loc.getLongitude() * 1e6)); map.getOverlays().add(new MarkerOverlay(p)); map.invalidate(); map.getController().animateTo(p); } @Override public void onProviderDisabled(String provider) { Toast.makeText(GMapsActivity.this, provider + " disabled", Toast.LENGTH_SHORT).show(); } @Override public void onProviderEnabled(String provider) { Toast.makeText(GMapsActivity.this, provider + " enabled", Toast.LENGTH_SHORT).show(); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); manager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); loc = getLastCopords(); buildUI(); requestNewCoordinates(); } @Override protected void onPause() { super.onPause(); if (manager != null) manager.removeUpdates(listener); } public void setLocation(Location loc) { this.loc = loc; } private Location getLastCopords() { String[] providers = new String[] { LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER, LocationManager.PASSIVE_PROVIDER }; Location loc = null; for (String provider : providers) { loc = manager.getLastKnownLocation(provider); if (loc != null) { break; } } return loc; } private void buildUI() { RelativeLayout root = new RelativeLayout(this); // creating map with zoom controls and set center map = new MapView(this, "0kUYZ329eS_2MX4EyZ6YbJq4KFLm0hjiK1zjxLw"); map.setBuiltInZoomControls(true); map.setClickable(true); MapController controller = map.getController(); if (loc != null) { GeoPoint p = new GeoPoint((int) (loc.getLatitude() * 1e6), (int) (loc.getLongitude() * 1e6)); controller.setCenter(p); map.getOverlays().add(new MarkerOverlay(p)); } else { controller.setCenter(new GeoPoint(0, 0)); } controller.setZoom(4); root.addView(map, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); // creating custom controls panel LinearLayout panel = new LinearLayout(this); panel.setOrientation(LinearLayout.VERTICAL); panel.setBackgroundColor(Color.argb(200, 200, 200, 200)); RelativeLayout.LayoutParams plp = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); plp.addRule(RelativeLayout.ALIGN_PARENT_TOP | RelativeLayout.ALIGN_PARENT_RIGHT); // map mode button ImageView mode = new ImageView(this); mode.setImageResource(android.R.drawable.ic_menu_mapmode); mode.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { map.setSatellite(!map.isSatellite()); } }); panel.addView(mode, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); // go to my location button ImageView my = new ImageView(this); my.setImageResource(android.R.drawable.ic_menu_mylocation); my.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (loc != null) { map.getController() .animateTo(new GeoPoint((int) (loc.getLatitude() * 1e6), (int) (loc.getLongitude() * 1e6))); } else { Toast.makeText(GMapsActivity.this, "Coordinates is not found", Toast.LENGTH_SHORT).show(); } } }); panel.addView(my, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); root.addView(panel, plp); // show all setContentView(root, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); } private void requestNewCoordinates() { manager.removeUpdates(listener); final WifiManager wfManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); if (manager.getAllProviders().contains(LocationManager.GPS_PROVIDER) && manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener); } else if (manager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) && wfManager.isWifiEnabled()) { manager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, listener); } else if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1 && manager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER)) { manager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, listener); } else { Toast.makeText(this, "Location providers not found", Toast.LENGTH_SHORT).show(); } } @Override protected boolean isRouteDisplayed() { return false; } private class MarkerOverlay extends Overlay { private GeoPoint p; public MarkerOverlay(GeoPoint p) { this.p = p; } @Override public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) { super.draw(canvas, mapView, shadow); // translate the GeoPoint to screen pixels Point screenPts = new Point(); mapView.getProjection().toPixels(p, screenPts); // add the marker Bitmap bmp = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_menu_myplaces); canvas.drawBitmap(bmp, screenPts.x, screenPts.y - 50, null); return true; } } }
Координаты текущего местоположения тут мы получаем дважды: перед отрисовкой карты (последние известные с прошлого поиска) и после обновления.
Первое получение координат мы делаем в методе getLastCopords(). Эта операция выполняется достаточно быстро, чтобы не тормозить нам построение интерфейса. Она может завершиться неудачно, и в этом случае мы устанавливаем карту в точку new GeoPoint(0, 0). В случае успеха устанавливаем карту в нужное место и устанавливаем маркер текущего местоположения методом map.getOverlays().add(new MarkerOverlay(p)).
Важно учесть, что в нашем устройстве есть три источника получения координат.
Первый: GPS - даёт самые точные координаты, но работает медленнее всех и может ничего не найти. Второй: сеть wifi - отвечает быстро, относительно точно и всегда, когда wifi вообще есть в наличии. Третий: (с версии Android 2.2): PASSIVE_PROVIDER - сигнал соты оператора. Даёт координаты с точностью до нескольких кварталов, медленно, но практически всегда. Для получения данных с прошлого поиска скорость не имеет значения, поэтому опрашиваем провайдеров по очереди, начиная с самого точного.
Чтобы обновить координаты, подписываемся на результат обновления лучшего в данный момент провайдера в методе requestNewCoordinates(). При этом проверяем, разрешено ли использование спутников (может быть выключено пользователем ради экономии заряда батареи), есть ли сеть wifi и т.п.
При запуске обновления передаём выбранному провайдеру экземпляр LocationListener-а, в методе onLocationChanged которого мы получим новые координаты, передвинем на них карту и поставим маркер.
При выходе из приложения (в onPause()) нужно не забыть остановить обновление координат у выбранного ранее провайдера методом removeUpdates(listener), чтобы не разряжать пользователю батарею напрасно.
Отлаживаем приложение
Несколько слов о отладке "координатного" приложения. Есть одна проблема, которая может попить кровушки у вас при отладке приложения на реальном устройстве. Различные провайдеры могут не давать события locationChanged очень долго или совсем. Например, PASSIVE_PROVIDER у меня вернул координаты только при отключении и повторном включении передачи данных. Спутники вообще могут быть недоступны в помещении. Поэтому мой совет: выполняйте отладку на эмуляторе, а событие locationChanged тут можно вызвать очень просто:
path/to/android-sdk/platform-tools/adb emu geo fix -121.45356 46.51119
Эта команда в консоли переместит вас в уютное заснеженое ущелье где-то в северной америке :)
Спасибо Вам большое за статью! Очень понравилось)
ОтветитьУдалитьеще в тему-тоже полезным показалось http://www.enterra.ru/blog/gps-android/
Интересно, доступно, понятно. Спасибо.
ОтветитьУдалитьЭтот комментарий был удален администратором блога.
ОтветитьУдалить> PASSIVE_PROVIDER - сигнал соты оператора.
ОтветитьУдалитьЭто неверно. PASSIVE_PROVIDER предоставляет обновления местоположения тогда, когда местоположение обновляется в принципе каким-либо способом (например, в другом приложении) без самостоятельного запроса.
Note: The Google Maps Android API v2 uses a new system of managing keys. Existing keys from a Google Maps Android v1 application, commonly known as MapView, will not work with the v2 API.
ОтветитьУдалитьMapView уже не актуален.... ?