Как обрабатывать AsyncTask во время поворота экрана?


88

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

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

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

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

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


1
Смотрите мой ответ здесь . Вы также можете найти эту информацию о том, что setRetainInstance(true)действительно полезно.
— Timmmm

я бы просто реализовал локальную службу, которая выполняет обработку (в потоке), которую выполняет ваша asyncTask. Чтобы отобразить результаты, транслируйте данные для своего занятия. Теперь действие отвечает только за отображение данных, и обработка никогда не прерывается поворотом экрана.
— Someone Somewhere

А как насчет использования AsyncTaskLoader вместо AsyncTask ??
— Sourangshu Biswas

Ответы:


6

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

android:configChanges="keyboardHidden|orientation"

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


5
Но это предотвратит изменение макета Activity. И поэтому заставляет пользователя использовать свое устройство в определенной ориентации, продиктованной вашим приложением, а не его потребностями.
— Януш

77
Использование этого метода не позволяет легко использовать ресурсы для конкретной конфигурации. Например, если вы хотите, чтобы ваш макет, чертежи, строки или что-то еще отличалось в портретной и пейзажной ориентации, вам понадобится поведение по умолчанию. Отмена изменения конфигурации должна выполняться только в очень конкретных случаях (игра, веб-браузер и т. Д.), А не из-за лени или удобства, потому что вы ограничиваете себя.
— Romain Guy,

38
Ну вот и все, Ромен. «Если вы хотите, чтобы ваш макет, чертежи, строки или что-то еще отличалось в портретной и пейзажной ориентации, вам нужно поведение по умолчанию», - я считаю, что это гораздо более редкий вариант использования, чем вы предполагаете. Я верю, что то, что вы называете «очень специфическими случаями», - это большинство разработчиков. Использование относительных макетов, которые работают во всех измерениях, - лучшая практика, и это не так сложно. Разговоры о лени ошибочны, эти методы предназначены для улучшения пользовательского опыта, а не для сокращения времени разработки.
— Джим Блэклер,

2
Я обнаружил, что это идеально подходит для LinearLayout, но при использовании RelativeLayout он не перерисовывает макет правильно при переключении в альбомный режим (по крайней мере, не на N1). См.
— Следующие

9
Я согласен с Роменом (он знает, о чем говорит, разрабатывает ОС). Что произойдет, если вы захотите перенести свое приложение на планшет, и ваш пользовательский интерфейс в растянутом виде выглядит ужасно? Если вы воспользуетесь подходом этого ответа, вам нужно будет перекодировать все свое решение, потому что вы использовали этот ленивый взлом.
— Остин Махони 02

46

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

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


1
Я пробовал это и вращение во время прерывания поиска книги и дает мне меньше результатов, чем когда не вращается, очень плохо
— max4ever

1
Я не смог найти ни одного использования AsyncTask в этом коде. Есть похожий класс UserTask. Этот проект предшествует AsyncTask?
— devconsole

7
AsyncTask пришла из UserTask. Изначально я написал UserTask для своих приложений, а затем превратил его в AsyncTask. Извините, что забыл, его переименовали.
— Romain Guy

@RomainGuy Привет, надеюсь, у тебя все хорошо. В соответствии с вашим кодом на сервер отправляются 2 запроса, хотя сначала задача отменяется, но не отменяется успешно. Не знаю почему. Скажите, пожалуйста, есть ли способ решить эту проблему.
— iamcrypticcoder

10

Это самый интересный вопрос, который я видел по Android !!! Собственно решение уже искал последние месяцы. До сих пор не решил.

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

android:configChanges="keyboardHidden|orientation"

мелочи не хватает.

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


6

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

Когда бы ни запускалась задача, в PreExecute или в построителе вы определяете:

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

По завершении вы устанавливаете значение null.

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

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

Если он равен нулю, вы знаете, что в настоящее время ничего не запущено!

:-)


Это сработает? Кто-нибудь это тестировал? Будет ли задача по-прежнему прервана системой, если произойдет прерывание телефонного разговора или если мы перейдем к новому действию, а затем вернемся назад?
— Роберт

6
ApplicationУ экземпляра есть свой жизненный цикл - он также может быть убит ОС, поэтому это решение может вызвать трудновоспроизводимую ошибку.
— Вит Худенко,

7
Я подумал: если приложение убито, убивается все приложение (а значит, и все AsyncTasks)?
— manmal

Я думаю, что приложение можно убить без каких-либо асинтаксических задач (очень редко). Но @Arhimed с помощью простых проверок в начале и в конце каждой асинтаксической задачи вы можете избежать ошибок.
— neteinstein

5

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

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

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


Вот еще один учебник, в котором используются сохраненные фрагменты: blogactivity.wordpress.com/2011/09/01/proper-use-of-asynctask
— devconsole


3

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


2

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
}

}


1

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

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

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


1

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


0

Мое решение.

В моем случае у меня есть цепочка 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();
}

0
@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);
        }
    }
}

0

вы также можете добавить 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">
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.