Как справится с AsyncTask при повороте экрана?


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

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

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

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

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

13 84

13 ответов:

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

android:configChanges="keyboardHidden|orientation"

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

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

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

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

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

android:configChanges="keyboardHidden|orientation"

вещи не хватает.

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

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

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

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

всякий раз, когда он заканчивается вы установите его в null.

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

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

Если это null вы знаю, что в настоящее время не работает!

: -)

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

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

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

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

слаб справка

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

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
}

}

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

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

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

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

мое решение.

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

вы также можете добавить 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">