GPS no Android!

Neste artigo vamos falar de uma das funcionalidades mais comuns encontradas em aplicativos para dispositivos móveis, a captura de localização.

No Android conseguimos implementar esse requisito com muita facilidade. Basicamente, precisamos de duas estruturas, uma classe capaz de iniciar o processo de captura de localização e outro que irá tratar os eventos resultantes desse processo.

O primeiro componente mencionado, responsável por iniciar o processo de localização, é o LocationManager.

Ele possui uma série de métodos úteis, como o requestLocationUpdates, que inicia o processo de captura de localização.

Esse método recebe quatro parâmetros, são eles:

O provedor, esse parâmetro é definido em forma de constante da própria classe LocationManager. E define o tipo de localização que o desenvolvedor deseja obter. Existem três tipos possíveis:
* NETWORK_PROVIDER: Essa constante define que a localização será capturada através das torres de celulares e/ou redes WIFI.
* GPS_PROVIDER: Talvez o mais comum e mais utilizado, a tradicional captura de localização via satélites GPS.

* PASSIVE_PROVIDER: Um tipo mais particular, define que o seu aplicativo irá capturar localizações através de outros serviços.
(Vale ressaltar que podemos utilizar em nossa aplicação varios provedores de uma só vez.)

O intervalo de tempo, definido em mili segundos em que o aplicativo iniciará a captura de localização.
Exemplo: Se você informar 60000 nesse parâmetro, o aplicativo tentará capturar uma localização a cada 1 minuto.

O espaço físico entre uma localização e outra, definido em metros entre a localização atual e a próxima.
Exemplo: Se você informar 1 nesse parâmetro o aplicativo irá ignorar localizações que estejam no mesmo raio de 1 metro.

O tratador de novas localizações seria uma implementação do LocationListener que tratará os eventos referentes a captura de localização.
Por exemplo, quando uma localização for capturada um método dessa interface, mais precisamente o onLocationChanged, será executado.

A classe LocationManager também possui outros métodos importantes tais como:

– O removeUpdates, que para o serviço de captura de localização, lembre de chamar esse método quando não precisar pegar mais coordenadas. Esse processo de captura é caro em termos de bateria e processador.

isProviderEnabled, verifica se o usuário habilitou aquele provedor (passado por parâmetro).

Permissão de localização

Para utilizar alguns recursos do Android, é necessário solicitar permissão no AndroidManifest.xml.

Localização é um destes recursos, por tanto, é necessário informar a seguinte permissão de segurança:

android.permission.ACCESS_FINE_LOCATION.

Agora que estamos familiarizados com a parte teórica, vamos partir para um aplicativo de exemplo.

Crie no Eclipse ou no Android Studio um novo projeto Android, poderá chama-lo de: MyGPS.

Esse projeto terá apenas duas classes: GPSActivity e GPSFacade.

A classe GPSFacade irá conter todos os detalhes do processo de captura de localização.
Ele implementará a classe LocationListener, por tanto conseguirá tratar as localizações que forem capturadas.
E também disponibilizará em forma de métodos públicos os serviços de iniciar e parar a captura de localização.

A classe GPSActivity por sua vez, irá disponibilizar alguns botões para que o usuário consiga iniciar o processo de captura de localização, listar as localizações e exibir a última localização no mapa.

É comum desenvolvedores optarem por realizar todas essas tarefas na Activity, dependendo essa abordagem deixa o seu projeto mais simples.
No entanto, com o uso do Facade conseguimos usar a funcionalidade de captura de localização em vários pontos no nosso sistema de uma forma desacoplada.

As primeiras listagens logo abaixo mostram o nosso Layout XML e a nossa classe GPSActivity:

<LinearLayout 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:orientation="vertical">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fillViewport="true">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TextView
                android:id="@+id/textViewLocations"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:padding="22dp"
                android:text="Hello World" />

            <Button
                android:id="@+id/buttonGps"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="eventGPS"
                android:text="GPS" />

            <Button
                android:id="@+id/buttonLista"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="eventListLocation"
                android:text="Lista Location" />

            <Button
                android:id="@+id/buttonMap"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="eventCheckLocation"
                android:text="Check Location" />
        </LinearLayout>

    </ScrollView>

</LinearLayout>

Agora a Activity que usara o Layout acima:

public class GPSActivity extends Activity {

