У меня особенная проблема с Loaders. В настоящее время я не уверен, что это ошибка в моем коде или я неправильно понимаю загрузчиков.
Приложение
Проблема возникает при разговорах (представьте что-то похожее на Whatsapp). Используемые загрузчики реализованы на основе примера AsyncTaskLoader. Я использую библиотеку поддержки.
- В OnCreate я запускаю загрузчик для получения кэшированных сообщений.
- Когда CachedMessageLoader заканчивается, он запускает RefreshLoader для извлечения (онлайн) новейших сообщений.
- Каждый тип загрузчика как отдельный идентификатор (скажем, в автономном режиме: 1 онлайн: 2)
Это работает очень хорошо, со следующим исключением.
Проблема
Когда я открываю другой фрагмент (и добавляю транзакцию к стопке), а затем использую Back-Key для возврата к фрагменту беседы, 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();