[Tutorial] GPS und Threading

Status
Für weitere Antworten geschlossen.
A

Artwork

Ambitioniertes Mitglied
10
Hallo alle zusammen,

seit einiger Zeit beschäftige ich mich mit dem Thema GPS im Hinblick auf Location Based Services (LBS) mit Android. Da ich zu diesem Thema im ganzen Web kaum gute Tutorials bzw. Beispiele/Lösungsansätze gefunden habe. möchte ich euch hiermit eine meiner Lösungen zeigen und erklären.

Beschreibung:

Die App die wir schreiben ist ganz easy. Wir benötigen ein Modul, welches uns in definierten Abständen GPS Ortskoordinaten zur Verfügung stellt. Mit diesen Koordinaten, genauer gesagt eine longitude zu Deutsch Geographische Länge und eine latitude zu Deutsch Geographische Breite, können wir dann weiter nette Dinge anstellen, wie z.B. die passende Adresse ermitteln.

Diese Daten wollen wir dann ganz einfach darstellen :) Das Problem dabei ist, dass das wir "das Gewinnen" z.B. der Adresse aber auch das Darstellen der Adresse auf unserem View nicht im sog. Main UI Thread ablaufen lassen können, weil dieser schon für das Zeichnen der View Elemente und für die Verarbeitung der Events zuständig ist. Wir wollen ja nicht, dass das Userinterface blockiert wird oder die App durch einen Timeout schließt. Deshalb lagern wir lastige Prozesse in Threads aus, die dann unabhängig vom Hauptthread laufen.

Also fangen wir an:

GPS Modul

Voraussetzungen:
Damit unsere App auch das Hardware GPS aktivieren und nutzen kann müssen wir unsere AndroidManifest.xml um Folgendes erweitern:

Im <application> Bereich für Zufriff auf Google Maps API, einfach direkt darunter:

Code:
<uses-library android:name="com.google.android.maps" />

...und dann hinter </application> für GPS und Internet:

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

Ich nenne die GPS Klasse mal GeoDataGateway. Diese startet das Hardware GPS. Dann benötigt man noch ein sog. LocationListener. Der auf Updates vom GPS Empfänger horcht. Eig ganz einfach und so siehts aus:

Code:
import ..... //was zu importieren ist seht ihr dann selbst

public class GeoDataGateway implements LocationListener{

	public LocationManager lm = null;	
	private MainActivity SystemService = null;
	//lat, lng
	private double mLongitude = 0;
	private double mLatitude = 0;
	
        //starten
	public GeoDataGateway(MainActivity  sservice){
		this.SystemService = sservice;
		this.startLocationService();
	}
	
	public void startLocationService(){		
		this.lm = (LocationManager) this.SystemService.getSystemService(Context.LOCATION_SERVICE);
		this.lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 3000, 2, this);
	}
	
	//hier spielt sich dann alles ab eig^^
	public void onLocationChanged(Location location) {
		location = this.lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
		try {
			this.mLongitude = location.getLongitude();
			this.mLatitude = location.getLatitude();	
		} catch (NullPointerException e) {
			Log.i("Null pointer exception " + mLongitude + "," + mLatitude, null);
		}
	}	

	public void onProviderDisabled(String provider) {//ist fuer unsere zwecke nicht von bedeutung	
	}

	public void onProviderEnabled(String provider) { //ist fuer unsere zwecke nicht von bedeutung		
	}

	public void onStatusChanged(String provider, int status, Bundle extras) {//ist fuer unsere zwecke nicht von bedeutung	
	}
}

Ich habe die Klasse hier auf das Wesentliche beschränkt. public void startLocationService startet den GPS Empfänger. In requestLocationUpdates(LocationManager.GPS_PROVIDER, 3000, 2, this) kann man angeben in welchen Zeitabständen empfangen werden soll, hier alle 3000 ms, und welche Genauigkeit die Daten in Metern haben sollen, hier 2.

Die Methode onLocationChanged wird dann immer ausgelößt, wenn sich der Ort ändert ;) und dort setzte ich dann die Felder lat und lng. Um lat und lng kapseln zu können führen wir den GeoPoint ein und implementieren die Methode getCurrentGeoPoint:

Code:
public GeoPoint getCurrentGeoPoint(){
		if((this.mLongitude != 0) & (this.mLatitude != 0)){
			return new GeoPoint((int)(this.mLatitude*1E6), (int)(this.mLongitude*1E6));
		}else{
			return new GeoPoint(0,0);
		}
}

Wenn man nun eine Instanz dieser Klasse erzeugt und versucht sich einen GeoPunkt zu holen, wird man nichts bzw. eine Fehlermeldung bekommen. Der Location Service läuft nämlich automatisch im Hintergrund ab. Wir brauchen etwas, dass uns benachrichtigt, wenn onLocationChanged ausgeführt wurde. Dies macht man über sog. Handler/Message Handler. Ein solcher Handler sieht so aus, er gehört in eure MainActivity, also den MainThread:
Code:
Handler myViewUpdateHandler = new Handler(){
		 
        public void handleMessage(Message msg) {
                switch (msg.what) {
                case UPDATE_LOCATION:
                	//jetzt haben wir unsere gps daten
                }
                super.handleMessage(msg);
        }
};

...und...

Code:
protected static final int UPDATE_LOCATION = 0;

...dieses Feld kommt natürlich auch das GeoDataGateway, wo wir die Message UPDATE_LOCATION noch senden müssen:

