Погрузчик поставляет результат в неправильный фрагмент
У меня есть активность с прокручиванием вкладок с помощью вкладок ActionBar на основе примера разработчика Android.
Каждая вкладка отображает фрагмент, и каждый фрагмент (на самом деле, Шерлок-Фрагмент) загружает другой вид удаленного запроса api через пользовательский AsyncTaskLoader.
Проблема заключается в том, что если вы нажмете вкладку, чтобы переместить 2 вкладки/страницы, а фрагмент для вкладки, которую вы оставляете (старый фрагмент), загружает результат, этот результат доставляется к фрагменту для вкладки, которую вы перемещаете к (новый фрагмент). В моем случае это приводит к исключению ClassCastException, поскольку ожидаемые результаты имеют несовместимые типы.
В коде суть сущности:
Погрузчики:
public class FooLoader extends AsyncTaskLoader<Foo>
public class BarLoader extends AsyncTaskLoader<Bar>
Фрагменты:
public class FooFragment extends Fragment implements LoaderManager.LoaderCallbacks<Foo> {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(0, null, this);
}
public Loader<Foo> onCreateLoader(int id, Bundle args) { return new FooLoader(); }
...
}
public class BarFragment extends Fragment implements LoaderManager.LoaderCallbacks<Bar> {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(0, null, this);
}
public Loader<Bar> onCreateLoader(int id, Bundle args) { return new BarLoader(); }
...
}
Код управления вкладками приведен в вышеупомянутом примере. Между вкладками Foo и Bar есть третья вкладка (назовите ее Baz). Когда мы переходим с вкладки Foo на вкладку "Бар", нажимая на вкладку "Бар" после того, как FooFragment вызвал initLoader в своем LoaderManager, но до того, как вызывается FooFragment.onLoadFinished, мы заканчиваем ClassCastException при вызове BarFragment.onLoadFinished:
java.lang.ClassCastException: com.example.Foo cannot be cast to com.example.Bar
at com.example.BarFragment.onLoadFinished(BarFragment.java:1)
at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:427)
at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:562)
at com.example.BarFragment.onCreate(BarFragment.java:36)
at android.support.v4.app.Fragment.performCreate(Fragment.java:1437)
...
Почему это происходит, и как его можно предотвратить? Это выглядит из журналов отладки, так как тот же LoaderManager повторно используется в фрагменте Bar (хотя у Baz-фрагмента есть свой собственный), но я не знаю, почему это должно произойти.
Обновление: Использование разных идентификаторов загрузчика в каждом фрагменте устраняет крах (или, кажется, - я действительно не знаю почему), но я бы предпочел не делать этого. В одном из фрагментов я фактически создаю идентификаторы динамически и не хочу предполагать, что столкновения не будет. Кроме того, это решение странно для меня - идентификаторы загрузчика должны быть локальными для каждого фрагмента (в противном случае, почему я могу иметь загрузчики с одинаковыми идентификаторами в разных фрагментах при обычных обстоятельствах?)
Кажется, я также могу устранить крах, вызывая setOffscreenPageLimit(2)
на моем ViewPager, так что просмотр Foo не отбрасывается, когда мы переключаемся на представление Bar. Но это обходное решение, а не общее решение.
Полный код: Я создал пример приложения демонстрирующий ошибку. Он включает в себя monkeyrunner script для принудительной ошибки (хотя он может не работать для всех размеров экрана).
Ответы
Ответ 1
Вы можете избежать этой проблемы, вызвав initLoader
в onActivityCreated
, а не в onCreate
- как отмечено Алексом Локвудом в комментариях к вопросу. Измененный код ниже.
Исправленные фрагменты:
public class FooFragment extends Fragment implements LoaderManager.LoaderCallbacks<Foo> {
...
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(0, null, this);
}
public Loader<Foo> onCreateLoader(int id, Bundle args) { return new FooLoader(); }
...
}
public class BarFragment extends Fragment implements LoaderManager.LoaderCallbacks<Bar> {
...
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(0, null, this);
}
public Loader<Bar> onCreateLoader(int id, Bundle args) { return new BarLoader(); }
...
}
Ответ 2
Не используйте 0 в качестве своего идентификатора. Поскольку LoaderManager
знает, что они предназначены для того же самого загрузчика.
Вы можете определить уникальные идентификаторы в файле ресурсов XML
<item type="id" name="loader_foo" />
<item type="id" name="loader_bar" />
И получить их от R.
loaderManager.initLoader(R.id.loader_foo, null, new LoaderCallbacks(){});
В документации для LoaderManager
говорится: "Идентификаторы привязаны к определенному экземпляру LoaderManager". И экземпляры LoaderMananger
привязаны к Activities
.
Для генерации уникальных идентификаторов вы можете вручную назначить им идентификаторы с большими пробелами между ними.
private static final int LOADER_FOO = 1;
private static final int LOADER_BAR = 100;
for(int i = 0; i < 10; ++i){
loaderManager.initLoader(LOADER_FOO + i, null, new LoaderCallbacks(){});
}
Ответ 3
Недавно я работал с SimpleCursorAdapter и загрузчиками для отображения данных из таблиц sql lite. Я долгое время отлаживал ошибку "нет такого столбца" _id ". который произошел в моем втором фрагменте onLoadFinished(). В моей первой таблице был столбец" _id ", но моя вторая таблица did not, что заставило меня поверить, что загрузчик доставляет неправильный фрагмент. Поэтому на всякий случай вы сделали что-то подобное, убедитесь, что ваши таблицы sql сначала имеют столбец" _id". Надеюсь, это поможет любому, у кого была такая же проблема, как и я.