wake-up-neo.com

Wie behandelt man eine AsyncTask während der Bildschirmdrehung?

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

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 neu gestartet werden und die isFinishing()-Methode der Aktivität aufrufen. Wenn die Aktivität beendet wird, aktualisieren sie nichts. 

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ürdest du das lösen? Was sind die Vor- oder Nachteile der möglichen Lösungen?

86
Janusz

Mein erster Vorschlag wäre, sicherzustellen, dass Sie Ihre Aktivität tatsächlich bei einer Bildschirmrotation zurücksetzen müssen (Standardverhalten). Jedes Mal, wenn ich Probleme mit der Rotation hatte, habe ich dieses Attribut zu meinem <activity>-Tag in der AndroidManifest.xml hinzugefügt.

Android:configChanges="keyboardHidden|orientation"

Es sieht zwar komisch aus, aber was es tut, wird von Ihrer onConfigurationChanged()-Methode übernommen. Wenn Sie keine Methode angeben, wird lediglich das Layout neu gemessen, was anscheinend eine völlig angemessene Art ist, die meisten Rotationsfunktionen zu handhaben Zeit.

6
Jim Blackler

Unter code.google.com/p/shelves können Sie sehen, wie ich mit AsyncTasks umgehe. Es gibt verschiedene Möglichkeiten, dies zu tun. Die von mir in dieser App gewählte Methode besteht darin, jede aktuell ausgeführte Aufgabe abzubrechen, ihren Status zu speichern und eine neue mit dem gespeicherten Status zu starten, wenn die neue Activity erstellt wird. Es ist einfach zu machen, es funktioniert gut und als Bonus kümmert es sich darum, Ihre Aufgaben zu beenden, wenn der Benutzer die App verlässt.

Sie können auch onRetainNonConfigurationInstance() verwenden, um Ihre AsyncTask an die neue Activity zu übergeben (achten Sie jedoch darauf, dass Sie die vorherige Activity-Datei nicht auf diese Weise verlieren.)

46
Romain Guy

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, überschreiben Sie einfach die 

Android:configChanges="keyboardHidden|orientation"

zeug reicht nicht aus.

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

10
Vit Khudenko

Warum beziehen Sie sich nicht immer auf die aktuelle AsyncTask auf dem Singleton, die von Android bereitgestellt wird?

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

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

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

Auf diese Weise haben Sie immer eine Referenz, die es Ihnen ermöglicht, so etwas wie onCreate oder onResume entsprechend Ihrer spezifischen Logik auszuführen:

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

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

:-)

6
neteinstein

Der beste Weg dazu ist, ein Fragment zu verwenden, um die Instanz der asynchronen Task über Rotationen zu behalten. 

Hier ist ein Link zu einem sehr einfachen Beispiel, das die Integration dieser Technik in Ihre Apps erleichtert.

https://Gist.github.com/daichan4649/2480065

4
user2342491

Aus meiner Sicht ist es besser, Asynctask zu speichern, indem Sie onRetainNonConfigurationInstance vom aktuellen Aktivitätsobjekt entkoppeln und nach der Orientierungsänderung an ein neues Aktivitätsobjekt binden. Hier Ich habe ein sehr schönes Beispiel gefunden, wie man mit AsyncTask und ProgressDialog arbeitet.

3
Yury

In Pro Android 4. Der Autor hat eine nette Möglichkeit vorgeschlagen, weak reference zu verwenden. 

Schwache Referenznote

3
hqt

Android: Hintergrundverarbeitung/Async-Operation mit Konfigurationsänderung 

Um den Status der asynchronen Operation während des Hintergrundprozesses beizubehalten: Sie können Fragmente unterstützen.

Siehe die folgenden Schritte: 

Schritt 1: Erstellen Sie ein headerloses Fragment, sagen Sie eine Hintergrundaufgabe und fügen Sie eine private async-Taskklasse hinzu.

Schritt 2 (optionaler Schritt): Wenn Sie einen Ladecursor über 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
}

}

2
Piyush Gupta

Schauen Sie sich diese post an. In diesem Beitrag wird ausgeführt, dass AsyncTask in einer Beispielanwendung eine lange Betriebsdauer und einen Speicherverlust durchführt, wenn eine Bildschirmdrehung auftritt. Die Beispiel-App ist auf der source forge verfügbar.

1
Vahid

Zu berücksichtigen ist, ob das Ergebnis der AsyncTask nur für die Aktivität verfügbar sein sollte, mit der die Task gestartet wurde. Wenn ja, dann ist die Antwort von Romain Guy am besten. Wenn es für andere Aktivitäten Ihrer Anwendung verfügbar sein sollte, können Sie in onPostExecuteLocalBroadcastManager verwenden.

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

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

1

Meine Lösung.

In meinem Fall habe ich eine Kette von AsyncTasks mit demselben Kontext. Aktivität hatte nur Zugriff auf den ersten. 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();
}
0
Pitt90

sie können auch .__ hinzufügen. Android: configChanges = "keyboardHidden | orientation | screenSize"

ich hoffe, es hilft Ihnen bei Ihrem manifesten Beispiel

 <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">
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);
        }
    }
}
0
Atif Mahmood