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