Die Antwort von @Romain Guy ist richtig. Trotzdem möchte ich eine Ergänzung von Informationen hinzufügen und einen Zeiger auf eine Bibliothek oder 2 geben, die für AsyncTask mit langer Laufzeit und noch mehr für netzwerkorientierte Asynctasks verwendet werden können.
AsyncTasks wurden für Aufgaben im Hintergrund entwickelt. Und ja, Sie können es mit der cancel
Methode stoppen . Wenn Sie Inhalte aus dem Internet herunterladen, empfehle ich Ihnen dringend, sich um Ihren Thread zu kümmern, wenn es sich um den E / A-Blockierungsstatus handelt . Sie sollten Ihren Download wie folgt organisieren:
public void download() {
while( inputStream.read(buffer) != -1 && !Thread.interrupted() ) {
}
}
Die Verwendung des Thread.interrupted
Flags hilft Ihrem Thread, einen blockierenden io-Status ordnungsgemäß zu beenden. Ihr Thread reagiert schneller auf einen Aufruf der cancel
Methode.
AsyncTask Designfehler
Wenn Ihre AsyncTask jedoch zu lange dauert, treten zwei verschiedene Probleme auf:
- Aktivitäten sind schlecht an den Aktivitätslebenszyklus gebunden und Sie erhalten das Ergebnis Ihrer AsyncTask nicht, wenn Ihre Aktivität stirbt. In der Tat, ja, Sie können, aber es wird der grobe Weg sein.
- AsyncTask sind nicht sehr gut dokumentiert. Eine naive, wenn auch intuitive Implementierung und Verwendung einer Asynctask kann schnell zu Speicherverlusten führen.
RoboSpice , die Bibliothek, die ich vorstellen möchte, verwendet einen Hintergrunddienst, um diese Art von Anforderungen auszuführen. Es wurde für Netzwerkanforderungen entwickelt. Es bietet zusätzliche Funktionen wie das automatische Zwischenspeichern der Ergebnisse von Anforderungen.
Hier ist der Grund, warum AsyncTasks für lange laufende Aufgaben schlecht sind. Das folgende Argument ist eine Anpassung aus Auszügen von RoboSpice-Motivationen : Die App, die erklärt, warum die Verwendung von RoboSpice einen Bedarf auf der Android-Plattform erfüllt.
Der Lebenszyklus von AsyncTask und Aktivität
AsyncTasks folgen nicht dem Lebenszyklus von Aktivitätsinstanzen. Wenn Sie eine AsyncTask innerhalb einer Aktivität starten und das Gerät drehen, wird die Aktivität zerstört und eine neue Instanz erstellt. Aber die AsyncTask wird nicht sterben. Es wird weiterleben, bis es fertig ist.
Wenn dies abgeschlossen ist, aktualisiert die AsyncTask die Benutzeroberfläche der neuen Aktivität nicht. In der Tat wird die frühere Instanz der Aktivität aktualisiert, die nicht mehr angezeigt wird. Dies kann zu einer Ausnahme vom Typ java.lang.IllegalArgumentException führen: Ansicht, die nicht an den Fenstermanager angehängt ist, wenn Sie beispielsweise findViewById verwenden, um eine Ansicht innerhalb der Aktivität abzurufen.
Speicherverlust Problem
Es ist sehr praktisch, AsyncTasks als innere Klassen Ihrer Aktivitäten zu erstellen. Da die AsyncTask die Ansichten der Aktivität bearbeiten muss, wenn die Aufgabe abgeschlossen ist oder ausgeführt wird, erscheint die Verwendung einer inneren Klasse der Aktivität praktisch: Innere Klassen können direkt auf jedes Feld der äußeren Klasse zugreifen.
Dies bedeutet jedoch, dass die innere Klasse einen unsichtbaren Verweis auf ihre äußere Klasseninstanz enthält: die Aktivität.
Auf lange Sicht führt dies zu einem Speicherverlust: Wenn die AsyncTask lange anhält, bleibt die Aktivität "am Leben", während Android sie gerne entfernen möchte, da sie nicht mehr angezeigt werden kann. Die Aktivität kann nicht durch Müll gesammelt werden. Dies ist ein zentraler Mechanismus für Android, um Ressourcen auf dem Gerät zu schonen.
Der Fortschritt Ihrer Aufgabe geht verloren
Sie können einige Problemumgehungen verwenden, um eine Asynctask mit langer Laufzeit zu erstellen und ihren Lebenszyklus entsprechend dem Lebenszyklus der Aktivität zu verwalten. Sie können die AsyncTask entweder in der onStop-Methode Ihrer Aktivität abbrechen oder Ihre asynchrone Aufgabe beenden lassen, ohne ihren Fortschritt zu verlieren und sie erneut mit der nächsten Instanz Ihrer Aktivität zu verknüpfen .
Dies ist möglich und wir zeigen, wie in RobopSpice Motivationen, aber es wird kompliziert und der Code ist nicht wirklich generisch. Darüber hinaus verlieren Sie immer noch den Fortschritt Ihrer Aufgabe, wenn der Benutzer die Aktivität verlässt und zurückkommt. Das gleiche Problem tritt bei Loadern auf, obwohl es ein einfacheres Äquivalent zu AsyncTask mit der oben erwähnten Problemumgehung für das erneute Verknüpfen wäre.
Verwenden eines Android-Dienstes
Am besten verwenden Sie einen Dienst, um Ihre lang laufenden Hintergrundaufgaben auszuführen. Und genau diese Lösung schlägt RoboSpice vor. Auch hier ist es für das Networking konzipiert, kann aber auch auf nicht netzwerkbezogene Inhalte erweitert werden. Diese Bibliothek verfügt über eine Vielzahl von Funktionen .
Dank einer Infografik können Sie sich sogar in weniger als 30 Sekunden ein Bild davon machen .
Es ist wirklich eine sehr, sehr schlechte Idee, AsyncTasks für lange laufende Vorgänge zu verwenden. Trotzdem eignen sie sich gut für kurzlebige Personen wie das Aktualisieren einer Ansicht nach 1 oder 2 Sekunden.
Ich empfehle Ihnen, die RoboSpice Motivations-App herunterzuladen. Sie erklärt dies ausführlich und bietet Beispiele und Demonstrationen der verschiedenen Möglichkeiten, netzwerkbezogene Aufgaben zu erledigen.
Wenn Sie nach einer Alternative zu RoboSpice für nicht netzwerkbezogene Aufgaben suchen (z. B. ohne Caching), können Sie sich auch Tape ansehen .
Romain Guy hat recht. Tatsächlich ist eine asynchrone Aufgabe in jedem Fall dafür verantwortlich, ihren eigenen Job zu beenden. Unterbrechen ist nicht der beste Weg, daher sollten Sie kontinuierlich überprüfen, ob jemand möchte, dass Sie Ihre Aufgabe abbrechen oder stoppen.
Nehmen wir an, Sie machen
AsyncTask
viele Male etwas in einer Schleife. Dann sollten SieisCancelled()
jede Schleife einchecken .while ( true ) { if ( isCancelled()) break; doTheTask(); }
doTheTask()
ist Ihre eigentliche Aufgabe und bevor Sie dies in jeder Schleife tun, prüfen Sie, ob Ihre Aufgabe abgebrochen werden soll.Im Allgemeinen sollten Sie in Ihrer
AsyncTask
Klasse eine Flagge setzen oder ein entsprechendes Ergebnis von Ihrer zurückgeben,doInBackground()
damit Sie in Ihrer KlasseonPostExecute()
überprüfen können, ob Sie das beenden können, was Sie möchten, oder ob Ihre Arbeit in der Mitte abgebrochen wurde.quelle
Folgendes löst Ihr Problem nicht, verhindert es jedoch: Führen Sie im App-Manifest Folgendes aus:
<activity android:name=".(your activity name)" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden|screenSize" > //this line here </activity>
Wenn Sie dies hinzufügen, wird Ihre Aktivität bei Konfigurationsänderungen nicht neu geladen. Wenn Sie bei Änderungen der Ausrichtung einige Änderungen vornehmen möchten, überschreiben Sie einfach die folgende Methode der Aktivität:
@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); //your code here }
quelle
Die Aktivität wird bei einem Orientierungswechsel neu erstellt, ja, das stimmt. Sie können Ihre Asynctask jedoch fortsetzen, wenn dieses Ereignis eintritt.
Sie überprüfen es auf
@Override protected void onCreate(Bundle savedInstanceState) { if ( savedInstanceState == null ) { startAsyncTask() } else { // ** Do Nothing async task will just continue. } }
-Prost
quelle
Aus MVC- Sicht ist Aktivität der Controller . Es ist falsch, dass der Controller Vorgänge ausführt, die die Ansicht überleben (abgeleitet von android.view.View, normalerweise verwenden Sie nur die vorhandenen Klassen wieder). Daher sollte es in der Verantwortung des Modells liegen , AsyncTasks zu starten.
quelle
Sie können verwenden
class MagicAppRestart
von diesem Beitrag zu dem Prozess zu töten zusammen mit allen AsyncTasks; Android stellt den Aktivitätsstapel wieder her (der Benutzer erwähnt nichts). Es ist wichtig zu beachten, dass die einzige Benachrichtigung vor einem Prozessneustart der Aufruf istonPause()
. Gemäß der Android-App-Lebenszykluslogik muss Ihre Anwendung ohnehin für eine solche Beendigung bereit sein.Ich habe es versucht und es scheint zu funktionieren. Dennoch plane ich im Moment, "zivilisiertere" Methoden wie schwache Referenzen aus der Application-Klasse zu verwenden (meine AsyncTasks sind eher kurzlebig und hoffentlich nicht so speicherintensiv).
Hier ist ein Code, mit dem Sie spielen können:
MagicAppRestart.java
package com.xyz; import android.app.Activity; import android.content.Intent; import android.os.Bundle; /** This activity shows nothing; instead, it restarts the android process */ public class MagicAppRestart extends Activity { // Do not forget to add it to AndroidManifest.xml // <activity android:name="your.package.name.MagicAppRestart"/> @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); System.exit(0); } public static void doRestart(Activity anyActivity) { anyActivity.startActivity(new Intent(anyActivity.getApplicationContext(), MagicAppRestart.class)); } }
Den Rest hat Eclipse für ein neues Android-Projekt für com.xyz.AsyncTaskTestActivity erstellt :
AsyncTaskTestActivity.java
package com.xyz; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; public class AsyncTaskTestActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { Log.d("~~~~","~~~onCreate ~~~ "+this); super.onCreate(savedInstanceState); setContentView(R.layout.main); } public void onStartButton(View view) { Log.d("~~~~","~~~onStartButton {"); class MyTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... params) { // TODO Auto-generated method stub Log.d("~~~~","~~~doInBackground started"); try { for (int i=0; i<10; i++) { Log.d("~~~~","~~~sleep#"+i); Thread.sleep(200); } Log.d("~~~~","~~~sleeping over"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } Log.d("~~~~","~~~doInBackground ended"); return null; } @Override protected void onPostExecute(Void result) { super.onPostExecute(result); taskDone(); } } MyTask task = new MyTask(); task.execute(null); Log.d("~~~~","~~~onStartButton }"); } private void taskDone() { Log.d("~~~~","\n\n~~~taskDone ~~~ "+this+"\n\n"); } public void onStopButton(View view) { Log.d("~~~~","~~~onStopButton {"); MagicAppRestart.doRestart(this); Log.d("~~~~","~~~onStopButton }"); } public void onPause() { Log.d("~~~~","~~~onPause ~~~ "+this); super.onPause(); } public void onStop() { Log.d("~~~~","~~~onStop ~~~ "+this); super.onPause(); } public void onDestroy() { Log.d("~~~~","~~~onDestroy ~~~ "+this); super.onDestroy(); } }
main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <Button android:text="Start" android:onClick="onStartButton" android:layout_width="fill_parent" android:layout_height="wrap_content"/> <Button android:text="Stop" android:onClick="onStopButton" android:layout_width="fill_parent" android:layout_height="wrap_content"/> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> </LinearLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.xyz" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="7" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".AsyncTaskTestActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".MagicAppRestart"/> </application> </manifest>
und ein relevanter Teil der Protokolle (beachten Sie, dass nur
onPause
aufgerufen wird ):D/~~~~ (13667): ~~~onStartButton { D/~~~~ (13667): ~~~onStartButton } D/~~~~ (13667): ~~~doInBackground started D/~~~~ (13667): ~~~sleep#0 D/~~~~ (13667): ~~~sleep#1 D/~~~~ (13667): ~~~sleep#2 D/~~~~ (13667): ~~~sleep#3 D/~~~~ (13667): ~~~sleep#4 D/~~~~ (13667): ~~~sleep#5 D/~~~~ (13667): ~~~sleep#6 D/~~~~ (13667): ~~~sleep#7 D/~~~~ (13667): ~~~sleep#8 D/~~~~ (13667): ~~~sleep#9 D/~~~~ (13667): ~~~sleeping over D/~~~~ (13667): ~~~doInBackground ended D/~~~~ (13667): D/~~~~ (13667): D/~~~~ (13667): ~~~taskDone ~~~ com.xyz.AsyncTaskTestActivity@40516988 D/~~~~ (13667): D/~~~~ (13667): ~~~onStartButton { D/~~~~ (13667): ~~~onStartButton } D/~~~~ (13667): ~~~doInBackground started D/~~~~ (13667): ~~~sleep#0 D/~~~~ (13667): ~~~sleep#1 D/~~~~ (13667): ~~~sleep#2 D/~~~~ (13667): ~~~sleep#3 D/~~~~ (13667): ~~~sleep#4 D/~~~~ (13667): ~~~sleep#5 D/~~~~ (13667): ~~~onStopButton { I/ActivityManager( 81): Starting: Intent { cmp=com.xyz/.MagicAppRestart } from pid 13667 D/~~~~ (13667): ~~~onStopButton } D/~~~~ (13667): ~~~onPause ~~~ com.xyz.AsyncTaskTestActivity@40516988 I/ActivityManager( 81): Process com.xyz (pid 13667) has died. I/WindowManager( 81): WIN DEATH: Window{4073ceb8 com.xyz/com.xyz.AsyncTaskTestActivity paused=false} I/ActivityManager( 81): Start proc com.xyz for activity com.xyz/.AsyncTaskTestActivity: pid=13698 uid=10101 gids={} I/ActivityManager( 81): Displayed com.xyz/.AsyncTaskTestActivity: +44ms (total +65ms) D/~~~~ (13698): ~~~onCreate ~~~ com.xyz.AsyncTaskTestActivity@40517238
quelle