Как справиться с AsyncTask во время поворота экрана? - PullRequest
86 голосов
/ 12 апреля 2010

Я много читал о том, как сохранить состояние моего экземпляра или как справиться с тем, что моя активность разрушается при повороте экрана.

Кажется, есть много возможностей, но я не выяснил, какая из них лучше всего подходит для получения результатов AsyncTask.

У меня есть несколько AsyncTasks, которые просто запускаются снова и вызывают метод isFinishing() действия, и если действие заканчивается, они ничего не обновят.

Проблема в том, что у меня есть одна задача, которая выполняет запрос к веб-службе, которая может дать сбой или завершиться успешно, и перезапуск задачи может привести к финансовым потерям для пользователя.

Как бы вы решили это? Каковы преимущества или недостатки возможных решений?

Ответы [ 13 ]

46 голосов
/ 12 апреля 2010

Вы можете проверить, как я справляюсь с AsyncTask s и изменениями ориентации, на code.google.com / p / shelves . Есть несколько способов сделать это, один из которых я выбрал в этом приложении, чтобы отменить любую текущую задачу, сохранить ее состояние и запустить новую с сохраненным состоянием при создании нового Activity. Это легко сделать, он работает хорошо, и в качестве бонуса он заботится о прекращении ваших задач, когда пользователь покидает приложение.

Вы также можете использовать onRetainNonConfigurationInstance(), чтобы передать AsyncTask новому Activity (однако, будьте осторожны, чтобы не пропустить предыдущий Activity таким образом.)

10 голосов
/ 12 апреля 2010

Это самый интересный вопрос, который я видел относительно Android !!! На самом деле я уже искал решение в течение последних месяцев. Все еще не решили.

Будьте осторожны, просто переопределяя

android:configChanges="keyboardHidden|orientation"

материала недостаточно.

Рассмотрим случай, когда пользователь получает телефонный звонок во время работы AsyncTask. Ваш запрос уже обрабатывается сервером, поэтому AsyncTask ожидает ответа. В этот момент ваше приложение переходит в фоновый режим, потому что приложение Phone только что вышло на передний план. ОС может убить вашу активность, так как она в фоновом режиме.

7 голосов
/ 12 апреля 2010

Моим первым предложением было бы убедиться, что вам действительно нужно, чтобы ваша активность была сброшена при повороте экрана (поведение по умолчанию). Каждый раз, когда у меня возникали проблемы с ротацией, я добавлял этот атрибут в свой тег <activity> в AndroidManifest.xml, и это было просто замечательно.

android:configChanges="keyboardHidden|orientation"

Это выглядит странно, но то, что он делает, передает ваш метод onConfigurationChanged(), если вы не предоставите его, он просто ничего не изменит, кроме повторного измерения макета, который кажется совершенно адекватным способом обработки. вращаться большую часть времени.

6 голосов
/ 19 апреля 2011

Почему вы не всегда сохраняете ссылку на текущий AsyncTask на Singleton, предоставляемый Android?

Каждый раз, когда запускается задача, в PreExecute или в компоновщике, вы определяете:

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

Когда он заканчивается, вы устанавливаете его на нуль.

Таким образом, у вас всегда есть ссылка, которая позволяет вам делать что-то вроде onCreate или onResume в соответствии с вашей конкретной логикой:

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

Если он нулевой, вы знаете, что в данный момент он не запущен!

: -)

4 голосов
/ 07 мая 2013

Наиболее правильным способом для этого является использование фрагмента для сохранения экземпляра асинхронной задачи за поворотами.

Вот ссылка на очень простой пример, позволяющий легко интегрировать эту технику в ваши приложения.

https://gist.github.com/daichan4649/2480065

3 голосов
/ 05 июля 2012

На мой взгляд, лучше хранить асинхронную задачу с помощью onRetainNonConfigurationInstance, отсоединив ее от текущего объекта Activity и связав с новым объектом Activity после изменения ориентации. Здесь Я нашел очень хороший пример работы с AsyncTask и ProgressDialog.

3 голосов
/ 22 марта 2012

В Pro android 4. Автор предложил хороший способ использования weak reference.

Слабая справочная записка

2 голосов
/ 23 августа 2013

Android: фоновая обработка / асинхронная работа с изменением конфигурации

Чтобы сохранить состояния асинхронной работы во время фонового процесса: Вы можете воспользоваться помощью фрагментов.

См. Следующие шаги:

Шаг 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
}

}

1 голос
/ 07 августа 2013

Посмотрите на этот пост . Эта публикация включает в себя AsyncTask, выполняющую длительную операцию и утечку памяти, когда поворот экрана происходит в одном примере приложения. Пример приложения доступен в кузнице источника

1 голос
/ 17 декабря 2012

Необходимо учитывать, должен ли результат AsyncTask быть доступен только для действия, которое запустило задачу. Если да, то Лучше всего ответ Романа Гая . Если оно должно быть доступно для других действий вашего приложения, то в onPostExecute вы можете использовать LocalBroadcastManager.

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

Вам также необходимо убедиться, что активность корректно обрабатывает ситуацию, когда широковещание отправляется, когда активность приостановлена.

...