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ń?
setRetainInstance(true)
faktycznie jest pomocne.Odpowiedzi:
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 .źródło
Możesz sprawdzić, jak obsługuję
AsyncTask
zmiany 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 nowegoActivity
. 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ć swojemuAsyncTask
nowemuActivity
(uważaj jednak, aby nie wyciekać wActivity
ten sposób poprzedniego ).źródło
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.
źródło
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!
:-)
źródło
Application
instancja 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.Najodpowiedniejszym sposobem jest użycie fragmentu w celu zachowania instancji zadania asynchronicznego nad obrotami.
Oto link do bardzo prostego przykładu ułatwiającego naśladowanie integracji tej techniki z aplikacjami.
https://gist.github.com/daichan4649/2480065
źródło
W
Pro android 4
. autor zasugerował miły sposób, którego powinieneś użyćweak reference
.Słaba notatka referencyjna
źródło
Z mojego punktu widzenia lepiej przechowywać asynctask poprzez
onRetainNonConfigurationInstance
oddzielenie 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.źródło
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 }
}
źródło
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,
onPostExecute
moż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.
źródło
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
źródło
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()
lubonPause()
:protected void onStop() { super.onStop(); TaskLoader.cancel(); }
źródło
@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); } } }
źródło
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">
źródło