Android приложения для определения местоположения. Часть 1

4.9 (2)

Определения места положения в наше время является одной из наиболее часто решаемых задач в android приложениях. Много задач требует для своего решения знать, где ты сейчас находишься. Внутри android устройства установлено различное количество сенсоров среди которых есть и Gps. Использования gps очень удобно для некоторых целей и облегчает множество задач, которые требуют позиционирования устройства на карте. Но определить местоположения можно также и от сети.  В одной из статей было описано, как работать с Google Maps и на этот раз добавим отображения местоположения устройства и точность с которой оно отображается. 
Первым делом убедимся, что в нашем манифесте прописано разрешения на использования определения местоположения. Для этого открываем файл AndroidManifest.xml и смотрим чтобы в нем были прописаны строчки


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

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

  • получить данные местоположения
  • оправить всем, кому нужны эти данные.

Теперь подробнее по каждому пункту. Данные о местоположении, которые может получать наше устройство, можно не только при помощи gps, но и также и от сети, используя при этом network provider.
Что же касается второго пункта, то здесь уже играет задача, которую необходимо решить для приложения. К примеру данные о местоположении необходимо отображать на карте (как в нашем случае), но проходит время, приходит новая задача (которую мы в дальнейшем смоделируем) и их еще необходимо будет записывать отдельно в журнал, где собирается история. Но такая задача может быть не едина и мы посмотрим как элегантно решить данную проблему. 
Теперь будем постепенно решать эти проблемы.
Создадим интерфейс, с которым мы будем работать в нашем приложении. Первое, что нам понадобится это установить слушателя, с которого мы будем получать наши данные. Это может быть gps или network.

Таким образом у нас получается 


public interface ILocations  {
    void setUpLocationListener(Context context,long minTime, float minDistance);
}

Создадим для начала класс который будет работать с gps, не забываем реализовать наш интерфейс.


public class GpsLocation implements Ilocations {
    private LocationManager locationManager;
    @Override
    public void setUpLocationListener(Context context,long minTime, float minDistance) {
            locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
            locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTime,minDistance,this);
    }
}

В методе requestLocationUpdates мы указали слушателем, который будет получать информацию, данный класс (this). Это означает, что нам необходимо реализовать интерфейс android.location.LocationListener. Также мы указали GPS_PROVIDER в качесве источника информации.
Давайте же его добавим к нашему интерфейсу.


public interface ILocations extends LocationListener {…}
а самом классе просто переопределим нужные методы. 
@Override
public void onLocationChanged(Location location) {
    Log.d(MapsActivity.LOG_TAG,location.toString());
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
    Log.d(MapsActivity.LOG_TAG,provider.toString()+" status:"+status);
}
@Override
public void onProviderEnabled(String provider) {
    Log.d(MapsActivity.LOG_TAG,provider.toString());
}
@Override
public void onProviderDisabled(String provider) {
    Log.d(MapsActivity.LOG_TAG,provider.toString());
}

onLocationChanged – новые данные о местоположении, объект Location. 
onProviderDisabled – указанный провайдер был отключен пользователем. 
onProviderEnabled – указанный провайдер был включен пользователем.
onStatusChanged – изменился статус указанного провайдера. В поле status могут быть значения OUT_OF_SERVICE (данные будут недоступны долгое время), TEMPORARILY_UNAVAILABLE (данные временно недоступны), AVAILABLE (все ок, данные доступны). 

С решением первой задачи вроде как справились. В дальнейшем создадим класс NetworkLocation, который также реализует этот интерфейс. Только вместо GPS_PROVIDER будет NETWORK_PROVIDER, а работать мы будет через интерфейс ILocations в приложении указав нужную реализацию.
Теперь необходимо решить вторую задачу. В начале статьи было упомянуто про некие паттерны проектирования. Настало время для одного из них, а именно Observer. Данный шаблон относят к поведенческим шаблонам проектирования. Суть его заключается в том, что предоставляется механизм, при котором оповещаются объекты о том, что было изменено состояния объекта, за которым они следят. 
Диаграмма классов выглядит следующим образом

