Wie gehe ich mit einer AsyncTask während der Bildschirmdrehung um?

88

Ich habe viel darüber gelesen, wie ich meinen Instanzstatus speichern oder wie ich damit umgehen kann, dass meine Aktivitäten während der Bildschirmrotation zerstört werden.

Es scheint viele Möglichkeiten zu geben, aber ich habe nicht herausgefunden, welche am besten zum Abrufen der Ergebnisse einer AsyncTask geeignet ist.

Ich habe einige AsyncTasks, die einfach erneut gestartet werden und die isFinishing()Methode der Aktivität aufrufen. Wenn die Aktivität beendet ist, werden sie nichts aktualisieren.

Das Problem ist, dass ich eine Aufgabe habe, die eine Anforderung an einen Webdienst ausführt, die fehlschlagen oder erfolgreich sein kann, und ein Neustart der Aufgabe würde zu einem finanziellen Verlust für den Benutzer führen.

Wie würden Sie das lösen? Was sind die Vor- oder Nachteile der möglichen Lösungen?

Janusz
quelle
1
Siehe meine Antwort hier . Möglicherweise finden Sie diese Informationen setRetainInstance(true)auch hilfreich.
Timmmm
Ich würde einfach einen lokalen Dienst implementieren, der die Verarbeitung (in einem Thread) durchführt, die Ihre asyncTask ausführt. Senden Sie die Daten an Ihre Aktivität, um die Ergebnisse anzuzeigen. Jetzt ist die Aktivität nur noch für die Anzeige von Daten verantwortlich und die Verarbeitung wird niemals durch eine Bildschirmdrehung unterbrochen.
Jemand irgendwo
Was ist mit AsyncTaskLoader anstelle von AsyncTask?
Sourangshu Biswas

Antworten:

7

Mein erster Vorschlag wäre, sicherzustellen, dass Ihre Aktivität tatsächlich auf einer Bildschirmdrehung zurückgesetzt werden muss (das Standardverhalten). Jedes Mal, wenn ich Probleme mit der Rotation hatte, habe ich dieses Attribut meinem <activity>Tag in der Datei AndroidManifest.xml hinzugefügt und war in Ordnung.

android:configChanges="keyboardHidden|orientation"

Es sieht seltsam aus, aber was es bewirkt, gibt es an Ihre onConfigurationChanged()Methode weiter. Wenn Sie keine angeben, misst es nur das Layout neu, was die meiste Zeit eine vollkommen angemessene Methode zu sein scheint, um die Drehung zu handhaben .

Jim Blackler
quelle
5
Dadurch wird jedoch verhindert, dass die Aktivität das Layout ändert. Und zwingt den Benutzer daher, sein Gerät in einer bestimmten Ausrichtung zu verwenden, die von Ihrer Anwendung und nicht von seinen Anforderungen vorgegeben wird.
Janusz
77
Die Verwendung dieser Technik verhindert, dass Sie konfigurationsspezifische Ressourcen problemlos verwenden können. Wenn Sie beispielsweise möchten, dass Ihr Layout oder Ihre Zeichnungen oder Zeichenfolgen oder was auch immer in Hoch- und Querformat unterschiedlich ist, möchten Sie das Standardverhalten. Das Überschreiben der Konfigurationsänderung sollte nur in ganz bestimmten Fällen (einem Spiel, einem Webbrowser usw.) erfolgen und nicht aus Faulheit oder Zweckmäßigkeit, da Sie sich selbst einschränken.
Romain Guy
38
Nun, das ist es nur, Romain. "Wenn Sie möchten, dass Ihr Layout oder Ihre Zeichnungen oder Zeichenfolgen oder was auch immer in Hoch- und Querformat unterschiedlich ist, möchten Sie das Standardverhalten", glaube ich, dass dies ein viel seltenerer Anwendungsfall ist, als Sie sich vorstellen. Was Sie "sehr spezifische Fälle" nennen, sind die meisten Entwickler, die ich glaube. Die Verwendung relativer Layouts, die in allen Dimensionen funktionieren, ist eine bewährte Methode und nicht so schwierig. Die Rede von Faulheit ist höchst falsch. Diese Techniken sollen die Benutzererfahrung verbessern und nicht die Entwicklungszeit verkürzen.
Jim Blackler
2
Ich habe festgestellt, dass dies perfekt für LinearLayout funktioniert, aber bei Verwendung von RelativeLayout wird das Layout beim Umschalten in den Querformatmodus nicht korrekt neu gezeichnet (zumindest nicht beim N1). Siehe diese Fragen: stackoverflow.com/questions/2987049/…
JohnRock
9
Ich stimme Romain zu (er weiß, wovon er spricht, er entwickelt das Betriebssystem). Was passiert, wenn Sie Ihre Anwendung auf ein Tablet portieren möchten und Ihre Benutzeroberfläche im gestreckten Zustand schrecklich aussieht? Wenn Sie sich dieser Antwort nähern, müssen Sie Ihre gesamte Lösung neu codieren, da Sie sich für diesen faulen Hack entschieden haben.
Austyn Mahoney
46

