Как обрабатывать 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

Ответ 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">