Absturz der APP nach Auslagerung der Datengenerierung in einen Thread

  • Antworten:9
BoFiaZ
  • Forum-Beiträge: 5

29.11.2017, 12:00:37 via Website

Hi,

ich erzeuge in meiner App eine Heatmap mit über 50.000 Koordinaten. Nach dem Start braucht die APP ca. 10 Sekunde bis alle Daten geladen sind und zeigt die Heatmap anschließend wie gewünscht an.

Über einen eigenen Thread für die Erstellung der Heatmap wollte ich nun erreichen, dass nach dem Start der APP die Google Map ohne Heatmap direkt angezeigt wird und erst nach Fertigstellung der Heatmap (nach ca. 10 Sekunden), auf die Map projiziert wird. Leider stürzt die APP genau an dieser Stelle ab, an der ich den HeatmapTileProvider mit der Methode addTileOverlay() an die Map übergebe.

Ich ändere an dem funktionierenden Code nichts, außer dass ich in die run() Methode des Threads die Erzeugung des HeatmapTileProvider() mit allen Koordinaten auslagere.

Unterbrochen wir die Ausführung an der Stelle mMap.addTileOverlay(new TileOverlayOptions().tileProvider(mProvider()); mit der Ausgabe AndroidRuntime: FATAL EXCEPTION: Thread-10 und der Meldung com.google.android.gms.maps.GoogleMap.addTileOverlay(Unknown Source:2). Es sieht also alles danach aus als wäre die Ressource unbekannt. Da die APP jedoch ohne den Thread mit der Heatmap bestens funktioniert, liegt es wohl am Thread selbst. Aber ich kann leider nicht feststellen was daran falsch ist und bräuchte Eure Hilfe.

Anbei zur Verdeutlichung die entsprechenden Codezeilen:

@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;

    new Thread(new Runnable() {
        @Override
        public void run() {
            mProvider = new HeatmapTileProvider
                    .Builder()
                    .weightedData(getData())
                    .build();

            addHeatmap();
        }
    }).start();
}

private void addHeatmap() {
    Log.d(TAG, "addHeatmap()");
    mMap.addTileOverlay(new TileOverlayOptions().tileProvider(mProvider));
}

Für Eure Bemühungen vielen Dank!

Gruß

Antworten
swa00
  • Forum-Beiträge: 3.704

29.11.2017, 12:09:21 via Website

Hallo Björn,

na der Error ist ja vielsagend :-)

Man müsste also vermuten :
Kann es sein , dass auf den UI Thread zugegriffen wird ?

Liebe Grüße - Stefan
[ App - Entwicklung ]

Pascal P.

Antworten
BoFiaZ
  • Forum-Beiträge: 5

29.11.2017, 12:22:35 via Website

Hi Stefan,

leider habe ich nicht mehr, was ich Dir als Fehlermeldung anbieten kann.

Aber du hast recht. In der run() Methode rufe ich über die Funktion addHeatmap() und dem darin enthaltenen Code den UI-Thread auf.

Ich werde googeln und mir Beispiele suchen wie mein Vorhaben richtig angegangen wird. Für Tipps bin ich natürlich jederzeit dankbar.

Ich danke Dir...

Antworten
Pascal P.
  • Admin
  • Forum-Beiträge: 11.286

29.11.2017, 12:27:01 via App

Bei Threads:
UI muss immer im UI Thread erledigt werden.
Somit entweder Callback falls es länger dauert oder Context#runOnUiThread

LG Pascal //It's not a bug, it's a feature. :) ;)

BoFiaZ

Antworten
swa00
  • Forum-Beiträge: 3.704

29.11.2017, 12:27:37 via Website

dann bau dir mal folgenden Construct drumrum

runOnUiThread(new Runnable()
{
@Override
public void run()
{
mProvider = new HeatmapTileProvider
.Builder()
.weightedData(getData())
.build();
addHeatmap();
}
});

— geändert am 29.11.2017, 12:28:10

Liebe Grüße - Stefan
[ App - Entwicklung ]

Pascal P.BoFiaZ

Antworten
BoFiaZ
  • Forum-Beiträge: 5

29.11.2017, 12:51:54 via Website

Läuft. Vielen Dank!

