Jak obsłużyć AsyncTask podczas obracania ekranu?

88

Dużo czytałem o tym, jak zapisać stan instancji lub jak radzić sobie z niszczeniem mojej aktywności podczas obracania ekranu.

Wydaje się, że istnieje wiele możliwości, ale nie doszedłem do wniosku, który z nich najlepiej sprawdza się przy pobieraniu wyników AsyncTask.

Mam kilka AsyncTasks, które są po prostu uruchamiane ponownie i wywołują isFinishing()metodę działania, a jeśli działanie się kończy, niczego nie aktualizują.

Problem polega na tym, że mam jedno zadanie, które wysyła żądanie do usługi internetowej, która może się nie powieść lub zakończyć, a ponowne uruchomienie zadania spowodowałoby straty finansowe dla użytkownika.

Jak byś to rozwiązał? Jakie są zalety lub wady możliwych rozwiązań?

Janusz
źródło
1
Zobacz moją odpowiedź tutaj . Możesz również znaleźć te informacje o tym, co setRetainInstance(true)faktycznie jest pomocne.
Timmmm
co chciałbym zrobić, to po prostu zaimplementować lokalną usługę, która wykonuje przetwarzanie (w wątku), które wykonuje twój asyncTask. Aby wyświetlić wyniki, prześlij dane do swojej aktywności. Teraz aktywność jest odpowiedzialna tylko za wyświetlanie danych, a przetwarzanie nigdy nie jest przerywane przez obrót ekranu.
Someone Somewhere,
A co z używaniem AsyncTaskLoader zamiast AsyncTask?
Sourangshu Biswas

Odpowiedzi:

6

Moją pierwszą sugestią byłoby upewnienie się, że rzeczywiście potrzebujesz resetowania aktywności przy obracaniu ekranu (zachowanie domyślne). Za każdym razem, gdy miałem problemy z rotacją, dodawałem ten atrybut do mojego <activity>tagu w pliku AndroidManifest.xml i wszystko było w porządku.

android:configChanges="keyboardHidden|orientation"

Wygląda dziwnie, ale to, co robi, przekazuje twojej onConfigurationChanged()metodzie, jeśli jej nie dostarczysz, po prostu nie robi nic poza ponownym pomiarem układu, co wydaje się być całkowicie odpowiednim sposobem obsługi rotacji przez większość czasu .

Jim Blackler
źródło
5
Ale to zapobiegnie zmianie układu działania. A zatem zmusza użytkownika do korzystania z urządzenia w określonej orientacji podyktowanej przez aplikację, a nie jego potrzeby.
Janusz
77
Użycie tej techniki uniemożliwia łatwe korzystanie z zasobów specyficznych dla konfiguracji. Na przykład, jeśli chcesz, aby twój układ, elementy do rysowania, stringi lub cokolwiek innego różniło się w pionie i krajobrazie, będziesz chciał zachować domyślne zachowanie. Zastępowanie zmiany konfiguracji powinno być wykonywane tylko w bardzo szczególnych przypadkach (gra, przeglądarka internetowa itp.), A nie z lenistwa lub wygody, ponieważ ograniczasz się.
Romain Guy,
38
Cóż, to po prostu to, Romain. „Jeśli chcesz, aby twój układ, elementy do rysowania, stringi lub cokolwiek innego różniło się w portrecie i krajobrazie, będziesz chciał zachowania domyślnego”, uważam, że jest to znacznie rzadszy przypadek użycia, niż sobie wyobrażasz. Uważam, że większość programistów nazywa się „bardzo konkretnymi przypadkami”. Korzystanie z układów względnych, które działają we wszystkich wymiarach, jest najlepszą praktyką i nie jest takie trudne. Mówienie o lenistwie jest wysoce błędne, techniki te mają na celu poprawę doświadczenia użytkownika, a nie skrócenie czasu programowania.
Jim Blackler
2
Odkryłem, że działa to idealnie dla LinearLayout, ale podczas korzystania z RelativeLayout nie przerysowuje poprawnie układu podczas przełączania do trybu poziomego (przynajmniej nie na N1). Zobacz te pytania: stackoverflow.com/questions/2987049/…
JohnRock
9
Zgadzam się z Romainem (on wie o czym mówi, rozwija system operacyjny). Co się dzieje, gdy chcesz przenieść aplikację na tablet, a interfejs użytkownika wygląda okropnie po rozciągnięciu? Jeśli podejmiesz tę odpowiedź, będziesz musiał ponownie zakodować całe rozwiązanie, ponieważ poszedłeś z tym leniwym hackiem.
Austyn Mahoney
46

Możesz sprawdzić, jak obsługuję AsyncTaskzmiany i zmiany orientacji na code.google.com/p/shelves . Można to zrobić na różne sposoby, ten, który wybrałem w tej aplikacji, to anulowanie aktualnie uruchomionego zadania, zapisanie jego stanu i rozpoczęcie nowego z zapisanym stanem po utworzeniu nowego Activity. Jest to łatwe do zrobienia, działa dobrze, a jako bonus dba o zatrzymanie zadań, gdy użytkownik opuszcza aplikację.

Możesz również użyć, onRetainNonConfigurationInstance()aby przekazać swojemu AsyncTasknowemu Activity(uważaj jednak, aby nie wyciekać w Activityten sposób poprzedniego ).