    private GPSFacade facade = null;
    private TextView textViewLocations = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        facade = GPSFacade.getInstance(this);
        textViewLocations = (TextView) findViewById(R.id.textViewLocations);
    }

    public void eventGPS(View v) {
        if(facade.isCapturingGPSStarted()) {
            Toast.makeText(this, "Serviço de captura já foi solicitado!", Toast.LENGTH_SHORT).show();
        } else if(facade.isGPSEnable()) {
            facade.startCaptureLocation();
        } else {
            buildAlertMessageNoGps();
        }
    }

    public void eventListLocation(View v) {
        List<Location> locations = facade.getListLocation();
        if(locations.isEmpty()) {
            textViewLocations.setText("Sem localizações!");
        } else {
            StringBuilder builder = new  StringBuilder();
            for(Location location : locations) {
                String text = getLocationInString(location);
                builder.append(text);
            }
            textViewLocations.setText(builder.toString());
        }

    }

    public void eventCheckLocation(View v) {
        Location location = facade.getLastLocation();
        if(location != null) {
            String locationStr = getLocationInString(location);
            Log.i("INFO", locationStr);
            showInMap(location);
        }

    }

    private void showInMap(Location location) {
        String url = "http://maps.google.com/maps?daddr=" + location.getLatitude() + "," + location.getLongitude(); 

        Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
        startActivity(browserIntent);
    }

    private String getLocationInString(Location location) {
        StringBuilder builder = new StringBuilder();

        builder.append(" Precisão: " );
        builder.append(location.getAccuracy());

        builder.append(" Provider: " );
        builder.append(location.getProvider());

        builder.append("  Latitude: " );
        builder.append(location.getLatitude());

        builder.append("  Longitude: " );
        builder.append(location.getLongitude());

        builder.append("  Tempo: " );

        Date date = new Date(location.getTime());
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int hours = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);
        String dateString = DateFormat.getDateInstance(DateFormat.SHORT).format(date);

        builder.append(dateString + " - " + hours + ":" + minute + ":" + second);
        builder.append("\n");

        return builder.toString();

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        facade.stopCaptureLocation();
    }

    private void buildAlertMessageNoGps() {
        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage("Seu GPS esta desabilitado, deseja habilita-lo?")
                .setCancelable(false)
                .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                    public void onClick(final DialogInterface dialog, final int id) {
                        startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
                    }
                })
                .setNegativeButton("No", new DialogInterface.OnClickListener() {
                    public void onClick(final DialogInterface dialog, final int id) {
                        dialog.cancel();
                    }
                });
        final AlertDialog alert = builder.create();
        alert.show();
    }
}

Todos os métodos que começam com event no nome correspondem a ação de clique em algum botão.
Vale ressaltar que esse é um padrão que eu coloquei nesse projeto e não é um padrão oficial do Android.

Podemos ver então que teremos três botões:

– eventGPS: Iniciará o processo de captura de localização se o mesmo já não estiver iniciado. Esse método também verificará se o usuário habilitou a opção de uso de GPS no seu dispositivo. Caso não tenha habilitado, o sistema exibe uma mensagem perguntando se ele deseja habilita-lo.

– eventListLocation: Irá recuperar a lista de localizações capturadas. E em seguida irá exibi-las na tela.

– eventCheckLocation: Irá recuperar a última localização da lista e em seguida irá exibi-la no Google Maps.

Também é importante ressaltar que no método onDestroy da Activity o processo de GPS será parado.
Podemos ver então que a classe GPSActivty consome os serviço da classe GPSFacade.

Caso seja necessário utilizar os serviços de GPSFacade em outra estrutura, ou alterar algum comportamento dessa classe, teremos apenas um ponto de modificação e não teremos que duplicar código algum.
Se a implementação fosse totalmente feita na GPSActivity, as duas vantagens citadas acima não seriam alcançadas.

Sem mais delongas, segue logo abaixo a classe GPSFacade:


public class GPSFacade implements LocationListener {

	private static final long MINUTE = 60000;
	private static final long INTERVAL_CAPTURE_IN_MILIS = 2 * MINUTE;

	private static final long INTERVAL_PHYSICAL_SPACE_IN_METERS = 0;

	private Context context;
	private LocationManager locationManager;

	private List<Location> listLocation = new ArrayList<Location>();

	private static GPSFacade instance;

	private boolean capturingGPSStarted = false;

	private GPSFacade(Context context) {
		this.context = context;
		locationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
	}

	public static GPSFacade getInstance(Context context) {
		if(instance == null)
			instance = new GPSFacade(context);

		return instance;
	}

	public boolean isGPSEnable() {
		return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
	}

