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

Использование сохраненного фрагмента для размещения асинхронных задач не является новой идеей (см. Алекс Локвуд отлично сообщение в блоге по теме)

Но после использования этого я столкнулся с проблемами при доставке контента обратно в свою активность из обратных вызовов AsyncTask. В частности, я обнаружил, что попытка отклонить диалог может привести к исключению IllegalStateException. Опять же, объяснение этого можно найти в еще одно сообщение в блоге от Alex Lockwood. В частности, в этом разделе объясняется, что происходит:

Избегайте выполнения транзакций внутри асинхронных методов обратного вызова.

Это включает обычно используемые методы, такие как AsyncTask # onPostExecute() и LoaderManager.LoaderCallbacks # onLoadFinished(). Проблема с выполнение транзакций в этих методах заключается в том, что они не имеют знание текущего состояния жизненного цикла деятельности, когда они называется. Например, рассмотрим следующую последовательность событий:

  • Действие выполняет AsyncTask.
  • Пользователь нажимает клавишу "Главная", вызывая действия onSaveInstanceState() и onStop() называться.
  • AsyncTask завершается, и вызывается onPostExecute() не подозревая, что деятельность с тех пор была остановлена.
  • FragmentTransaction выполняется внутри метода onPostExecute() вызывая исключение.

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

Избегайте выполнения транзакций внутри асинхронных методов обратного вызова.

На самом деле должно быть:

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

Вопросы:

  • Если вы используете этот шаблон, вы должны отменить свою задачу, предотвращая обратные вызовы в onSaveInstanceState(), если они не вращаются?

Так же:

@Override
public void onSaveInstanceState(Bundle outState)
{
    if (!isChangingConfigurations())
    {
        //if we aren't rotating, we need to lose interest in the ongoing task and cancel it
        mRetainedFragment.cancelOnGoingTask();
    }
    super.onSaveInstanceState(outState);
}
  1. Следует ли вообще использовать сохраненные фрагменты для сохранения текущих задач? Будет ли более эффективным всегда отмечать что-то в вашей модели о текущем запросе? Или сделайте что-то вроде RoboSpice, где вы можете повторно подключиться к текущей задаче, если она находится на рассмотрении. Чтобы получить аналогичное поведение с сохраненным фрагментом, вам придется отменить задачу, если вы остановились по причинам, отличным от изменения конфигурации.

  2. Продолжая с первого вопроса: даже во время изменения конфигурации вы не должны создавать какие-либо обновления пользовательского интерфейса после onSaveInstanceState(), поэтому вы должны сделать что-то вроде этого:

Грубый код:

@Override
public void onSaveInstanceState(Bundle outState)
{
    if (!isChangingConfigurations())
    {
        //if we aren't rotating, we need to lose interest in the ongoing task and cancel it
        mRetainedFragment.cancelOnGoingTask();
    }
    else
    {
        mRetainedFragment.beginCachingAsyncResponses();
    }
    super.onSaveInstanceState(outState);
}

@Override
public void onRestoreInstanceState(Bundle inState)
{
    super.onRestoreInstanceState(inState);
    if (inState != null)
    {
        mRetainedFragment.stopCachingAndDeliverAsyncResponses();
    }
}

beginCachingAsyncResponses() сделает что-то вроде PauseHandler, увиденного здесь

Ответ 1

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

Хотя это не документировано, изменения состояния активности выполняются в синхронных блоках. То есть, когда начинается изменение конфигурации, поток пользовательского интерфейса будет занят полностью от onPause до onResume. Поэтому не нужно иметь что-либо вроде beginCachingAsyncResponses, как я имел в своем вопросе, так как невозможно было бы перейти на основной поток после запуска изменения конфигурации.

Вы можете убедиться, что это верно, сканируя исходный код: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.0.2_r1/android/app/ActivityThread.java#3886, смотря на это, похоже, что onSaveInstancestate выполняется последовательно с handleDestroyActivity... И поэтому было бы невозможно обновить пользовательский интерфейс, если бы он потерялся во время изменения конфигурации.

Итак, этого должно быть достаточно:

@Override
public void onSaveInstanceState(Bundle outState)
{
    if (!isChangingConfigurations())
    {
        //if we aren't rotating, we need to lose interest in the ongoing task and cancel it
        mRetainedFragment.cancelOnGoingTask();
    }
    super.onSaveInstanceState(outState);
}

Из сохраненного фрагмента важно получить доступ к активности из основного потока:

public void onSomeAsyncNetworkIOResult(Result r)
{
    Handler mainHandler = new Handler(Looper.getMainLooper());
    Runnable myRunnable = new Runnable()
    {
        //If we were to call getActivity here, it might be destroyed by the time we hit the main thread
        @Override 
        public void run()
        {
            //Now we are running on the UI thread, we cannot be part-way through a config change
            // It crucial to call getActivity from the main thread as it might change otherwise
            ((MyActivity)getActivity()).handleResultInTheUI(r);
        }
    };
    mainHandler.post(myRunnable);
    return;

}

Ответ 2

С точки зрения разработчика, избегать NPE в реальном приложении - это первый порядок ведения бизнеса. Для таких методов, как onPostExecute() of AsyncTask и onResume() и onError() в Volley Request, добавьте:

Activity = getActivity();
if(activity != null && if(isAdded())){

    // proceed ...

}

Внутри Activity он должен быть

if(this != null){

    // proceed ...

}

Это неэлегантно. И неэффективен, потому что работа над другой нитью не ослабевает. Но это позволит приложению уклониться от NPE. Кроме того, существует вызов различных методов cancel() в onPause(), onStop() и onDestroy().

Теперь мы перейдем к более общей проблеме изменений конфигурации и выхода приложений. Я читал, что AsyncTask и Volley Request должны выполняться только из Service, а не Activity s, потому что Service продолжают работать, даже если пользователь "выйдет" из приложения.