Как обрабатывать AsyncTask во время вращения экрана?
Я много читал о том, как сохранить состояние моего экземпляра или как бороться с моей деятельностью, которая будет уничтожена во время вращения экрана.
Кажется, у меня много возможностей, но я не понял, какой из них лучше всего подходит для получения результатов AsyncTask.
У меня есть несколько AsyncTasks, которые просто запускаются снова и вызывают метод isFinishing()
активности, и если действие заканчивается, они ничего не обновят.
Проблема заключается в том, что у меня есть одна Задача, которая выполняет запрос к веб-службе, которая может потерпеть неудачу или преуспеть, а перезапуск задачи приведет к финансовым потерям для пользователя.
Как бы вы решили это? Каковы преимущества или недостатки возможных решений?
Ответы
Ответ 1
Мое первое предложение состояло в том, чтобы убедиться, что вам действительно нужна ваша активность, чтобы быть reset при вращении экрана (поведение по умолчанию). Каждый раз, когда у меня возникали проблемы с поворотом, я добавил этот атрибут в свой тег <activity>
в AndroidManifest.xml и был в порядке.
android:configChanges="keyboardHidden|orientation"
Это выглядит странно, но то, что он делает, передается вашему методу onConfigurationChanged()
, если вы его не поставляете, он просто ничего не делает, кроме повторной оценки макета, что, кажется, вполне адекватный способ обработки вращайте большую часть времени.
Ответ 2
Вы можете проверить, как я обрабатываю AsyncTask
и изменения ориентации на code.google.com/p/shelves. Существуют различные способы сделать это, я выбрал в этом приложении, чтобы отменить любую текущую задачу, сохранить ее состояние и запустить новый с сохраненным состоянием при создании нового Activity
. Это легко сделать, он работает хорошо и в качестве бонуса он заботится о том, чтобы остановить ваши задачи, когда пользователь покидает приложение.
Вы также можете использовать onRetainNonConfigurationInstance()
, чтобы передать ваш AsyncTask
в новый Activity
(будьте осторожны, чтобы не пропустить предыдущий Activity
).)
Ответ 3
Это самый интересный вопрос, который я видел относительно Android!!! На самом деле я уже искал решение в течение последних месяцев. Все еще не решили.
Будьте осторожны, просто переопределяя
android:configChanges="keyboardHidden|orientation"
недостаточно.
Рассмотрим случай, когда пользователь получает телефонный звонок во время работы AsyncTask. Ваш запрос уже обрабатывается сервером, поэтому AsyncTask ожидает ответа. В этот момент ваше приложение идет в фоновом режиме, потому что приложение "Телефон" только что появилось на переднем плане. ОС может убить вашу деятельность, поскольку она находится в фоновом режиме.
Ответ 4
Почему бы вам не всегда ссылаться на текущую AsyncTask на Singleton, предоставляемую Android?
Всякий раз, когда запускается задача, на PreExecute или на строителе вы определяете:
((Application) getApplication()).setCurrentTask(asyncTask);
Всякий раз, когда он заканчивается, вы устанавливаете его равным нулю.
Таким образом, у вас всегда есть ссылка, которая позволяет вам делать что-то вроде onCreate или onResume, как присваивается для вашей конкретной логики:
this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();
Если он равен нулю, вы знаете, что в настоящее время его нет!
: -)
Ответ 5
Самый правильный способ - использовать фрагмент, чтобы сохранить экземпляр задачи async, за повороты.
Вот ссылка на очень простой пример, позволяющий легко интегрировать эту технику в ваши приложения.
https://gist.github.com/daichan4649/2480065
Ответ 6
В Pro android 4
. автор предлагает хороший способ, чтобы вы использовали weak reference
.
Слабая справочная нота
Ответ 7
С моей точки зрения, лучше сохранить asynctask с помощью onRetainNonConfigurationInstance
отделить его от текущего объекта Activity и привязать его к новому объекту Activity после изменения ориентации. Здесь Я нашел очень хороший пример работы с AsyncTask и ProgressDialog.
Ответ 8
Android: фоновая обработка/выход Async с изменением конфигурации
Чтобы поддерживать состояния асинхронного вывода во время фонового процесса:
вы можете воспользоваться фрагментами.
См. следующие шаги:
Шаг 1. Создайте фрагмент без заголовка, скажем, фоновая задача и добавьте в него закрытый класс задач async.
Шаг 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
}
}
Ответ 9
Следует рассмотреть вопрос о том, должен ли результат AsyncTask быть доступен только для активности, которая запустила задачу. Если да, то Ромен Гай отвечает. Если он должен быть доступен для других действий вашего приложения, то в onPostExecute
вы можете использовать LocalBroadcastManager
.
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));
Вам также необходимо убедиться, что активность правильно обрабатывает ситуацию, когда широковещательная передача отправляется, когда действие приостановлено.
Ответ 10
Посмотрите на это сообщение . Это сообщение включает в себя AsyncTask, выполняющую длительную работу и утечку памяти, когда вращение экрана происходит как в одном примере приложения. Пример приложения доступен на исходной кузнице
Ответ 11
Мое решение.
В моем случае у меня есть цепочка AsyncTasks с тем же контекстом. Активность имела доступ только к первому. Чтобы отменить любую выполняемую задачу, я сделал следующее:
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();
}
Ответ 12
@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);
}
}
}
Ответ 13
вы также можете добавить
андроид: configChanges = "keyboardHidden | ориентация | Размер экрана"
к вашему манифестному примеру, я надеюсь, что он поможет
<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">