Как доставлять и сохранять изменения в пользовательском интерфейсе из асинхронной задачи, размещенной с сохраненным фрагментом?
Использование сохраненного фрагмента для размещения асинхронных задач не является новой идеей (см. Алекс Локвуд отлично сообщение в блоге по теме)
Но после использования этого я столкнулся с проблемами при доставке контента обратно в свою активность из обратных вызовов 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);
}
-
Следует ли вообще использовать сохраненные фрагменты для сохранения текущих задач? Будет ли более эффективным всегда отмечать что-то в вашей модели о текущем запросе? Или сделайте что-то вроде RoboSpice, где вы можете повторно подключиться к текущей задаче, если она находится на рассмотрении. Чтобы получить аналогичное поведение с сохраненным фрагментом, вам придется отменить задачу, если вы остановились по причинам, отличным от изменения конфигурации.
-
Продолжая с первого вопроса: даже во время изменения конфигурации вы не должны создавать какие-либо обновления пользовательского интерфейса после 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
продолжают работать, даже если пользователь "выйдет" из приложения.