Android: LoaderCallbacks.OnLoadFinished вызывается дважды

Я заметил странную ситуацию с помощью Android Loaders и Fragments. Когда я вызываю LoaderManager.initLoader() после изменения ориентации onLoadFinished не вызывается (хотя документация предполагает, что я должен быть готов к этому), но он вызывается дважды после этого. Вот ссылка на сообщение в группах google, которые описывают ту же ситуацию https://groups.google.com/forum/?fromgroups#!topic/android-developers/aA2vHYxSskU. Я написал пример приложения, в котором я только запускаю простой загрузчик в Fragment.onActivityCreated(), чтобы проверить, не произошло ли это, и это произойдет. Кто-нибудь заметил это?

Ответ 1

Вы можете поместить метод initLoader() внутри обратного вызова Fragment onResume(); то Loader onLoadFinished() больше не будет вызываться дважды.

    @Override
public void onResume()
{
    super.onResume();
    getLoaderManager().initLoader(0, null, this);
}

Ответ 2

Эта проблема проявилась для меня с CursorLoader, возвращающим курсор, который уже был закрыт:

android.database.StaleDataException: Attempted to access a cursor after it has been closed.

Я предполагаю, что это ошибка или недосмотр. Хотя перемещение initLoader() в onResume может работать, то, что я смог сделать, это удалить Loader, когда я закончил с ним:

Чтобы запустить загрузчик (в моем onCreate):

  getLoaderManager().initLoader(MUSIC_LOADER_ID, null, this);

Затем после того, как я закончил с ним (в основном в конце onLoadFinished)

  getLoaderManager().destroyLoader(MUSIC_LOADER_ID);

Это похоже на поведение, как ожидалось, никаких дополнительных вызовов.

Ответ 3

документация по initLoader,

Если в точке вызова вызывающий абонент находится в запущенном состоянии, а запрошенный загрузчик уже существует и сгенерировал его данные, затем callback onLoadFinished (Loader, D)

Я предлагаю вам реализовать что-то вроде функции onStartLoading в этом образце

Для быстрого тестирования вы можете попробовать:

@Override protected void onStartLoading() {
    forceLoad();
}

Эта функция запуска loadInBackground, а затем onLoadFinished в фрагменте.

В любом случае, если вы присоедините некоторый код, я попытаюсь дать вам больше информации.

Ответ 4

Я решил проблему onLoadFinished, называемую дважды так. В вашем Fragment.onActivityCreated() запустите свой загрузчик, как этот

if (getLoaderManager().getLoader(LOADER_ID) == null) {
    getLoaderManager().initLoader(LOADER_ID, bundle, loaderCallbacks);
} else {
    getLoaderManager().restartLoader(LOADER_ID, bundle, loaderCallbacks);

}

здесь loaderCallbacks реализует ваши обычные обратные вызовы Loader

private LoaderManager.LoaderCallbacks<T> loaderCallbacks
        = new LoaderManager.LoaderCallbacks<T>() {
    @Override
    public Loader<T> onCreateLoader(int id, Bundle args) {
        ...
        ...
    }

    @Override
    public void onLoadFinished(Loader<T> loader, T data) {
        ...
        ...
    }

    @Override
    public void onLoaderReset(Loader<T> loader) {
        ...
        ...
    }
};

Ответ 5

Проблема состоит в том, что он дважды вызывался:
1. из Fragment.onStart
2. из FragmentActivity.onStart

Единственное различие заключается в том, что в Fragment.onStart он проверяет, является ли mLoaderManager!= null. Это означает, что если вы вызываете getLoadManager перед onStart, например, в onActivityCreated, он получит/создаст диспетчер нагрузки и он будет вызван. Чтобы этого избежать, вам нужно вызвать его позже, например, в onResume.

Ответ 6

При вызове initLoader из onActivityCreated вы можете обнаружить поворот:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    if (savedInstanceState == null) {
        // fresh new fragment, not orientation/config change
        getLoaderManager().initLoader(YOUR_LOADER_ID, null, mCallbacks);
    }
    ...
}

Таким образом, загрузчик ведет себя как ожидалось, что приводит к одиночному вызову onLoadFinished.
Он больше не называется для вращения, поэтому, если вы хотите данные загрузчика, вы можете сохранить его в своем фрагменте, например, переопределив onSaveInstanceState.

Изменить:
Я просто понял, что onLoadFinished не будет вызываться, если ротация происходит во время загрузки loadInBackground. Чтобы исправить это, вам все равно нужно вызвать initLoader после вращения, если данные из загрузчика еще не доступны.

Надеюсь, что это поможет.

Ответ 7

Вы также можете сравнить объект данных в onLoadFinished (загрузчик загрузчика, данные объекта). Если объект данных совпадает с тем, который у вас уже есть, вы можете просто ничего не делать, когда вызывается onLoadFinished. Например:

public void onLoadFinished(Loader loader, Object data) {
        if(data != null && mData != data){
            //Do something
        }
}

Ответ 8

Поскольку все поиски этой темы неизбежно заканчиваются здесь, я просто хотел добавить свой опыт. Как сказал @jperera, виновником было то, что LoaderManager будет вызывать onLoadFinished(), если загрузчики уже существуют. В моем случае у меня были фрагменты в FragmentPager и прокрутка 2 вкладок, а затем прокрутка рядом с ним снова заставила бы мой старый фрагмент начать создавать себя.

Так как размещение initLoader() внутри onCreate() также вызывает двойные обратные вызовы, я поместил initLoader() внутри onResume(). Но последовательность событий заканчивается тем, что onCreate(), LoaderManager вызывает обратные вызовы, поскольку существуют загрузчики, затем вызывается onResume(), вызывая очередную последовательность initLoader() и onLoadFinished(). IE, еще один двойной обратный вызов.

Решение

Я нашел быстрое решение "Matt" . После того, как все ваши данные загружены (если у вас более одного загрузчика), уничтожьте все загрузчики, чтобы их обратные вызовы не назывались дополнительным временем.

Ответ 9

У меня есть эта проблема. Но я использовал для вызова destroyloader(YOUR_ID) в методах loaderfinished. то загрузчик снова не вызывает задачу backgrdound дважды.