Code:
this.mLongitude = location.getLongitude();
this.mLatitude = location.getLatitude();	
Message msg = Message.obtain();
msg.what = UPDATE_LOCATION;
this.SystemService.myViewUpdateHandler.sendMessage(msg);

Das wars auch schon mit dem GPS. Im Handler könnten wir z.B. eine TextView setzten und die Daten hineinschreiben. Das ist wie oben beschrieben aber nicht sinnvoll. Es muss nämlich in einem Thread laufen.

Das Threading

In Java gibt es mehrere Möglichkeiten des Threading, hier beschrieben Painless Threading. In Android wird die Klasse AsyncTask benutzt. Mit dieser Klasse implementiert man auch folgende Methoden(selbsterklärend;)):

  1. onPreExecute
  2. doInBackground
  3. onPostExecute
  4. onProgressUpdate

Der Mehode doInBackground kann man Parameter übergeben. DoInbackground liefert dann einen Wert und übergibt diesen an onPostExecute. Die lastigen Dinge machen wir dann also in doInBackground.

Ich habe eine allgemeine Task Klasse gebastelt, denn in einer Android App kann es ein Haufen von Threads geben:

Code:
public class AppTask extends AsyncTask<AppTask.Payload, Object, AppTask.Payload>
{
    public static final String TAG = "AppTask";

    public static final int APPTASK_1 = 1001;

    protected void onPreExecute() {
    }
    
    public void onPostExecute(AppTask.Payload payload)
    {
        switch(payload.taskType) {
        case APPTASK_1:	
        	 MainActivity app1 = 
                 (MainActivity) payload.data[0];       	
        	if(payload.result != null){
        		String[] locs = (String[]) payload.result;
                app1.streetView.setText(locs[0].toString());
                app1.cityView.setText(locs[1].toString());
        	}     	
            break;
        }
    }

    public void onProgressUpdate(Object... value){
     //hier könnte man zb einen ladebalken "laden" lassen
    }

    public AppTask.Payload doInBackground(AppTask.Payload... params)
    {
        AppTask.Payload payload = params[0];  
            switch(payload.taskType) {
            case APPTASK_1:
            	MainActivity app2 = 
                    (MainActivity) payload.data[0];
             
            	GeoPoint point = ((GeoPoint)payload.data[1]);
            	
            	Geocoder geoc = new Geocoder(app2.getBaseContext(), Locale.getDefault());
        		List<Address> addresses;
        		String street = "";
        		String city = "";
        		
        		try {
        			addresses = geoc.getFromLocation(point.getLatitudeE6()/1E6, point.getLongitudeE6()/1E6, 1);
        		    street = addresses.get(0).getAddressLine(0);
        		    city = addresses.get(0).getLocality();
        		} catch (IOException e) {
        			e.printStackTrace();
        		}   
        		String[] locs = {street,city};
        		payload.result = locs;
        		
                break;
            }
         
        return payload;
    }   

    public static class Payload
    {
        public int taskType;
        public Object[] data;
        public Object result;
        public Exception exception;

        public Payload(int taskType, Object[] data) {
            this.taskType = taskType;
            this.data = data;
        }
    }
}

Ich definiere meine Threads in dieser einzelnen Klasse und starte die Threads indem ich die jeweilige Konstante übergebe:

Code:
new AppTask().execute(
                            new AppTask.Payload(
                                AppTask.APPTASK_1,
                                new Object[] { MainActivity.this, 
                                                geo.getCurrentPoint() }));
                }

Hier wird ein Thread gestartet, und zwar der APPTASK_1 Thread und es wird der aktuelle GeoPoint übergeben. Dieser Task holt für den GeoPunkt die Adresse im Hintergrund und setzt diese in TextViews in onPostExecute.

Das wars auch schon ;) Ich hoffe ich konnte euch etwas Sinnvolles für eure App Entwicklung beitragen. Es war eine sache die mir viel Kopfzerbrechen bereitet hat und ich bin froh sie endlich gemeistert zu haben :D

Hier noch meine Quellen:

 

Anhänge

  • AppException.txt
    226 Bytes · Aufrufe: 903
  • GeoDataGateway.txt
    2,2 KB · Aufrufe: 993
  • AppTask.txt
    2,3 KB · Aufrufe: 920
  • Danke
Reaktionen: Kardroid, Johan, blackfire185 und 4 andere
Ich habe mal eine Frage zu diesem Thema:
Hast du einen Weg, wie ich in der laufenden App die Werte "Zeitabstand" und "Genauigkeit" Ändern kann?

public void startLocationService(){
this.lm = (LocationManager) this.SystemService.getSystemService(Context.LOCATION_SERVICE);
this.lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 3000, 2, this);
}
 
Dieser Thread sollte eher unter Codeschnipsel verschoben werden, sonst wird er in ein paar Tagen vergraben (in neuen Themen) sein!
 
Kannst du bitte auch MainActivity hochladen?
 
@spezifisch1


Hallo Spezifisch,
der Beitrag ist 8 Jahre alt und der Ersteller war 6 Jahre nicht mehr online.

Aus diesem Grunde ist hier auch mal zu .
 
Status
Für weitere Antworten geschlossen.

Ähnliche Themen

M
  • MikelKatzengreis
Antworten
5
Aufrufe
127
swa00
swa00
R
Antworten
1
Aufrufe
1.093
Tiefkuehlpizza
Tiefkuehlpizza
Laser5001
Antworten
3
Aufrufe
650
swa00
swa00
Zurück
Oben Unten