Allerdings habe ich mit runOnUiThread() wieder das alte unschöne Ereignis. der Bildschirm bleib weiß bis alles geladen wird. Dann also doch eher Callbacks?

Ich möchte ja das die leere Karte zuerst angezeigt wird und die Heatmap nachgeladen wird. Ich schaue mir noch die Callback Variante an. Wenn das damit lösbar ist, wäre das super.

Antworten
BoFiaZ
  • Forum-Beiträge: 5

29.11.2017, 13:03:48 via Website

Vielen Dank für Eure Hilfe. Ich habe mein Code nun wie folgt abgeändert und es funktioniert alles so wie gewünscht.

Handler mHandler = new Handler();

@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;

    new Thread(new Runnable() {
        @Override
        public void run() {
            mProvider = new HeatmapTileProvider
                    .Builder()
                    .weightedData(getData())
                    .build();

            addHeatmap();
        }
    }).start();
}

private void addHeatmap() {
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            mMap.addTileOverlay(new TileOverlayOptions().tileProvider(mProvider));
        }
    });
}

Noch eine letzte Frage an die Experten... ist es sinnvoll so viele Daten auf einmal aus der Datenbank zu lesen oder gibt es da Wege es besser zu machen?

Antworten
swa00
  • Forum-Beiträge: 3.704

29.11.2017, 13:10:43 via Website

Ich habe schon ein wenig gezuckt , als du 50000 erwähnt hattest - Warum denn ??
Unnötiger Resourcenverbrauch und du siehst ja schon , dass du an die Grenze kommst.

Berechne dir deine Tiles so , wie du sie nur benötigst .. und verwende nicht nur einen Thread sondern evt. mehrere.

Also die Logik selbst umsetzen - Das da oben ist eher "unsauber"
Ich möchte nicht wissen , was die app bei etwas "preisgünstigeren" Devices macht :-)

— geändert am 29.11.2017, 13:12:18

Liebe Grüße - Stefan
[ App - Entwicklung ]

Pascal P.BoFiaZ

Antworten
BoFiaZ
  • Forum-Beiträge: 5

29.11.2017, 14:28:07 via Website

Momentan habe ich eine sqlite Datenbank mit Koordinaten. Damit ich die Datenmenge verkleinern kann, müsste ich an das Select Statement die Latitude von/bis und Longitude von/bis übergeben.

Ist das der richtige Ansatz? Ich müsste somit die Werte Lat/Lon (min/max) der aktuellen Ansicht besitzen. Gibt es so etwas oder geht das komplett in die falsche Richtung?

Bisher habe ich die Möglichkeit gefunden die Zoomstufe auszugeben und kann darüber die Anzahl der Kacheln bestimmen. Da fehlt mir aber immer noch die genauen min/max Koordinaten die ich an das Select Statement übergeben kann.

Boah... ich weiß gerade nicht wie ich es benennen kann.

Antworten
swa00
  • Forum-Beiträge: 3.704

29.11.2017, 14:46:17 via Website

An der Stelle muss ich dir mitteilen , dass ich mit der GoogleMap-API nicht arbeite.

Ich bin nur auf den Punkt eingegangen, dass du im Thread 50000 Koordinaten einließt.
Egal ob das jetzt Koordinaten oder was Anderes ist, aber eine Loop in dieser Grösse beim Start der App ist schon ein wenig ungewöhnlich .

Was sagt eigentlich dein Speicher ? der müsste eigentlich doch auch schon an der Grenze sein ..

Ich würde mir aus der Datenbank jeweils mit einem berechnetem Distance-Model nur das herausnehmen , was benötigt wird und das in einer Arraylist verwalten.

Und wenn es sich ändert , wieder neu ..

P.S mir ist aufgefallen , dass du in deiner "Lösung" innerhalb eines Threads (A) wieder einen Thread (B) aufrufst. (Keinen UI-Thread) Thread A wartet allerdings nicht, dass Thread B fertig ist
Ich vermute jetzt mal , dass dir das zu einem Timer-Verhängnis wird und du dadurch evt. keinen sauberen Abschluss hast.

Kann es sein , dass du aus dem C++ Bereich kommst ???

— geändert am 29.11.2017, 15:49:24

Liebe Grüße - Stefan
[ App - Entwicklung ]

Pascal P.

Antworten