Чтобы ответить на этот вопрос, вам нужно покопаться в LoaderManager
коде. Хотя документация для самого LoaderManager недостаточно ясна (иначе бы не было этого вопроса), документация для LoaderManagerImpl, подкласса абстрактного LoaderManager, гораздо более информативна.
initLoader
Вызов для инициализации конкретного идентификатора с помощью загрузчика. Если с этим идентификатором уже связан загрузчик, он остается неизменным, а все предыдущие обратные вызовы заменяются вновь предоставленными. Если в настоящее время загрузчика для идентификатора нет, создается и запускается новый.
Эту функцию обычно следует использовать при инициализации компонента, чтобы гарантировать создание загрузчика, на который он полагается. Это позволяет повторно использовать данные существующего загрузчика, если он уже есть, так что, например, когда действие создается повторно после изменения конфигурации, ему не нужно повторно создавать свои загрузчики.
restartLoader
Вызов для воссоздания загрузчика, связанного с определенным идентификатором. Если в настоящее время с этим идентификатором связан загрузчик, он будет отменен / остановлен / уничтожен соответствующим образом. Будет создан новый загрузчик с заданными аргументами, и его данные будут доставлены вам, когда они станут доступны.
[...] После вызова этой функции любые предыдущие загрузчики, связанные с этим идентификатором, будут считаться недействительными, и вы не будете получать от них дальнейшие обновления данных.
В основном есть два случая:
- Загрузчика с идентификатором не существует: оба метода создадут новый загрузчик, поэтому разницы нет.
- Загрузчик с идентификатором уже существует:
initLoader
заменит только обратные вызовы, переданные в качестве параметра, но не отменит или остановит загрузчик. Для a CursorLoader
это означает, что курсор остается открытым и активным (если это было до initLoader
вызова). `restartLoader, с другой стороны, отменит, остановит и уничтожит загрузчик (и закроет базовый источник данных, например, курсор) и создаст новый загрузчик (который также создаст новый курсор и повторно запустит запрос, если загрузчик CursorLoader).
Вот упрощенный код для обоих методов:
initLoader
LoaderInfo info = mLoaders.get(id);
if (info == null) {
// Loader doesn't already exist -> create new one
info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
// Loader exists -> only replace callbacks
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
restartLoader
LoaderInfo info = mLoaders.get(id);
if (info != null) {
LoaderInfo inactive = mInactiveLoaders.get(id);
if (inactive != null) {
// does a lot of stuff to deal with already inactive loaders
} else {
// Keep track of the previous instance of this loader so we can destroy
// it when the new one completes.
info.mLoader.abandon();
mInactiveLoaders.put(id, info);
}
}
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
Как мы видим, если загрузчик не существует (info == null), оба метода создадут новый загрузчик (info = createAndInstallLoader (...)). В случае, если загрузчик уже существует, initLoader
только заменяет обратные вызовы (info.mCallbacks = ...), при этом restartLoader
деактивирует старый загрузчик (он будет уничтожен, когда новый загрузчик завершит свою работу), а затем создает новый.
Таким образом, теперь ясно, когда использовать, initLoader
а когда использовать, restartLoader
и почему имеет смысл иметь два метода.
initLoader
используется для проверки наличия инициализированного загрузчика. Если такового не существует, создается новый, если он уже существует, он используется повторно. Мы всегда используем этот метод, ЕСЛИ нам не нужен новый загрузчик, потому что запрос для выполнения изменился (не базовые данные, а фактический запрос, как в операторе SQL для CursorLoader), и в этом случае мы будем вызывать restartLoader
.
Деятельность / Фрагмент жизненного цикла не имеет ничего общего с решением использовать один или другой метод (и нет необходимости отслеживать звонки , используя флаг один выстрел , как предположил Симон)! Это решение принято исключительно исходя из «необходимости» нового погрузчика. Если мы хотим выполнить тот же запрос, который мы используем initLoader
, если мы хотим выполнить другой запрос, который мы используем restartLoader
.
Мы всегда могли использовать, restartLoader
но это было бы неэффективно. После поворота экрана или если пользователь уходит от приложения и позже возвращается к тому же самому Activity, мы обычно хотим показать тот же результат запроса, и поэтому restartLoader
без необходимости повторно создаст загрузчик и отклонит базовый (потенциально дорогой) результат запроса.
Очень важно понимать разницу между загружаемыми данными и «запросом» для загрузки этих данных. Предположим, мы используем CursorLoader, запрашивающий у таблицы заказы. Если в эту таблицу добавляется новый порядок, CursorLoader использует onContentChanged (), чтобы информировать пользовательский интерфейс об обновлении и отображении нового порядка ( restartLoader
в этом случае нет необходимости ). Если мы хотим отображать только открытые ордера, нам нужен новый запрос, и мы должны использовать его restartLoader
для возврата нового CursorLoader, отражающего новый запрос.
Есть ли какая-то связь между двумя методами?
Они совместно используют код для создания нового загрузчика, но они делают разные вещи, когда загрузчик уже существует.
Есть ли вызов restartLoader
всегда позвонить initLoader
?
Нет, никогда не бывает.
Могу ли я позвонить, restartLoader
не звоня initLoader
?
Да.
Безопасно ли initLoader
дважды звонить для обновления данных?
initLoader
Дважды позвонить безопасно, но данные не будут обновлены.
Когда мне следует использовать один из двух и почему ?
Это должно (надеюсь) быть ясным после моих объяснений выше.
Изменения конфигурации
LoaderManager сохраняет свое состояние при изменении конфигурации (включая изменение ориентации), поэтому вы можете подумать, что нам больше нечего делать. Подумай еще раз ...
Во-первых, LoaderManager не сохраняет обратные вызовы, поэтому, если вы ничего не сделаете, вы не получите вызовов к своим методам обратного вызова onLoadFinished()
и т. Д., И это, скорее всего, сломает ваше приложение.
Поэтому мы ДОЛЖНЫ вызвать хотя бы initLoader
для восстановления методы обратного вызова (это restartLoader
, конечно, тоже возможно). В документации указано:
Если в момент вызова вызывающая сторона находится в своем запущенном состоянии, а запрошенный загрузчик уже существует и сгенерировал свои данные, то обратный вызов onLoadFinished(Loader, D)
будет вызван немедленно (внутри этой функции) [...].
Это означает, что если мы позвоним initLoader
после изменения ориентации, мы сразу же получим onLoadFinished
вызов, потому что данные уже загружены (при условии, что это было до изменения). Хотя это звучит прямо, но может быть сложно (разве мы не все любим Android ...).
Мы должны различать два случая:
- Самостоятельно обрабатывает изменения конфигурации: это относится к фрагментам, использующим setRetainInstance (true), или к Activity с соответствующим
android:configChanges
тегом в манифесте. Эти компоненты не получат вызов onCreate, например, после поворота экрана, поэтому не забывайте вызывать
initLoader/restartLoader
другой метод обратного вызова (например, in
onActivityCreated(Bundle)
). Чтобы иметь возможность инициализировать загрузчик (-ы), идентификаторы загрузчика должны быть сохранены (например, в списке). Поскольку компонент сохраняется при изменении конфигурации, мы можем просто перебрать существующие идентификаторы загрузчика и вызвать initLoader(loaderid,
...)
.
- Сам не обрабатывает изменения конфигурации: в этом случае загрузчики могут быть инициализированы в onCreate, но нам нужно вручную сохранить идентификаторы загрузчиков, иначе мы не сможем сделать необходимые вызовы initLoader / restartLoader. Если идентификаторы хранятся в ArrayList, мы должны выполнить
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
onSaveInstanceState и восстановить идентификаторы в onCreate:
loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)
перед тем, как сделать вызов (ы) initLoader.
initLoader
(и все обратные вызовы завершены, загрузчик простаивает) после ротации, вы не получитеonLoadFinished
обратный вызов, но если вы используете,restartLoader
вы получите?Вызов,
initLoader
когда загрузчик уже был создан (обычно это происходит после изменения конфигурации, например), сообщает LoaderManager о необходимости немедленной доставки самых последних данных загрузчикаonLoadFinished
. Если загрузчик еще не был создан (например, при первом запуске действия / фрагмента), вызов toinitLoader
сообщает LoaderManager о необходимости вызватьonCreateLoader
для создания нового загрузчика.Вызов
restartLoader
уничтожает уже существующий загрузчик (а также любые существующие данные, связанные с ним) и сообщает LoaderManager вызватьonCreateLoader
для создания нового загрузчика и инициировать новую загрузку.В документации об этом тоже довольно ясно:
initLoader
обеспечивает инициализацию и активность загрузчика. Если загрузчик еще не существует, он создается и (если действие / фрагмент в данный момент запущен) запускает загрузчик. В противном случае повторно используется последний созданный загрузчик.restartLoader
запускает новый или перезапускает существующий загрузчик в этом диспетчере, регистрирует обратные вызовы для него и (если действие / фрагмент в настоящее время запущено) начинает его загрузку. Если ранее был запущен загрузчик с таким же идентификатором, он будет автоматически уничтожен, когда новый загрузчик завершит свою работу. Обратный вызов будет доставлен до того, как старый загрузчик будет уничтожен.источник
initLoader()
inonCreate()
/onActivityCreated()
при первом запуске активности / фрагмента. Таким образом, когда пользователь впервые открывает действие, загрузчик будет создан в первый раз ... но при любых последующих изменениях конфигурации, когда все действие / фрагмент должны быть уничтожены, следующий вызовinitLoader()
просто вернет старыйLoader
вместо создание нового. Обычно вы используете,restartLoader()
когда вам нужно изменитьLoader
запрос (например, вы хотите получить отфильтрованные / отсортированные данные и т. Д.).Недавно у меня возникла проблема с несколькими диспетчерами загрузчиков и изменениями ориентации экрана, и я хотел бы сказать, что после множества проб и ошибок у меня работает следующий шаблон как для действий, так и для фрагментов:
(другими словами, установить некоторый флаг так , чтобы initLoader это всегда выполняется один раз и что restartLoader выполняется на 2 - й и последующих проходах через onResume )
Кроме того, не забудьте назначить разные идентификаторы для каждого из ваших загрузчиков в Activity (что может быть проблемой с фрагментами внутри этого действия, если вы не будете осторожны с нумерацией)
Я пробовал использовать только initLoader .... похоже, не работает эффективно.
Пробовал initLoader на onCreate с нулевыми аргументами (документы говорят, что это нормально) и restartLoader (с допустимыми аргументами) в onResume .... документы неверны, и initLoader выдает исключение нулевого указателя.
Пробовал только restartLoader ... какое-то время работает, но срабатывает при 5 или 6 переориентации экрана.
Пробовал initLoader в onResume ; снова работает какое-то время, а затем дует. (в частности, ошибка «Вызывается doRetain, когда не запускается:» ...)
Пробовал следующее: (отрывок из класса обложки, у которого идентификатор загрузчика передан в конструктор)
(который я нашел где-то в Stack-Overflow)
Опять же, это работало какое-то время, но время от времени возникали сбои.
Из того, что я могу выяснить во время отладки, я думаю, что есть какое-то отношение к состоянию экземпляра сохранения / восстановления, которое требует, чтобы initLoader (/ s) запускался в части жизненного цикла onCreate, если они должны пережить вращение цикла , (Я могу ошибаться.)
в случае менеджеров, которые не могут быть запущены до тех пор, пока результаты не вернутся от другого менеджера или задачи (т.е. не могут быть инициализированы в onCreate ), я использую только initLoader . (Возможно, я ошибаюсь в этом, но, похоже, это работает. Эти вторичные загрузчики не являются частью непосредственного состояния экземпляра, поэтому использование initLoader может быть правильным в этом случае)
Глядя на диаграммы и документы, я бы подумал, что initLoader должен войти в onCreate и restartLoader в onRestart для действий, но это оставляет фрагменты с использованием другого шаблона, и у меня не было времени исследовать, действительно ли это стабильно. Может ли кто-нибудь еще прокомментировать, успешно ли он использует этот шаблон для занятий?
источник
initLoader
будет повторно использовать те же параметры, если загрузчик уже существует. Он немедленно возвращается, если старые данные уже загружены, даже если вы вызываете его с новыми параметрами. В идеале загрузчик должен автоматически уведомлять об активности новых данных. Если бы экран повернулся,initLoader
будет вызываться снова, и сразу же отобразятся старые данные.restartLoader
предназначен для случаев, когда вы хотите принудительно перезагрузить и изменить параметры. Если бы вы создавали экран входа в систему с помощью загрузчиков, вы бы вызывали толькоrestartLoader
каждый раз, когда нажимали кнопку. (Кнопка может быть нажата несколько раз из-за неправильных учетных данных и т. Д.). Вы будете звонить толькоinitLoader
при восстановлении сохраненного состояния экземпляра действия в случае поворота экрана во время входа в систему.источник
Если загрузчик уже существует, restartLoader остановит / отменит / уничтожит старый, а initLoader просто инициализирует его заданным обратным вызовом. Я не могу понять, что делают старые обратные вызовы в этих случаях, но я думаю, что от них просто откажутся.
Я просканировал http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.java, но я не могу узнать, что именно разница в том, что помимо этого методы делают разные вещи. Поэтому я бы сказал, используйте initLoader в первый раз и перезапустите его в следующие раз, хотя я не могу с уверенностью сказать, что именно каждый из них будет делать.
источник
initLoader
в этом случае делать?Загрузчик инициализации при первом запуске использует метод loadInBackground (), при втором запуске он будет опущен. Итак, на мой взгляд, лучшее решение:
////////////////////////////////////////////////// /////////////////////////
Я потратил много времени, чтобы найти это решение - в моем случае restartLoader (...) не работал должным образом. Единственный forceLoad () позволяет завершить предыдущий поток загрузки без обратного вызова (так что все транзакции db будут завершены правильно) и снова запускает новый поток. Да, требует дополнительного времени, но работает более стабильно. Обратный вызов примет только последний запущенный поток. Таким образом, если вы хотите провести тесты с прерыванием ваших транзакций db - добро пожаловать, попробуйте перезапуститьLoader (...), иначе forceLoad (). Единственное удобство restartLoader (...) - это доставка новых исходных данных, я имею в виду параметры. И не забудьте в этом случае уничтожить загрузчик в методе onDetach () подходящего фрагмента. Также имейте в виду, что иногда, когда вы занимаетесь чем-то, и пусть говорят, 2 фрагмента с загрузчиком, каждый из которых включает действие - вы получите доступ только к 2 диспетчерам загрузчиков, поэтому Activity разделяет свой LoaderManager с фрагментом (ами), который сначала отображается на экране во время загрузки. Попробуйте LoaderManager.enableDebugLogging (true); чтобы увидеть подробности в каждом конкретном случае.
источник
getLoader(0)
вtry { ... } catch (Exception e) { ... }
.