Unter code.google.com/p/shelves können Sie nachlesen , wie ich mit AsyncTasks und Orientierungsänderungen umgehe . Es gibt verschiedene Möglichkeiten, dies zu tun. Die in dieser App ausgewählte Methode besteht darin, eine aktuell ausgeführte Aufgabe abzubrechen, ihren Status zu speichern und eine neue mit dem gespeicherten Status zu starten, wenn die neue Activityerstellt wird. Es ist einfach zu machen, es funktioniert gut und als Bonus sorgt es dafür, dass Ihre Aufgaben gestoppt werden, wenn der Benutzer die App verlässt.

Sie können auch verwenden onRetainNonConfigurationInstance(), um Ihre AsyncTaskan die neue weiterzugeben Activity(achten Sie jedoch darauf, dass die vorherige auf Activitydiese Weise nicht verloren geht.)

Romain Guy
quelle
1
Ich habe es versucht und rotiere während der Buchsuche unterbricht und gibt mir weniger Ergebnisse als wenn ich nicht
rotiere
1
Ich konnte in diesem Code keine einzige Verwendung von AsyncTask finden. Es gibt eine Klasse UserTask, die ähnlich aussieht. Ist dieses Projekt älter als AsyncTask?
Devconsole
7
AsyncTask kam von UserTask. Ich habe UserTask ursprünglich für meine eigenen Apps geschrieben und später in AsyncTask umgewandelt. Tut mir leid, dass ich vergessen habe, dass es umbenannt wurde.
Romain Guy
@RomainGuy Hallo, ich hoffe es geht dir gut. Gemäß Ihrem Code werden 2 Anfragen an den Server gesendet, obwohl die erste Aufgabe abgebrochen wurde, aber nicht erfolgreich abgebrochen wurde. Ich weiß nicht warum. Würden Sie mir bitte sagen, ob es eine Möglichkeit gibt, dies zu beheben?
iamcrypticcoder
10

Dies ist die interessanteste Frage, die ich in Bezug auf Android gesehen habe !!! Eigentlich habe ich schon in den letzten Monaten nach der Lösung gesucht. Noch nicht gelöst.

Seien Sie vorsichtig und überschreiben Sie einfach die

android:configChanges="keyboardHidden|orientation"

Zeug ist nicht genug.

Betrachten Sie den Fall, wenn der Benutzer einen Anruf erhält, während Ihre AsyncTask ausgeführt wird. Ihre Anfrage wird bereits vom Server verarbeitet, sodass die AsyncTask auf eine Antwort wartet. In diesem Moment geht Ihre App in den Hintergrund, da die Telefon-App gerade in den Vordergrund getreten ist. Das Betriebssystem kann Ihre Aktivität beenden, da es sich im Hintergrund befindet.

Vit Khudenko
quelle
6

Warum behalten Sie nicht immer einen Verweis auf die aktuelle AsyncTask auf dem Singleton bei, die von Android bereitgestellt wird?

Wann immer eine Aufgabe gestartet wird, in PreExecute oder im Builder, definieren Sie:

((Application) getApplication()).setCurrentTask(asyncTask);

Immer wenn es fertig ist, setzen Sie es auf null.

Auf diese Weise haben Sie immer eine Referenz, mit der Sie so etwas wie onCreate oder onResume ausführen können, wie es für Ihre spezifische Logik geeignet ist:

this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();

Wenn es null ist, wissen Sie, dass derzeit keine ausgeführt wird!

:-)

neteinstein
quelle
Ob das funktioniert? Hat jemand das getestet? Wird die Aufgabe weiterhin vom System beendet, wenn eine Unterbrechung des Telefonanrufs auftritt oder wenn wir zu einer neuen Aktivität navigieren und dann zurückkehren?
Robert
6
ApplicationDie Instanz hat ihren eigenen Lebenszyklus - sie kann auch vom Betriebssystem beendet werden, sodass diese Lösung einen schwer zu reproduzierenden Fehler verursachen kann.
Vit Khudenko
7
Ich dachte: Wenn die Anwendung beendet wird, wird die gesamte App beendet (und damit auch alle AsyncTasks)?
Manmal
Ich denke, dass die Anwendung beendet werden kann, ohne dass alle Asynctasks ausgeführt werden (sehr selten). Aber @Arhimed mit einigen einfach zu erledigenden Überprüfungen am Anfang und Ende jeder Asynctask können Sie die Fehler vermeiden.
Neteinstein
3