шаблон проектирования Observer

Как это выглядит на практике. Нашему объекту мы говорим, что на него будут подписаны различные объекты, которые хотят знать, что происходит с получением координат. 
Создадим интерфейс, которой будет говорить о том, что на него будут подписаны объекты, которым нужно знать информации о новой координате.


  public interface IPositionObservable {
    //добавить нового слушателя
    void addListener(IPositionListener gpsListener);
    //удалить слушателя
    void removeListener(IPositionListener gpsListener);
    //метод который вызывается при получении новых координат
    void notifyNewLocations(Location location);
}

И интерфейс, который будет говорить, что данный объект может слушать координаты.


public interface IPositionListener {
    void onLocationChanged(Location location);
}

Теперь нам необходимо модифицировать ранее созданный интерфейс ILocations
 и сказать ему, что бы он был тем объектом, за которым наблюдают, добавив IPositionObservable. 


public interface ILocations extends LocationListener,IPositionObservable {
    void setUpLocationListener(Context context,long minTime, float minDistance);
}

Теперь осталось это реализовать в классе. 


public class GpsLocation implements ILocations {
    private LocationManager locationManager;
    //хранит список объектов, которые хотя слушать изменения кординат
    private List<IPositionListener> gpsListenersList = new ArrayList<>();
    @Override
    public void addListener(IPositionListener gpsListener) {
        gpsListenersList.add(gpsListener);
    }
    @Override
    public void removeListener(IPositionListener gpsListener) {
        gpsListenersList.remove(gpsListener);
    }
    /**
     * рассылаем всем подпсианым обектам иформации о том что изменились координаты
     *
     * @param location
     */
    @Override
    public void notifyNewLocations(android.location.Location location) {
        for (IPositionListener gpsListener: gpsListenersList) {
            gpsListener.onLocationChanged(location);
        }
    }
    @Override
    public void setUpLocationListener(Context context,long minTime, float minDistance) {
        locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTime,minDistance,this);    }
    @Override
    public void onLocationChanged(android.location.Location location) {
        Log.d(MapsActivity.LOG_TAG,location.toString());
        notifyNewLocations(location);
    }
    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
        //Log.d(MapsActivity.LOG_TAG,provider.toString()+" status:"+status);
    }
    @Override
    public void onProviderEnabled(String provider) {
        //Log.d(MapsActivity.LOG_TAG,provider.toString());
    }
    @Override
    public void onProviderDisabled(String provider) {
        //Log.d(MapsActivity.LOG_TAG,provider.toString());
    }
}

Отлично, наш класс принимает не только координаты, но и список объектов, которые хотят слушать изменения.

Теперь нам необходимо создать класс, который будет управлять, от какого источника нам слушать данные.  Данный класс реализует шаблон проектирования Singleton, для того, чтобы он у нас был в одном экземпляре.  


public class DI {
    private static DI instance;
    private ILocations location;
    private DI(){}
    public static DI getDI() {
        if (instance == null) {
            instance = new DI();
        }
        return instance;
    }
    public void setLocationDevice(ILocations gpsLocation) {
        this.location = gpsLocation;
    }
    public ILocations getLocation() {
        if (location == null) {
            location = new GpsLocation();
        }
        return location;
    }
}

Как можно заметить, данный класс работает через интерфейс  ILocations и в случае если мы не указали конкретной реализации, то будем получать данные по gps. 
Теперь над необходимо описать xml файл для отображения на экране информации. Для этого откроем файл activity_maps.xml и пропишем следующее:


 <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent">
    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <fragment
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/map"
            tools:context=".MapsActivity"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            />
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:background="@color/background_material_dark"
            android:alpha="0.8"
            >
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/text_view_longitude"
                android:textColor="@color/abc_secondary_text_material_dark"
                android:layout_margin="15dp"
                />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/text_view_latitude"
                android:textColor="@color/abc_secondary_text_material_dark"
                android:layout_margin="15dp"
                />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/text_view_accuracy"
                android:textColor="@color/abc_secondary_text_material_dark"
                android:layout_margin="15dp"
                />
        </LinearLayout>
    </RelativeLayout>
