2010-01-23 13 views
33

Ich habe mit AsyncTasks in Android gearbeitet und ich habe ein Problem.Android AsyncTask Kontextverhalten

Nehmen Sie ein einfaches Beispiel, eine Aktivität mit einer AsyncTask. Die Aufgabe im Hintergrund macht nichts Spektakuläres, sie schläft nur für 8 Sekunden.

Am Ende der AsyncTask in der onPostExecute() -Methode setze ich nur einen Button-Sichtbarkeitsstatus auf View.VISIBLE, nur um meine Ergebnisse zu überprüfen.

Jetzt funktioniert das großartig, bis der Benutzer entscheidet, die Ausrichtung seines Telefons zu ändern, während die AsyncTask funktioniert (innerhalb des 8-Sekunden-Schlaffensters).

Ich verstehe die Android-Aktivität Lebenszyklus und ich weiß, dass die Aktivität zerstört und neu erstellt wird.

Dies ist, wo das Problem auftritt. Die AsyncTask verweist auf eine Schaltfläche und enthält offenbar einen Verweis auf den Kontext, der die AsyncTask an erster Stelle gestartet hat.

Ich würde erwarten, dass dieser alte Kontext (seit der Benutzer eine Orientierungsänderung verursacht) entweder null wird und die AsyncTask eine NPE für den Verweis auf die Schaltfläche, die es sichtbar machen möchte, zu werfen.

Stattdessen wird keine NPE ausgelöst, die AsyncTask denkt, dass die Schaltflächenreferenz nicht null ist, und legt sie auf visible fest. Das Ergebnis? Nichts passiert auf dem Bildschirm!

Update: Ich habe dies angegangen, indem ich eine WeakReference auf die Aktivität und schalten, wenn eine Konfiguration ändert passiert. Dies ist umständlich.

Hier ist der Code:

public class Main extends Activity { 

    private Button mButton = null; 
    private Button mTestButton = null; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main); 

     mButton = (Button) findViewById(R.id.btnStart); 
     mButton.setOnClickListener(new OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       new taskDoSomething().execute(0l); 
      } 
     }); 
     mTestButton = (Button) findViewById(R.id.btnTest); 
    } 

    private class TaskDoSomething extends AsyncTask<Long, Integer, Integer> 
    { 
     @Override 
     protected Integer doInBackground(Long... params) { 
      Log.i("LOGGER", "Starting..."); 
      try { 
       Thread.sleep(8000); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
      return 0; 
     } 

     @Override 
     protected void onPostExecute(Integer result) { 
      Log.i("LOGGER", "...Done"); 
      mTestButton.setVisibility(View.VISIBLE); 
     } 
    } 
} 

Versuchen Sie es ausführen und während der AsyncTask arbeitet Ihre Telefone Orientierung ändern.

+0

ein bisschen irreführend zu schreiben "eine WeakReference auf die Aktivität zu halten" und als es nicht im Code tun. Schlimmer noch, weiter sagst du "und schalt, wenn eine Konfigurationsänderung passiert", was wirklich nichts bedeutet ... Was schalten? – Ewoks

Antwort

23

AsyncTask ist nicht darauf ausgelegt, sobald eine Aktivität wiederverwendet werden wurde abgerissen und neu gestartet. Das interne Handler-Objekt wird abgestorben, genau wie Sie es angegeben haben. Im Beispiel "Regale" von Romain Guy bricht er einfach alle aktuell laufenden AsyncTask's ab und startet dann neue Änderungen nach der Orientierung.

Es ist möglich, Ihren Thread an die neue Aktivität zu übergeben, aber es fügt eine Menge Rohrleitungen hinzu.Es gibt keine in der Regel auf dem Weg vereinbart, dies zu tun, aber Sie können hier über meine Methode lesen: http://foo.jasonhudgins.com/2010/03/simple-progressbar-tutorial.html

2

Dies ist die Art von Sache, die mich immer zu verhindern, dass meine Aktivität bei Orientierungsänderung zerstört/neu erstellt wird.

so fügen Sie diese in Ihrer Manifest-Datei auf Ihrem <Activity> Tag zu tun:

android:configChanges="orientation|keyboardHidden" 

Und in Ihrer Aktivitätsklasse onConfigurationChanged außer Kraft setzen:

@Override 
public void onConfigurationChanged(final Configuration newConfig) 
{ 
    // Ignore orientation change to keep activity from restarting 
    super.onConfigurationChanged(newConfig); 
} 
+5

Ihr Ansatz funktioniert, aber haben Sie gute Gegenargumente zu diesem Ansatz? Warum ist es standardmäßig das Betriebssystem zerstört und neu erstellt die Aktivität dann? Mit anderen Worten: Was verliere ich, wenn ich das mache, was Sie vorschlagen? Btw, es funktionierte wie erwartet. Danke für die Antwort. – dnkoutso

+0

So weit ich weiß, wäre die Hauptsache, dass Sie verschiedene Aktivitätslayouts für Quer- und Hochformat haben könnten. Es könnte andere Dinge geben, die du "verlieren" würdest, aber ich weiß nicht, was sie sind. –

+1

Google empfiehlt generell diesen Ansatz, es sei denn, Sie wissen, was Sie tun. – emmby

2

Um dies zu vermeiden Sie die Antwort Givin hier verwenden können: https://stackoverflow.com/a/2124731/327011

Aber wenn man die Aktivität zerstören müssen (verschiedene Layouts für Hoch- und Querformat) können Sie die AsyncTask zu einer öffentlichen Klasse machen (hier lesen, warum sie nicht privat sein sollte Android: AsyncTask recommendations: private class or public class?) und dann eine Methode setActivity erstellen, um den Verweis auf die aktuelle Aktivität immer dann zu setzen, wenn sie zerstört/erstellt wird.

Sie können ein Beispiel hier sehen: Android AsyncTask in external class

3

Wenn Sie nur einen Rahmen brauchen, und es wird nicht für ui Sachen verwenden Sie einfach die Application zu Ihrem AsyncTask.You oft den Kontext für Systemressourcen benötigen passieren kann, beispielsweise.

Versuchen Sie nicht, die Benutzeroberfläche von einer AsyncTask zu aktualisieren, und versuchen Sie, Konfigurationsänderungen selbst zu vermeiden, da dies zu Problemen führen kann. Um die Benutzeroberfläche zu aktualisieren, könnten Sie einen Broadcast-Empfänger registrieren und eine Übertragung senden.

Sie sollten auch die AsyncTask als separate öffentliche Klasse von der Aktivität wie oben erwähnt haben, es macht das Testen viel einfacher. Leider verstärkt Android-Programmierung oft schlechte Praktiken und die offiziellen Beispiele helfen nicht.