In Pro android 4. Autor hat einen schönen Weg vorgeschlagen, den Sie verwenden sollten weak reference.

Schwacher Hinweis

hqt
quelle
3

Aus meiner Sicht ist es besser, eine asynchrone Aufgabe zu speichern, indem Sie onRetainNonConfigurationInstancesie vom aktuellen Aktivitätsobjekt entkoppeln und nach der Änderung der Ausrichtung an ein neues Aktivitätsobjekt binden. Hier habe ich ein sehr schönes Beispiel gefunden, wie man mit AsyncTask und ProgressDialog arbeitet.

Yury
quelle
2

Android: Hintergrundverarbeitung / Async Opeartion mit Konfigurationsänderung

So behalten Sie den asynchronen Zustand während des Hintergrundprozesses bei: Sie können Fragmente verwenden.

Siehe die folgenden Schritte:

Schritt 1: Erstellen Sie ein Fragment ohne Header, beispielsweise eine Hintergrundaufgabe, und fügen Sie eine private asynchrone Aufgabenklasse hinzu.

Schritt 2 (optionaler Schritt): Wenn Sie einen Ladecursor auf Ihre Aktivität setzen möchten, verwenden Sie den folgenden Code:

Schritt 3: Implementieren Sie in Ihrer Hauptaktivität die in Schritt 1 definierte BackgroundTaskCallbacks-Schnittstelle

class BackgroundTask extends Fragment {
public BackgroundTask() {

}

// Add a static interface 

static interface BackgroundTaskCallbacks {
    void onPreExecute();

    void onCancelled();

    void onPostExecute();

    void doInBackground();
}

private BackgroundTaskCallbacks callbacks;
private PerformAsyncOpeation asyncOperation;
private boolean isRunning;
private final String TAG = BackgroundTask.class.getSimpleName();

/**
 * Start the async operation.
 */
public void start() {
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION ENTER *********");
    if (!isRunning) {
        asyncOperation = new PerformAsyncOpeation();
        asyncOperation.execute();
        isRunning = true;
    }
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION EXIT *********");
}

/**
 * Cancel the background task.
 */
public void cancel() {
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION ENTER *********");
    if (isRunning) {
        asyncOperation.cancel(false);
        asyncOperation = null;
        isRunning = false;
    }
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION EXIT *********");
}

/**
 * Returns the current state of the background task.
 */
public boolean isRunning() {
    return isRunning;
}

/**
 * Android passes us a reference to the newly created Activity by calling
 * this method after each configuration change.
 */
public void onAttach(Activity activity) {
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH ENTER *********");
    super.onAttach(activity);
    if (!(activity instanceof BackgroundTaskCallbacks)) {
        throw new IllegalStateException(
                "Activity must implement the LoginCallbacks interface.");
    }

    // Hold a reference to the parent Activity so we can report back the
    // task's
    // current progress and results.
    callbacks = (BackgroundTaskCallbacks) activity;
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH EXIT *********");
}

public void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE ENTER *********");
    super.onCreate(savedInstanceState);
    // Retain this fragment across configuration changes.
    setRetainInstance(true);
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE EXIT *********");
}

public void onDetach() {
    super.onDetach();
    callbacks = null;
}

private class PerformAsyncOpeation extends AsyncTask<Void, Void, Void> {
    protected void onPreExecute() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPreExecute();
        }
        isRunning = true;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE EXIT *********");
    }

    protected Void doInBackground(Void... params) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND ENTER *********");
        if (callbacks != null) {
            callbacks.doInBackground();
        }
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND EXIT *********");
        return null;
    }

    protected void onCancelled() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL ENTER *********");
        if (callbacks != null) {
            callbacks.onCancelled();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL EXIT *********");
    }

    protected void onPostExecute(Void ignore) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPostExecute();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE EXIT *********");
    }
}

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setRetainInstance(true);
}

public void onStart() {
    super.onStart();
}

public void onResume() {
    super.onResume();
}

public void onPause() {
    super.onPause();
}

public void onStop() {
    super.onStop();
}

public class ProgressIndicator extends Dialog {

public ProgressIndicator(Context context, int theme) {
    super(context, theme);
}

private ProgressBar progressBar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.progress_indicator);
    this.setCancelable(false);
    progressBar = (ProgressBar) findViewById(R.id.progressBar);
    progressBar.getIndeterminateDrawable().setColorFilter(R.color.DarkBlue, android.graphics.PorterDuff.Mode.SCREEN);
}

@Override
public void show() {
    super.show();
}

@Override
public void dismiss() {
    super.dismiss();
}

@Override
public void cancel() {
    super.cancel();
}