	public void startCaptureLocation() {
		capturingGPSStarted = true;
		locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, INTERVAL_CAPTURE_IN_MILIS, INTERVAL_PHYSICAL_SPACE_IN_METERS, this);
		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, INTERVAL_CAPTURE_IN_MILIS, INTERVAL_PHYSICAL_SPACE_IN_METERS, this);
	}

	public void stopCaptureLocation() {
		capturingGPSStarted = false;
		locationManager.removeUpdates(this);
	}

	public List<Location> getListLocation() {
		return listLocation;
	}

	public boolean isCapturingGPSStarted() {
		return capturingGPSStarted;
	}

	public Location getLastLocation() {
		if(listLocation.isEmpty())
			return null;

		Comparator<Location> comparator = new Comparator<Location>() {

			@Override
			public int compare(Location one, Location other) {
				Date dataUm = new Date(one.getTime());
				Date dataDois = new Date(other.getTime());
				return dataUm.compareTo(dataDois);
			}
		};

		Collections.sort(listLocation, comparator);
		Collections.reverse(listLocation);

		return listLocation.get(0);
	}

	@Override
	public void onLocationChanged(Location location) {
		listLocation.add(location);
		vibrar();
	}

	private void vibrar() {
		Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
		v.vibrate(300);
	}

	@Override
	public void onProviderDisabled(String provider) {
		// TODO Auto-generated method stub
	}

	@Override
	public void onProviderEnabled(String provider) {
		// TODO Auto-generated method stub
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
		// TODO Auto-generated method stub
	}

}

Utilizei como referência para a criação dessa classe, esse post.

A nossa classe GPSFacade encapsula em apenas uma classe as funcionalidades do LocationManager e LocationListener.
É através dessas duas classes que conseguimos prover serviços úteis para os clientes da mesma.
Ou seja, quem utilizar essa classe, conseguirá acessar de modo simples recursos de GPS.

Alguns pontos importantes

– Quando uma localização é capturada, através do método onLocationChanged, além de adicionar a localização recém capturada na nossa lista também fazemos o celular vibrar.
Para isso, usamos a classe Vibrator do Android.
Para saber mais detalhes do funcionamento dessa estrutura veja esse post.

– Definimos no começo da classe através de constantes, os intervalos físicos e temporais do nosso processo de captura de localização.

Qualquer dúvida pode deixar um comentário!

  1. #1 by Rogerio on Março 14, 2018 - 2:26 pm

    Muito claro e útil.
    Vou usar com algumas modificações em meus exercícios de estudos.
    Certamente indicarei este tutorial como um dos melhores.
    Abraço.

  2. #2 by Ricardo on Março 29, 2016 - 1:16 pm

    Olá Leonardo, o post me ajudou bastante mas queria saber apenas como faço para que ao clicar em um botao ele abra o Próprio app Gloogle Maps do celular com uma coordanada que eu desejo. desde já obrigado

  3. #4 by Anselmo on Junho 23, 2015 - 12:47 am

    Parabéns pelo artigo. Quero saber do sr. como posso fazer para chamar o gps, para que ele me guie até determinado endereço, ou seja, passo o endereço para ele e ele me guia de onde estou até lá. Abraço.

  4. #6 by Miguel Costa on Março 18, 2014 - 11:45 am

    Muito bom o post, tem algum post que mostre o rastreamento de uma rota!?

    • #7 by Leonardo Casasanta on Março 19, 2014 - 2:58 pm

      Ola Miguel,

      Obrigado pelo comentário. Não entendi bem, o que seria um “rastreamento de uma rota”?

      • #8 by miguel17cost on Março 19, 2014 - 6:14 pm

        Leonardo, seria um rastreamento de rota dinâmica, mais especificamente queria saber a distancia percorrida em km de uma rota percorrida dinamicamente!

      • #9 by Leonardo Casasanta on Março 19, 2014 - 6:56 pm

        Miguel,

        Para pegar a distancia entre dois pontos, você pode usar esse método:

        private float getDistanceInMeters(double fromLatitude, double fromLongitude, double toLatitude, double toLongitude) {
        float[] results = new float[3];
        Location.distanceBetween(fromLatitude, fromLongitude, toLatitude, toLongitude, results);
        float distanceMeters = results[0];
        return distanceMeters;
        }

        Acione ele sempre que vc pegar uma nova localização. Ou seja, no public void onLocationChanged(Location location).

        Desse modo você sempre terá a distância atualizada de onde você esta, até onde você quer ir.

        Ai você pode fazer a seguinte lógica, pega a rota através da API da Google. Através da distância, você consegue identificar quando um cara esta saindo da rota, e pode providenciar uma nova requisição para um a nova rota. Desse modo ele teria uma navegação bem dinâmica.

        Espero ter ajudado.

  1. Android & GPS e Google Maps V2 – 100loop.com
  2. Android – GPS e Google Maps V2 | 100loop.com

Deixe uma resposta para Leonardo Casasanta Cancelar resposta