Я много читал о том, как сохранить состояние моего экземпляра или как справиться с разрушением моей активности во время поворота экрана.
Кажется, есть много возможностей, но я не понял, какая из них лучше всего работает для получения результатов AsyncTask.
У меня есть несколько AsyncTasks, которые просто запускаются снова и вызывают isFinishing()
метод действия, и если действие завершается, они ничего не обновляют.
Проблема в том, что у меня есть одна задача, которая выполняет запрос к веб-службе, которая может завершиться неудачно или успешно, а перезапуск задачи приведет к финансовым потерям для пользователя.
Как бы вы это решили? Каковы преимущества или недостатки возможных решений?
setRetainInstance(true)
действительно полезно.Ответы:
Мое первое предложение - убедиться, что вам действительно нужно, чтобы ваша активность сбрасывалась при повороте экрана (поведение по умолчанию). Каждый раз, когда у меня возникали проблемы с вращением, я добавлял этот атрибут в свой
<activity>
тег в AndroidManifest.xml, и все было в порядке.android:configChanges="keyboardHidden|orientation"
Это выглядит странно, но то, что он делает, он передает вашему
onConfigurationChanged()
методу: если вы не предоставите его, он просто не делает ничего, кроме повторного измерения макета, что кажется совершенно адекватным способом обработки поворота большую часть времени .источник
Вы можете узнать, как я справляюсь
AsyncTask
с изменениями ориентации и ориентации, на code.google.com/p/shelves . Есть несколько способов сделать это. В этом приложении я выбрал отменить любую текущую задачу, сохранить ее состояние и запустить новую с сохраненным состоянием при создании новойActivity
. Это легко сделать, он хорошо работает и в качестве бонуса заботится о остановке ваших задач, когда пользователь покидает приложение.Вы также можете использовать
onRetainNonConfigurationInstance()
для переходаAsyncTask
к новомуActivity
(Activity
однако будьте осторожны, чтобы не допустить утечки предыдущего ).источник
Это самый интересный вопрос, который я видел по Android !!! Собственно решение уже искал последние месяцы. До сих пор не решил.
Будьте осторожны, просто переопределив
android:configChanges="keyboardHidden|orientation"
мелочи не хватает.
Рассмотрим случай, когда пользователь получает телефонный звонок во время работы вашей AsyncTask. Ваш запрос уже обрабатывается сервером, поэтому AsyncTask ожидает ответа. В этот момент ваше приложение переходит в фоновый режим, потому что приложение «Телефон» только что вышло на передний план. ОС может убить вашу активность, так как она находится в фоновом режиме.
источник
Почему вы всегда не сохраняете ссылку на текущую AsyncTask в синглтоне, предоставляемом Android?
Когда бы ни запускалась задача, в PreExecute или в построителе вы определяете:
((Application) getApplication()).setCurrentTask(asyncTask);
По завершении вы устанавливаете значение null.
Таким образом, у вас всегда будет ссылка, которая позволяет вам делать что-то вроде onCreate или onResume в зависимости от вашей конкретной логики:
this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();
Если он равен нулю, вы знаете, что в настоящее время ничего не запущено!
:-)
источник
Application
У экземпляра есть свой жизненный цикл - он также может быть убит ОС, поэтому это решение может вызвать трудновоспроизводимую ошибку.Наиболее правильный способ сделать это - использовать фрагмент для сохранения экземпляра асинхронной задачи при поворотах.
Вот ссылка на очень простой пример, позволяющий легко интегрировать эту технику в свои приложения.
https://gist.github.com/daichan4649/2480065
источник
В
Pro android 4
. автор предложил хороший способ, который вы должны использоватьweak reference
.Слабая справка
источник
С моей точки зрения, асинктную задачу лучше сохранить, отделив
onRetainNonConfigurationInstance
ее от текущего объекта Activity и привязав к новому объекту Activity после изменения ориентации. Здесь я нашел очень хороший пример работы с AsyncTask и ProgressDialog.источник
Android: фоновая обработка / Async Opeartion с изменением конфигурации
Чтобы поддерживать состояние асинхронной работы во время фонового процесса: вы можете воспользоваться помощью фрагментов.
См. Следующие шаги:
Шаг 1. Создайте фрагмент без заголовка, скажем, фоновую задачу, и добавьте в него частный класс асинхронной задачи.
Шаг 2 (необязательный шаг): если вы хотите поместить курсор загрузки поверх своей активности, используйте следующий код:
Шаг 3. В своей основной деятельности реализуйте интерфейс BackgroundTaskCallbacks, определенный на шаге 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 }
}
источник
Следует учитывать, что результат AsyncTask должен быть доступен только для действия, которое запустило задачу. Если да, то лучше всего подойдет ответ Ромена Гая . Если он должен быть доступен для других действий вашего приложения,
onPostExecute
вы можете использовать егоLocalBroadcastManager
.LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));
Вам также необходимо убедиться, что активность правильно обрабатывает ситуацию, когда широковещательная передача отправляется, когда активность приостановлена.
источник
Взгляните на этот пост . Этот пост включает в себя выполнение AsyncTask длительной операции и утечки памяти, когда поворот экрана происходит как в одном примере приложения. Пример приложения доступен в исходной кузнице
источник
Мое решение.
В моем случае у меня есть цепочка AsyncTasks с тем же контекстом. У Activity был доступ только к первому. Чтобы отменить любую запущенную задачу, я сделал следующее:
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); } }
Задача
doInBackground()
:protected Void doInBackground(Params... params) { TaskLoader.setTask(this); .... }
Активность
onStop()
илиonPause()
:protected void onStop() { super.onStop(); TaskLoader.cancel(); }
источник
@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); } } }
источник
вы также можете добавить android: configChanges = "keyboardHidden | Ориентация | screenSize"
к вашему примеру манифеста я надеюсь, что это поможет
<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">
источник