public class MyActivity extends FragmentActivity implements BackgroundTaskCallbacks,{

private static final String KEY_CURRENT_PROGRESS = "current_progress";

ProgressIndicator progressIndicator = null;

private final static String TAG = MyActivity.class.getSimpleName();

private BackgroundTask task = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(//"set your layout here");
    initialize your views and widget here .............



    FragmentManager fm = getSupportFragmentManager();
    task = (BackgroundTask) fm.findFragmentByTag("login");

    // If the Fragment is non-null, then it is currently being
    // retained across a configuration change.
    if (task == null) {
        task = new BackgroundTask();
        fm.beginTransaction().add(task, "login").commit();
    }

    // Restore saved state
    if (savedInstanceState != null) {
        Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON CREATE :: "
                + task.isRunning());
        if (task.isRunning()) {
            progressIndicator = new ProgressIndicator(this,
                    R.style.TransparentDialog);
            if (progressIndicator != null) {
                progressIndicator.show();
            }
        }
    }
}

@Override
protected void onPause() {
    // TODO Auto-generated method stub
    super.onPause();

}

@Override
protected void onSaveInstanceState(Bundle outState) {
    // save the current state of your operation here by saying this 

    super.onSaveInstanceState(outState);
    Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON SAVE INSTANCE :: "
            + task.isRunning());
    outState.putBoolean(KEY_CURRENT_PROGRESS, task.isRunning());
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}


private void performOperation() {

            if (!task.isRunning() && progressIndicator == null) {
                progressIndicator = new ProgressIndicator(this,
                        R.style.TransparentDialog);
                progressIndicator.show();
            }
            if (task.isRunning()) {
                task.cancel();
            } else {
                task.start();
            }
        }


@Override
protected void onDestroy() {
    super.onDestroy();
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}

@Override
public void onPreExecute() {
    Log.i(TAG, "CALLING ON PRE EXECUTE");
}

@Override
public void onCancelled() {
    Log.i(TAG, "CALLING ON CANCELLED");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();

}

public void onPostExecute() {
    Log.i(TAG, "CALLING ON POST EXECUTE");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
        progressIndicator = null;
    }
}

@Override
public void doInBackground() {
    // put your code here for background operation
}

}}

Piyush Gupta
quelle
1

Zu berücksichtigen ist, ob das Ergebnis der AsyncTask nur für die Aktivität verfügbar sein soll, mit der die Aufgabe gestartet wurde. Wenn ja, dann ist Romain Guys Antwort am besten. Wenn es für andere Aktivitäten Ihrer Anwendung verfügbar sein soll, onPostExecutekönnen Sie es verwenden LocalBroadcastManager.

LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));

Sie müssen auch sicherstellen, dass die Aktivität die Situation korrekt behandelt, wenn die Übertragung gesendet wird, während die Aktivität angehalten wird.

Juozas Kontvainis
quelle
1

Schauen Sie sich diesen Beitrag an . In diesem Beitrag führt AsyncTask einen Betrieb mit langer Laufzeit und einen Speicherverlust durch, wenn die Bildschirmdrehung in einer Beispielanwendung erfolgt. Die Beispiel-App ist in der Quellschmiede verfügbar

Vahid
quelle
0

Meine Lösung.

In meinem Fall habe ich eine Kette von AsyncTasks mit demselben Kontext. Die Aktivität hatte nur Zugriff auf die erste. Um eine laufende Aufgabe abzubrechen, habe ich Folgendes getan:

public final class TaskLoader {

private static AsyncTask task;

     private TaskLoader() {
         throw new UnsupportedOperationException();
     }

     public static void setTask(AsyncTask task) {
         TaskLoader.task = task;
     }

    public static void cancel() {
         TaskLoader.task.cancel(true);
     }
}

Aufgabe doInBackground():

protected Void doInBackground(Params... params) {
    TaskLoader.setTask(this);
    ....
}

Aktivität onStop()oder onPause():

protected void onStop() {
    super.onStop();
    TaskLoader.cancel();
}
Pitt90
quelle
0
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    final AddTask task = mAddTask;
    if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
        final String bookId = task.getBookId();
        task.cancel(true);

        if (bookId != null) {
            outState.putBoolean(STATE_ADD_IN_PROGRESS, true);
            outState.putString(STATE_ADD_BOOK, bookId);
        }

        mAddTask = null;
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) {
        final String id = savedInstanceState.getString(STATE_ADD_BOOK);
        if (!BooksManager.bookExists(getContentResolver(), id)) {
            mAddTask = (AddTask) new AddTask().execute(id);
        }
    }
}
Atif Mahmood
quelle
0

Sie können auch Android hinzufügen: configChanges = "keyboardHidden | orientierung | screenSize"

Zu Ihrem offensichtlichen Beispiel hoffe ich, dass es hilft

 <application
    android:name=".AppController"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:theme="@style/AppTheme">
eng mohamed emam
quelle