Romain Guy
źródło
1
próbowałem i obracanie podczas wyszukiwania książki przerywa i daje mi mniej wyników niż bez obracania, szkoda
max4ever
1
Nie mogłem znaleźć ani jednego użycia AsyncTask w tym kodzie. Istnieje klasa UserTask, która wygląda podobnie. Czy ten projekt jest starszy od AsyncTask?
devconsole
7
AsyncTask pochodzi z UserTask. Pierwotnie napisałem UserTask dla moich własnych aplikacji, a później przekształciłem go w AsyncTask. Przepraszam, że zapomniałem, że została zmieniona.
Romain Guy
@RomainGuy Cześć, mam nadzieję, że u Ciebie wszystko w porządku. Zgodnie z Twoim kodem żądania 2 są wysyłane do serwera, chociaż na początku zadanie jest anulowane, ale nie zostało pomyślnie anulowane. Nie wiem dlaczego. Czy mógłbyś mi powiedzieć, czy istnieje sposób, aby to rozwiązać.
iamcrypticcoder
10

To najciekawsze pytanie dotyczące Androida, jakie widziałem !!! Właściwie to szukałem już rozwiązania przez ostatnie miesiące. Nadal nie rozwiązałem.

Uważaj, po prostu zastępując plik

android:configChanges="keyboardHidden|orientation"

rzeczy to za mało.

Rozważ przypadek, gdy użytkownik odbiera połączenie telefoniczne, gdy AsyncTask jest uruchomiony. Twoje żądanie jest już przetwarzane przez serwer, więc AsyncTask czeka na odpowiedź. W tym momencie Twoja aplikacja działa w tle, ponieważ aplikacja Telefon właśnie pojawiła się na pierwszym planie. System operacyjny może zabić Twoją aktywność, ponieważ działa w tle.

Vit Khudenko
źródło
6

Dlaczego nie zawsze zachowujesz odniesienie do bieżącego AsyncTask w Singletonie dostarczonym przez Androida?

Za każdym razem, gdy rozpoczyna się zadanie, w programie PreExecute lub w konstruktorze, definiujesz:

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

Za każdym razem, gdy kończy się, ustawiasz ją na null.

W ten sposób zawsze masz odniesienie, które pozwala ci zrobić coś takiego jak, onCreate lub onResume, odpowiednio do twojej konkretnej logiki:

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

Jeśli jest zerowy, wiesz, że obecnie żaden nie działa!

:-)

neteinstein
źródło
Czy to zadziała? Czy ktoś to testował? Czy zadanie nadal zostanie zabite przez system, jeśli nastąpi przerwa w rozmowie telefonicznej lub jeśli przejdziemy do nowej czynności, a następnie wrócimy?
Robert
6
Applicationinstancja ma swój własny cykl życia - może zostać zabita również przez system operacyjny, więc to rozwiązanie może spowodować trudny do odtworzenia błąd.
Vit Khudenko,
7
Pomyślałem: jeśli aplikacja zostanie zabita, cała aplikacja zostanie zabita (a tym samym wszystkie AsyncTasks)?
manmal
Myślę, że tę aplikację można zabić bez wykonywania wszystkich asynchronicznych zadań (bardzo rzadko). Ale @Arhimed z kilkoma łatwymi do wykonania weryfikacjami na początku i na końcu każdego asynchronicznego zadania, możesz uniknąć błędów.
neteinstein
3

W Pro android 4. autor zasugerował miły sposób, którego powinieneś użyć weak reference.

Słaba notatka referencyjna

hqt
źródło
3

Z mojego punktu widzenia lepiej przechowywać asynctask poprzez onRetainNonConfigurationInstanceoddzielenie go od bieżącego obiektu Activity i powiązanie go z nowym obiektem Activity po zmianie orientacji. Tutaj znalazłem bardzo fajny przykład pracy z AsyncTask i ProgressDialog.

Yury
źródło
2

Android: przetwarzanie w tle / Async Opeartion ze zmianą konfiguracji

Aby utrzymać stan pracy asynchronicznej podczas procesu w tle: możesz skorzystać z fragmentów.

Zobacz następujące kroki:

Krok 1: Utwórz fragment bez nagłówka, powiedzmy zadanie w tle i dodaj w nim prywatną klasę zadania asynchronicznego.

Krok 2 (krok opcjonalny): jeśli chcesz umieścić kursor ładowania na górze swojej aktywności, użyj poniższego kodu:

Krok 3: W głównym działaniu zaimplementuj interfejs BackgroundTaskCallbacks zdefiniowany w kroku 1

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
źródło
1

Należy wziąć pod uwagę, czy wynik AsyncTask powinien być dostępny tylko dla działania, które uruchomiło zadanie. Jeśli tak, to odpowiedź Romaina Guya jest najlepsza. Jeśli powinien być dostępny dla innych działań Twojej aplikacji, onPostExecutemożesz użyć LocalBroadcastManager.

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

Musisz również upewnić się, że działanie poprawnie obsługuje sytuację, gdy transmisja jest wysyłana, gdy aktywność jest wstrzymana.

Juozas Kontvainis
źródło
1

Spójrz na ten post . W tym poście AsyncTask wykonuje długotrwałe operacje i przecieki pamięci, gdy obrót ekranu odbywa się w jednej przykładowej aplikacji. Przykładowa aplikacja jest dostępna w źródłowej kuźni

Vahid
źródło
0

Moje rozwiązanie.

W moim przypadku mam łańcuch AsyncTasks z tym samym kontekstem. Aktywność miała dostęp tylko do pierwszej. Aby anulować jakiekolwiek uruchomione zadanie, wykonałem następujące czynności:

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);
     }
}

Zadanie doInBackground():

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

Aktywność onStop()lub onPause():

protected void onStop() {
    super.onStop();
    TaskLoader.cancel();
}
Pitt90
źródło
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
źródło
0

możesz także dodać androida: configChanges = "keyboardHidden | Orientacja | screenSize"

Mam nadzieję, że na twój wyraźny przykład pomoże

 <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
źródło