</LinearLayout>

Далее перейдем к нашему MapsActivity. В нашем случае нужно отображать данные на карте и поэтому ему необходимо слушать информацию о местоположении. Для этого указываем интерфейс IPositionListener 


public class MapsActivity extends FragmentActivity implements IPositionListener {…}

 и добавляем его слушателем


DI.getDI().getLocation().addListener(this);

Теперь нам необходимо переопределить метод и прописать в нем логику отображения координат на карте


    @Override
    public void onLocationChanged(Location location) {
        LatLng positions = new LatLng(location.getLatitude(),location.getLongitude());
        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(positions,cameraZoom));
        textViewLongitude.setText(Double.toString(location.getLongitude()));
        textViewLatitude.setText(Double.toString(location.getLatitude()));
        textViewAccuracy.setText(Double.toString(location.getAccuracy()));
        myLocation.setCenter(positions);
        myLocation.setRadius(location.getAccuracy());
    }

Полный текст  MapsActivity показан ниже:


import android.location.Location;
import android.support.v4.app.FragmentActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
public class MapsActivity extends FragmentActivity implements IPositionListener {
    public static String LOG_TAG = "GPS_GOOGLE_MAPS_APP";
    private GoogleMap mMap; // Might be null if Google Play services APK is not available.
    private Circle myLocation;
    private float cameraZoom = 10;
    private TextView textViewLatitude;
    private TextView textViewLongitude;
    private TextView textViewAccuracy;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
        setUpMapIfNeeded();
        textViewAccuracy = (TextView) findViewById(R.id.text_view_accuracy);
        textViewLatitude = (TextView) findViewById(R.id.text_view_latitude);
        textViewLongitude = (TextView) findViewById(R.id.text_view_longitude);
        DI.getDI().getLocation().setUpLocationListener(getApplicationContext(), 0, 0);
        DI.getDI().getLocation().addListener(this);
    }
    @Override
    protected void onResume() {
        super.onResume();
        setUpMapIfNeeded();
    }
    private void setUpMapIfNeeded() {
        // Do a null check to confirm that we have not already instantiated the map.
        if (mMap == null) {
            // Try to obtain the map from the SupportMapFragment.
            mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map))
                    .getMap();
            // Check if we were successful in obtaining the map.
            if (mMap != null) {
                setUpMap();
            }
        }
    }
    private void setUpMap() {
        //создаем координаты для позиции камеры с центром в городе Киев
        LatLng positions = new LatLng(50.452842, 30.524418);
        //перемещаем камеру и оттдаляем ее что мы можно было увидеть город
        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(positions, cameraZoom));
        //Добавляем  маркер с местополежние на Хрещатике
        mMap.addMarker(new MarkerOptions().position(new LatLng(50.450137, 30.524180)).title("Крещатик"));
        myLocation = mMap.addCircle(new CircleOptions().center(positions));
        mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
            @Override
            public void onCameraChange(CameraPosition cameraPosition) {
                cameraZoom = cameraPosition.zoom;
            }
        });
    }
    @Override
    public void onLocationChanged(Location location) {
        LatLng positions = new LatLng(location.getLatitude(),location.getLongitude());
        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(positions,cameraZoom));
        textViewLongitude.setText(Double.toString(location.getLongitude()));
        textViewLatitude.setText(Double.toString(location.getLatitude()));
        textViewAccuracy.setText(Double.toString(location.getAccuracy()));
        myLocation.setCenter(positions);
        myLocation.setRadius(location.getAccuracy());
    }
}

Ну вот теперь можно запускать наше приложение. Мы увидим, где мы находимся и с какой точностью получаем наши координаты.

Комментарий (0)

Войдите с помощью соцсетей:
или
введите свои данные: