Почему onLoadFinished вызывается снова после возобновления фрагмента?

У меня особенная проблема с Loaders. В настоящее время я не уверен, что это ошибка в моем коде или я неправильно понимаю загрузчиков.

Приложение

Проблема возникает из-за разговоров (представьте что-то похожее на Whatsapp). Используемые загрузчики реализованы на основе примера AsyncTaskLoader . Я использую библиотеку поддержки.

  • В OnCreate я запускаю загрузчик для получения кэшированных сообщений.
  • Когда CachedMessageLoader заканчивается, он запускает RefreshLoader для извлечения (онлайн) новейших сообщений.
  • Каждый тип загрузчика как отдельный идентификатор (скажем, в автономном режиме: 1 онлайн: 2)

Это работает очень хорошо, со следующим исключением.

проблема

Когда я открываю другой фрагмент (и добавляю транзакцию к стоп-кадрам), а затем использую Back-Key для возврата к onLoadFinished беседы, onLoadFinished вызывается снова с обоими результатами ранее. Этот вызов происходит до того, как у фрагмента появилась возможность снова запустить загрузчик …

Это предоставление «старых» результатов, которые я получил до получения дублированных сообщений.

Вопрос

  • Почему эти результаты доставляются снова?
  • Я неправильно использую эти загрузчики?
  • Могу ли я «аннулировать» результаты, чтобы гарантировать, что я получаю их только один раз, или я должен сам устранить дубликаты?

Трассировка стека вызовов

 MyFragment.onLoadFinished(Loader, Result) line: 369 MyFragment.onLoadFinished(Loader, Object) line: 1 LoaderManagerImpl$LoaderInfo.callOnLoadFinished(Loader, Object) line: 427 LoaderManagerImpl$LoaderInfo.reportStart() line: 307 LoaderManagerImpl.doReportStart() line: 768 MyFragment(Fragment).performStart() line: 1511 FragmentManagerImpl.moveToState(Fragment, int, int, int, boolean) line: 957 FragmentManagerImpl.moveToState(int, int, int, boolean) line: 1104 BackStackRecord.popFromBackStack(boolean) line: 764 ... 

Обновление 1 Указанные выше загрузчики инициируются фрагментом беседы:

 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); Bundle args = getArguments(); m_profileId = args.getString(ArgumentConstants.ARG_USERID); m_adapter = new MessageAdapter(this); if (savedInstanceState != null) { restoreInstanceState(savedInstanceState); } if (m_adapter.isEmpty()) { Bundle bundle = new Bundle(); bundle.putString(ArgumentConstants.ARG_USERID, m_profileId); getLoaderManager().restartLoader(R.id.loader_message_initial, bundle, this); } else { // Omitted: Some arguments passed in Bundle Bundle b = new Bundle(). getLoaderManager().restartLoader(R.id.loader_message_refresh, b, this); } } @Override public void onResume() { super.onResume(); // Omitted: setting up UI state / initiating other loaders that work fine } @Override public AbstractMessageLoader onCreateLoader(final int type, final Bundle bundle) { final SherlockFragmentActivity context = getSherlockActivity(); context.setProgressBarIndeterminateVisibility(true); switch (type) { case R.id.loader_message_empty: return new EmptyOnlineLoader(context, bundle); case R.id.loader_message_initial: return new InitialDBMessageLoader(context, bundle); case R.id.loader_message_moreoldDB: return new OlderMessageDBLoader(context, bundle); case R.id.loader_message_moreoldOnline: return new OlderMessageOnlineLoader(context, bundle); case R.id.loader_message_send: sendPreActions(); return new SendMessageLoader(context, bundle); case R.id.loader_message_refresh: return new RefreshMessageLoader(context, bundle); default: throw new UnsupportedOperationException("Unknown loader"); } } @Override public void onLoadFinished(Loader<Holder<MessageResult>> loader, Holder<MessageResult> holder) { if (getSherlockActivity() != null) { getSherlockActivity().setProgressBarIndeterminateVisibility(false); } // Omitted: Error handling of result (can contain exception) List<PrivateMessage> unreadMessages = res.getUnreadMessages(); switch (type) { case R.id.loader_message_moreoldDB: { // Omitted error handling (no data) if (unreadMessages.isEmpty()) { m_hasNoMoreCached = true; // Launch an online loader Bundle b = new Bundle(); // Arguments omitted getLoaderManager().restartLoader(R.id.loader_message_moreoldOnline, b, ConversationFragment.this); } // Omitted: Inserting results into adapter } case R.id.loader_message_empty: { // Online load when nothing in DB // Omitted: error/result handling handling break; } case R.id.loader_message_initial: { // Latest from DB, when opening // Omitted: Error/result handling // If we found nothing, request online if (unreadMessages.isEmpty()) { Bundle b = new Bundle(); // Omitted: arguments getLoaderManager().restartLoader(R.id.loader_message_empty, b, this); } else { // Just get new stuff Bundle b = new Bundle(); // Omitted: Arguments getLoaderManager().restartLoader(R.id.loader_message_refresh, b, this); } break; } // Omitted: Loaders that do not start other loaders, but only add returned data to the adapter default: throw new IllegalArgumentException("Unknown loader type " + type); } // Omitted: Refreshing UI elements } @Override public void onLoaderReset(Loader<Holder<MessageResult>> arg0) { } 

Обновите 2 My MainActivity (который в конечном итоге содержит все фрагменты) подклассы SherlockFragmentActivity и в основном запускают такие фрагменты:

  Fragment f = new ConversationFragment(); // Setup omitted f.setRetainInstance(false); // Omitted: Code related to navigation drawer FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction().replace(R.id.fragment_container_frame, f).commit(); 

Фрагмент разговора начинает фрагмент «display profile» следующим образом:

 DisplayProfileFragment f = new DisplayProfileFragment(); // Arguments omitted FragmentManager manager = getSherlockActivity().getSupportFragmentManager(); FragmentTransaction transaction = manager.beginTransaction(); transaction.replace(R.id.fragment_container_frame, f).addToBackStack(null).commit(); 

Solutions Collecting From Web of "Почему onLoadFinished вызывается снова после возобновления фрагмента?"

Существуют и другие подобные вопросы, такие как Android: LoaderCallbacks.OnLoadFinished вызывается дважды. Однако поведение крючков менеджера загрузчика – это то, что они есть. Вы можете либо уничтожить загрузчик после получения первого набора результатов

 public abstract void destroyLoader (int id) 

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

 public abstract void onLoaderReset (Loader<D> loader) 

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

Лично я бы использовал ContentProvider и CursorLoader для этого (для каждой строки данных должен быть уникальный _ID, но для сообщений, которые не должны быть проблемой).