Щоб відповісти на це запитання, вам потрібно зануритися в LoaderManager
код. Хоча сама документація для LoaderManager недостатньо чітка (або не виникало б цього питання), документація для LoaderManagerImpl, підкласу абстрактного LoaderManager, є набагато просвітнішою.
initLoader
Зателефонуйте ініціалізувати певний ідентифікатор за допомогою навантажувача. Якщо для цього ідентифікатора вже є асоційований навантажувач, він залишається незмінним і будь-які попередні зворотні дзвінки замінюються на щойно надані. Якщо наразі немає навантажувача для ідентифікатора, створюється і запускається новий.
Ця функція, як правило, повинна використовуватися при ініціалізації компонента, щоб забезпечити створення завантажувача, на який він покладається. Це дозволяє йому повторно використовувати дані наявних навантажувачів, якщо такі вже є, так що, наприклад, коли активність заново створюється після зміни конфігурації, їй не потрібно повторно створювати завантажувачі.
restartLoader
Заклик повторно створити навантажувач, пов’язаний з певним ідентифікатором. Якщо в даний час навантажувач пов'язаний з цим ідентифікатором, він буде скасований / зупинений / знищений, як це доречно. Буде створений новий навантажувач із заданими аргументами, і його дані будуть доставлені вам, коли вони стануть доступними.
[...] Після виклику цієї функції будь-які попередні навантажувачі, пов’язані з цим ідентифікатором, будуть вважатися недійсними, і ви більше не отримуватимете від них оновлення даних.
В основному є два випадки:
- Навантажувач з ідентифікатором не існує: обидва способи створять новий завантажувач, тому різниці там немає
- Навантажувач з ідентифікатором вже існує:
initLoader
замінить лише зворотні дзвінки, передані як параметр, але не скасовує та не зупиняє завантажувач. Для 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
але це було б неефективно. Після обертання екрана або якщо користувач відходить від програми та повертається пізніше до тієї ж діяльності, ми зазвичай хочемо показати той самий результат запиту, і тому ми 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), або для діяльності з
android:configChanges
тегом відповідно до маніфесту. Ці компоненти не отримуватимуть дзвінок onCreate після, наприклад, обертання екрана, тому майте на увазі, щоб викликати
initLoader/restartLoader
інший метод зворотного виклику (наприклад, у
onActivityCreated(Bundle)
). Щоб мати можливість ініціалізувати навантажувач (и), ідентифікатори завантажувача потрібно зберігати (наприклад, у списку). Оскільки компонент зберігається через зміни конфігурації, ми можемо просто перевести цикл на існуючі ідентифікатори завантажувача та викликати initLoader(loaderid,
...)
.
- Не обробляє самих змін конфігурації: у цьому випадку завантажувачі можуть бути ініціалізовані в onCreate, але нам потрібно зберегти ідентифікатори завантажувача вручну або ми не зможемо здійснити необхідні дзвінки initLoader / restartLoader. Якщо ідентифікатори зберігаються в ArrayList, ми зробимо
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
в onSaveInstanceState і відновимо ідентифікатори в onCreate:
loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)
перед тим, як здійснити виклик initLoader.
initLoader
(і всі зворотні дзвінки завершені, навантажувач не працює) після обертання ви не отримаєтеonLoadFinished
зворотний дзвінок, але якщо ви будете використовувати його,restartLoader
ви будете?